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:
ghaddaditw
2025-05-30 17:08:53 +00:00
parent b47089ae5d
commit cced901e9a
100 changed files with 20488 additions and 0 deletions
+160
View File
@@ -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' });
}
}
};
+185
View File
@@ -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' });
}
}
};
+181
View File
@@ -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' });
}
}
};
+150
View File
@@ -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' });
}
}
};
+203
View File
@@ -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' };
}
}
};