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,160 @@
|
||||
import { Response } from 'express';
|
||||
import { aiService } from '../services/aiService';
|
||||
import { storage } from '../storage';
|
||||
import { AuthenticatedRequest } from '../middleware/auth';
|
||||
|
||||
export const aiController = {
|
||||
async getDailyJoke(req: any, res: Response) {
|
||||
try {
|
||||
// Check if we already have a joke for today
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
const existingJokes = await storage.getAIInteractions(undefined, 'joke', 1);
|
||||
const todayJoke = existingJokes.find(joke => {
|
||||
const jokeDate = new Date(joke.createdAt);
|
||||
jokeDate.setHours(0, 0, 0, 0);
|
||||
return jokeDate.getTime() === today.getTime();
|
||||
});
|
||||
|
||||
if (todayJoke) {
|
||||
return res.json({
|
||||
joke: todayJoke.response,
|
||||
cached: true,
|
||||
timestamp: todayJoke.createdAt
|
||||
});
|
||||
}
|
||||
|
||||
// Generate new daily joke
|
||||
const jokeResponse = await aiService.generateDailyJoke();
|
||||
|
||||
res.json({
|
||||
joke: jokeResponse.content,
|
||||
category: jokeResponse.category,
|
||||
cached: false,
|
||||
processingTime: jokeResponse.processingTime,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get daily joke error:', error);
|
||||
res.status(500).json({ error: 'Failed to get daily joke' });
|
||||
}
|
||||
},
|
||||
|
||||
async generateJoke(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { personalized } = req.query;
|
||||
|
||||
let jokeResponse;
|
||||
if (personalized === 'true') {
|
||||
jokeResponse = await aiService.generatePersonalizedJoke(req.user.id);
|
||||
} else {
|
||||
jokeResponse = await aiService.generateDailyJoke();
|
||||
}
|
||||
|
||||
res.json({
|
||||
joke: jokeResponse.content,
|
||||
category: jokeResponse.category,
|
||||
personalized: personalized === 'true',
|
||||
processingTime: jokeResponse.processingTime,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Generate joke error:', error);
|
||||
res.status(500).json({ error: 'Failed to generate joke' });
|
||||
}
|
||||
},
|
||||
|
||||
async chat(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { message } = req.body;
|
||||
|
||||
if (!message) {
|
||||
return res.status(400).json({ error: 'Message is required' });
|
||||
}
|
||||
|
||||
const response = await aiService.chat(req.user.id, message);
|
||||
|
||||
res.json({
|
||||
response: response.content,
|
||||
processingTime: response.processingTime,
|
||||
modelUsed: response.modelUsed,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('AI chat error:', error);
|
||||
res.status(500).json({ error: 'Failed to process chat message' });
|
||||
}
|
||||
},
|
||||
|
||||
async getInteractions(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { type, limit } = req.query;
|
||||
|
||||
const interactions = await storage.getAIInteractions(
|
||||
req.user.id,
|
||||
type as string,
|
||||
limit ? parseInt(limit as string) : undefined
|
||||
);
|
||||
|
||||
res.json({ interactions });
|
||||
} catch (error) {
|
||||
console.error('Get AI interactions error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch AI interactions' });
|
||||
}
|
||||
},
|
||||
|
||||
async getStatus(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const status = await aiService.getModelStatus();
|
||||
|
||||
res.json({
|
||||
...status,
|
||||
uptime: process.uptime(),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get AI status error:', error);
|
||||
res.status(500).json({ error: 'Failed to get AI status' });
|
||||
}
|
||||
},
|
||||
|
||||
async generateInsight(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { type } = req.body;
|
||||
|
||||
if (!type || !['financial', 'productivity', 'general'].includes(type)) {
|
||||
return res.status(400).json({ error: 'Valid insight type is required (financial, productivity, general)' });
|
||||
}
|
||||
|
||||
const insight = await aiService.generateInsight(req.user.id, type);
|
||||
|
||||
res.json({
|
||||
insight: insight.content,
|
||||
type,
|
||||
processingTime: insight.processingTime,
|
||||
modelUsed: insight.modelUsed,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Generate insight error:', error);
|
||||
res.status(500).json({ error: 'Failed to generate insight' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,185 @@
|
||||
import { Request, Response } from 'express';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { storage } from '../storage';
|
||||
import { loginSchema, registerSchema } from '@shared/schema';
|
||||
import { AuthenticatedRequest } from '../middleware/auth';
|
||||
|
||||
export const authController = {
|
||||
async register(req: Request, res: Response) {
|
||||
try {
|
||||
const validatedData = registerSchema.parse(req.body);
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await storage.getUserByEmail(validatedData.email);
|
||||
if (existingUser) {
|
||||
return res.status(400).json({ error: 'User already exists with this email' });
|
||||
}
|
||||
|
||||
const existingUsername = await storage.getUserByUsername(validatedData.username);
|
||||
if (existingUsername) {
|
||||
return res.status(400).json({ error: 'Username already taken' });
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const saltRounds = 12;
|
||||
const hashedPassword = await bcrypt.hash(validatedData.password, saltRounds);
|
||||
|
||||
// Create user
|
||||
const user = await storage.createUser({
|
||||
username: validatedData.username,
|
||||
email: validatedData.email,
|
||||
password: hashedPassword,
|
||||
role: validatedData.role || 'standard',
|
||||
firstName: validatedData.firstName,
|
||||
lastName: validatedData.lastName,
|
||||
onboardingComplete: false,
|
||||
voiceEnabled: true,
|
||||
ttsEnabled: true
|
||||
});
|
||||
|
||||
// Remove password from response
|
||||
const { password, ...userWithoutPassword } = user;
|
||||
|
||||
res.status(201).json({
|
||||
message: 'User created successfully',
|
||||
user: userWithoutPassword
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
if (error instanceof Error && error.name === 'ZodError') {
|
||||
return res.status(400).json({ error: 'Invalid input data', details: error.message });
|
||||
}
|
||||
res.status(500).json({ error: 'Failed to create user' });
|
||||
}
|
||||
},
|
||||
|
||||
async login(req: Request, res: Response) {
|
||||
try {
|
||||
const validatedData = loginSchema.parse(req.body);
|
||||
|
||||
const user = await storage.getUserByEmail(validatedData.email);
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Invalid email or password' });
|
||||
}
|
||||
|
||||
const isValidPassword = await bcrypt.compare(validatedData.password, user.password);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({ error: 'Invalid email or password' });
|
||||
}
|
||||
|
||||
// Use passport login
|
||||
req.login(user, (err) => {
|
||||
if (err) {
|
||||
console.error('Login error:', err);
|
||||
return res.status(500).json({ error: 'Login failed' });
|
||||
}
|
||||
|
||||
const { password, ...userWithoutPassword } = user;
|
||||
res.json({
|
||||
message: 'Login successful',
|
||||
user: userWithoutPassword
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
if (error instanceof Error && error.name === 'ZodError') {
|
||||
return res.status(400).json({ error: 'Invalid input data' });
|
||||
}
|
||||
res.status(500).json({ error: 'Login failed' });
|
||||
}
|
||||
},
|
||||
|
||||
async logout(req: Request, res: Response) {
|
||||
req.logout((err) => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
return res.status(500).json({ error: 'Logout failed' });
|
||||
}
|
||||
res.json({ message: 'Logout successful' });
|
||||
});
|
||||
},
|
||||
|
||||
async me(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { password, ...userWithoutPassword } = req.user;
|
||||
res.json({ user: userWithoutPassword });
|
||||
} catch (error) {
|
||||
console.error('Get user error:', error);
|
||||
res.status(500).json({ error: 'Failed to get user information' });
|
||||
}
|
||||
},
|
||||
|
||||
async updateProfile(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const allowedUpdates = ['firstName', 'lastName', 'voiceEnabled', 'ttsEnabled'];
|
||||
const updates: any = {};
|
||||
|
||||
for (const field of allowedUpdates) {
|
||||
if (req.body[field] !== undefined) {
|
||||
updates[field] = req.body[field];
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length === 0) {
|
||||
return res.status(400).json({ error: 'No valid fields to update' });
|
||||
}
|
||||
|
||||
const updatedUser = await storage.updateUser(req.user.id, updates);
|
||||
if (!updatedUser) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
const { password, ...userWithoutPassword } = updatedUser;
|
||||
res.json({
|
||||
message: 'Profile updated successfully',
|
||||
user: userWithoutPassword
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Update profile error:', error);
|
||||
res.status(500).json({ error: 'Failed to update profile' });
|
||||
}
|
||||
},
|
||||
|
||||
async completeOnboarding(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { role, preferences } = req.body;
|
||||
|
||||
// Update user onboarding status and role if provided
|
||||
const updates: any = { onboardingComplete: true };
|
||||
if (role && ['standard', 'pro', 'admin'].includes(role)) {
|
||||
updates.role = role;
|
||||
}
|
||||
|
||||
const updatedUser = await storage.updateUser(req.user.id, updates);
|
||||
if (!updatedUser) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
// Save user preferences if provided
|
||||
if (preferences) {
|
||||
await storage.updateUserPreferences(req.user.id, preferences);
|
||||
}
|
||||
|
||||
const { password, ...userWithoutPassword } = updatedUser;
|
||||
res.json({
|
||||
message: 'Onboarding completed successfully',
|
||||
user: userWithoutPassword
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Complete onboarding error:', error);
|
||||
res.status(500).json({ error: 'Failed to complete onboarding' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
import { Response } from 'express';
|
||||
import { storage } from '../storage';
|
||||
import { financialRecordCreateSchema } from '@shared/schema';
|
||||
import { AuthenticatedRequest } from '../middleware/auth';
|
||||
|
||||
export const financialController = {
|
||||
async getRecords(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { startDate, endDate } = req.query;
|
||||
|
||||
let start: Date | undefined;
|
||||
let end: Date | undefined;
|
||||
|
||||
if (startDate) {
|
||||
start = new Date(startDate as string);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
end = new Date(endDate as string);
|
||||
}
|
||||
|
||||
const records = await storage.getFinancialRecords(req.user.id, start, end);
|
||||
|
||||
res.json({ records });
|
||||
} catch (error) {
|
||||
console.error('Get financial records error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch financial records' });
|
||||
}
|
||||
},
|
||||
|
||||
async getSummary(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { startDate, endDate, period } = req.query;
|
||||
|
||||
let start: Date | undefined;
|
||||
let end: Date | undefined;
|
||||
|
||||
if (period) {
|
||||
const now = new Date();
|
||||
switch (period) {
|
||||
case 'week':
|
||||
start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
end = now;
|
||||
break;
|
||||
case 'month':
|
||||
start = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
break;
|
||||
case 'year':
|
||||
start = new Date(now.getFullYear(), 0, 1);
|
||||
end = new Date(now.getFullYear(), 11, 31);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (startDate) start = new Date(startDate as string);
|
||||
if (endDate) end = new Date(endDate as string);
|
||||
}
|
||||
|
||||
const summary = await storage.getFinancialSummary(req.user.id, start, end);
|
||||
|
||||
res.json({
|
||||
summary,
|
||||
period: period || 'custom',
|
||||
startDate: start?.toISOString(),
|
||||
endDate: end?.toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get financial summary error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch financial summary' });
|
||||
}
|
||||
},
|
||||
|
||||
async createRecord(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const validatedData = financialRecordCreateSchema.parse(req.body);
|
||||
|
||||
const record = await storage.createFinancialRecord({
|
||||
userId: req.user.id,
|
||||
type: validatedData.type,
|
||||
amount: validatedData.amount.toString(),
|
||||
category: validatedData.category,
|
||||
description: validatedData.description,
|
||||
date: new Date(validatedData.date),
|
||||
createdViaVoice: req.body.createdViaVoice || false,
|
||||
voiceTranscription: req.body.voiceTranscription,
|
||||
metadata: req.body.metadata
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Financial record created successfully',
|
||||
record
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Create financial record error:', error);
|
||||
if (error instanceof Error && error.name === 'ZodError') {
|
||||
return res.status(400).json({ error: 'Invalid input data', details: error.message });
|
||||
}
|
||||
res.status(500).json({ error: 'Failed to create financial record' });
|
||||
}
|
||||
},
|
||||
|
||||
async updateRecord(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const recordId = parseInt(req.params.id);
|
||||
if (isNaN(recordId)) {
|
||||
return res.status(400).json({ error: 'Invalid record ID' });
|
||||
}
|
||||
|
||||
const allowedUpdates = ['type', 'amount', 'category', 'description', 'date', 'metadata'];
|
||||
const updates: any = {};
|
||||
|
||||
for (const field of allowedUpdates) {
|
||||
if (req.body[field] !== undefined) {
|
||||
if (field === 'date') {
|
||||
updates[field] = new Date(req.body[field]);
|
||||
} else if (field === 'amount') {
|
||||
updates[field] = req.body[field].toString();
|
||||
} else {
|
||||
updates[field] = req.body[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length === 0) {
|
||||
return res.status(400).json({ error: 'No valid fields to update' });
|
||||
}
|
||||
|
||||
const record = await storage.updateFinancialRecord(recordId, req.user.id, updates);
|
||||
if (!record) {
|
||||
return res.status(404).json({ error: 'Financial record not found' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: 'Financial record updated successfully',
|
||||
record
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Update financial record error:', error);
|
||||
res.status(500).json({ error: 'Failed to update financial record' });
|
||||
}
|
||||
},
|
||||
|
||||
async deleteRecord(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const recordId = parseInt(req.params.id);
|
||||
if (isNaN(recordId)) {
|
||||
return res.status(400).json({ error: 'Invalid record ID' });
|
||||
}
|
||||
|
||||
const deleted = await storage.deleteFinancialRecord(recordId, req.user.id);
|
||||
if (!deleted) {
|
||||
return res.status(404).json({ error: 'Financial record not found' });
|
||||
}
|
||||
|
||||
res.json({ message: 'Financial record deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Delete financial record error:', error);
|
||||
res.status(500).json({ error: 'Failed to delete financial record' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,150 @@
|
||||
import { Response } from 'express';
|
||||
import { storage } from '../storage';
|
||||
import { taskCreateSchema } from '@shared/schema';
|
||||
import { AuthenticatedRequest } from '../middleware/auth';
|
||||
|
||||
export const taskController = {
|
||||
async getTasks(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { status } = req.query;
|
||||
const tasks = await storage.getTasks(req.user.id, status as string);
|
||||
|
||||
res.json({ tasks });
|
||||
} catch (error) {
|
||||
console.error('Get tasks error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch tasks' });
|
||||
}
|
||||
},
|
||||
|
||||
async getTask(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const taskId = parseInt(req.params.id);
|
||||
if (isNaN(taskId)) {
|
||||
return res.status(400).json({ error: 'Invalid task ID' });
|
||||
}
|
||||
|
||||
const task = await storage.getTask(taskId, req.user.id);
|
||||
if (!task) {
|
||||
return res.status(404).json({ error: 'Task not found' });
|
||||
}
|
||||
|
||||
res.json({ task });
|
||||
} catch (error) {
|
||||
console.error('Get task error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch task' });
|
||||
}
|
||||
},
|
||||
|
||||
async createTask(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const validatedData = taskCreateSchema.parse(req.body);
|
||||
|
||||
const task = await storage.createTask({
|
||||
userId: req.user.id,
|
||||
title: validatedData.title,
|
||||
description: validatedData.description,
|
||||
priority: validatedData.priority,
|
||||
dueDate: validatedData.dueDate ? new Date(validatedData.dueDate) : undefined,
|
||||
status: 'pending',
|
||||
createdViaVoice: req.body.createdViaVoice || false,
|
||||
voiceTranscription: req.body.voiceTranscription
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Task created successfully',
|
||||
task
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Create task error:', error);
|
||||
if (error instanceof Error && error.name === 'ZodError') {
|
||||
return res.status(400).json({ error: 'Invalid input data', details: error.message });
|
||||
}
|
||||
res.status(500).json({ error: 'Failed to create task' });
|
||||
}
|
||||
},
|
||||
|
||||
async updateTask(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const taskId = parseInt(req.params.id);
|
||||
if (isNaN(taskId)) {
|
||||
return res.status(400).json({ error: 'Invalid task ID' });
|
||||
}
|
||||
|
||||
const allowedUpdates = ['title', 'description', 'priority', 'status', 'dueDate'];
|
||||
const updates: any = {};
|
||||
|
||||
for (const field of allowedUpdates) {
|
||||
if (req.body[field] !== undefined) {
|
||||
if (field === 'dueDate' && req.body[field]) {
|
||||
updates[field] = new Date(req.body[field]);
|
||||
} else {
|
||||
updates[field] = req.body[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If marking as completed, set completedAt timestamp
|
||||
if (updates.status === 'completed') {
|
||||
updates.completedAt = new Date();
|
||||
} else if (updates.status && updates.status !== 'completed') {
|
||||
updates.completedAt = null;
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length === 0) {
|
||||
return res.status(400).json({ error: 'No valid fields to update' });
|
||||
}
|
||||
|
||||
const task = await storage.updateTask(taskId, req.user.id, updates);
|
||||
if (!task) {
|
||||
return res.status(404).json({ error: 'Task not found' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: 'Task updated successfully',
|
||||
task
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Update task error:', error);
|
||||
res.status(500).json({ error: 'Failed to update task' });
|
||||
}
|
||||
},
|
||||
|
||||
async deleteTask(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const taskId = parseInt(req.params.id);
|
||||
if (isNaN(taskId)) {
|
||||
return res.status(400).json({ error: 'Invalid task ID' });
|
||||
}
|
||||
|
||||
const deleted = await storage.deleteTask(taskId, req.user.id);
|
||||
if (!deleted) {
|
||||
return res.status(404).json({ error: 'Task not found' });
|
||||
}
|
||||
|
||||
res.json({ message: 'Task deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Delete task error:', error);
|
||||
res.status(500).json({ error: 'Failed to delete task' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,203 @@
|
||||
import { Response } from 'express';
|
||||
import multer from 'multer';
|
||||
import { voiceService } from '../services/voiceService';
|
||||
import { storage } from '../storage';
|
||||
import { AuthenticatedRequest } from '../middleware/auth';
|
||||
|
||||
// Configure multer for audio file uploads
|
||||
const upload = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024 // 10MB limit
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
if (file.mimetype.startsWith('audio/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Only audio files are allowed'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const voiceController = {
|
||||
async initialize(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const success = await voiceService.initialize();
|
||||
|
||||
if (success) {
|
||||
res.json({
|
||||
message: 'Voice models initialized successfully',
|
||||
status: 'ready'
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({ error: 'Failed to initialize voice models' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Voice initialization error:', error);
|
||||
res.status(500).json({ error: 'Voice initialization failed' });
|
||||
}
|
||||
},
|
||||
|
||||
async processCommand(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { audioData } = req.body;
|
||||
|
||||
if (!audioData) {
|
||||
return res.status(400).json({ error: 'Audio data is required' });
|
||||
}
|
||||
|
||||
// Convert base64 audio to buffer
|
||||
const audioBuffer = Buffer.from(audioData, 'base64');
|
||||
|
||||
// Process audio to text
|
||||
const result = await voiceService.processAudioToText(audioBuffer, req.user.id);
|
||||
|
||||
// Execute the command based on intent
|
||||
let actionResult = null;
|
||||
if (result.intent && result.entities) {
|
||||
actionResult = await this.executeVoiceCommand(req.user.id, result.intent, result.entities);
|
||||
}
|
||||
|
||||
res.json({
|
||||
transcription: result.transcription,
|
||||
intent: result.intent,
|
||||
confidence: result.confidence,
|
||||
processingTime: result.processingTime,
|
||||
actionResult
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Voice command processing error:', error);
|
||||
res.status(500).json({ error: 'Failed to process voice command' });
|
||||
}
|
||||
},
|
||||
|
||||
async speak(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { text, voice } = req.body;
|
||||
|
||||
if (!text) {
|
||||
return res.status(400).json({ error: 'Text is required' });
|
||||
}
|
||||
|
||||
const result = await voiceService.textToSpeech(text, voice);
|
||||
|
||||
// Set appropriate headers for audio response
|
||||
res.set({
|
||||
'Content-Type': 'audio/wav',
|
||||
'Content-Length': result.audioBuffer.length
|
||||
});
|
||||
|
||||
res.send(result.audioBuffer);
|
||||
} catch (error) {
|
||||
console.error('Text-to-speech error:', error);
|
||||
res.status(500).json({ error: 'Failed to generate speech' });
|
||||
}
|
||||
},
|
||||
|
||||
async getCommands(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
const { limit } = req.query;
|
||||
const commands = await storage.getVoiceCommands(req.user.id, limit ? parseInt(limit as string) : undefined);
|
||||
|
||||
res.json({ commands });
|
||||
} catch (error) {
|
||||
console.error('Get voice commands error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch voice commands' });
|
||||
}
|
||||
},
|
||||
|
||||
async getStatus(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const status = await voiceService.getModelStatus();
|
||||
|
||||
res.json({
|
||||
...status,
|
||||
uptime: process.uptime(),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get voice status error:', error);
|
||||
res.status(500).json({ error: 'Failed to get voice status' });
|
||||
}
|
||||
},
|
||||
|
||||
async executeVoiceCommand(userId: number, intent: string, entities: Record<string, any>) {
|
||||
try {
|
||||
switch (intent) {
|
||||
case 'task_create':
|
||||
if (entities.title) {
|
||||
const task = await storage.createTask({
|
||||
userId,
|
||||
title: entities.title,
|
||||
description: entities.description,
|
||||
priority: entities.priority || 'medium',
|
||||
dueDate: entities.dueDate ? new Date(entities.dueDate) : undefined,
|
||||
status: 'pending',
|
||||
createdViaVoice: true,
|
||||
voiceTranscription: entities.originalTranscription
|
||||
});
|
||||
return { type: 'task_created', task };
|
||||
}
|
||||
break;
|
||||
|
||||
case 'expense_add':
|
||||
if (entities.amount && entities.category) {
|
||||
const record = await storage.createFinancialRecord({
|
||||
userId,
|
||||
type: 'expense',
|
||||
amount: entities.amount.toString(),
|
||||
category: entities.category,
|
||||
description: entities.description || 'Voice expense entry',
|
||||
date: new Date(),
|
||||
createdViaVoice: true,
|
||||
voiceTranscription: entities.originalTranscription
|
||||
});
|
||||
return { type: 'expense_added', record };
|
||||
}
|
||||
break;
|
||||
|
||||
case 'income_add':
|
||||
if (entities.amount) {
|
||||
const record = await storage.createFinancialRecord({
|
||||
userId,
|
||||
type: 'income',
|
||||
amount: entities.amount.toString(),
|
||||
category: entities.category || 'income',
|
||||
description: entities.description || 'Voice income entry',
|
||||
date: new Date(),
|
||||
createdViaVoice: true,
|
||||
voiceTranscription: entities.originalTranscription
|
||||
});
|
||||
return { type: 'income_added', record };
|
||||
}
|
||||
break;
|
||||
|
||||
case 'task_list':
|
||||
const tasks = await storage.getTasks(userId, 'pending');
|
||||
return { type: 'tasks_retrieved', tasks: tasks.slice(0, 5) }; // Limit for voice response
|
||||
|
||||
case 'financial_summary':
|
||||
const summary = await storage.getFinancialSummary(userId);
|
||||
return { type: 'financial_summary', summary };
|
||||
|
||||
default:
|
||||
return { type: 'unknown_intent', message: 'I didn\'t understand that command' };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Execute voice command error:', error);
|
||||
return { type: 'error', message: 'Failed to execute command' };
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user