feat: Implement structured message parsing and display in MessageViewer component

- Added MessageViewer component to display parsed messages in a chat-like format.
- Introduced new Message table in the database to store individual messages with timestamps, roles, and content.
- Updated Session model to include a relation to parsed messages.
- Created transcript parsing logic to convert raw transcripts into structured messages.
- Enhanced processing scheduler to handle sessions with parsed messages.
- Updated API endpoints to return parsed messages alongside session details.
- Added manual trigger commands for session refresh, transcript parsing, and processing.
- Improved user experience with color-coded message roles and timestamps in the UI.
- Documented the new scheduler workflow and transcript parsing implementation.
This commit is contained in:
Max Kowalski
2025-06-25 17:45:08 +02:00
parent 3196dabdf2
commit a9e4145001
20 changed files with 1043 additions and 90 deletions

View File

@ -181,46 +181,59 @@ function validateOpenAIResponse(data) {
/**
* Process unprocessed sessions
*/
async function processUnprocessedSessions() {
export async function processUnprocessedSessions() {
process.stdout.write("[ProcessingScheduler] Starting to process unprocessed sessions...\n");
// Find sessions that have transcript content but haven't been processed
// Find sessions that have messages but haven't been processed
const sessionsToProcess = await prisma.session.findMany({
where: {
AND: [
{ transcriptContent: { not: null } },
{ transcriptContent: { not: "" } },
{ processed: { not: true } }, // Either false or null
],
},
select: {
id: true,
transcriptContent: true,
include: {
messages: {
orderBy: { order: 'asc' }
}
},
take: 10, // Process in batches to avoid overloading the system
});
if (sessionsToProcess.length === 0) {
// Filter to only sessions that have messages
const sessionsWithMessages = sessionsToProcess.filter(session => session.messages.length > 0);
if (sessionsWithMessages.length === 0) {
process.stdout.write("[ProcessingScheduler] No sessions found requiring processing.\n");
return;
}
process.stdout.write(`[ProcessingScheduler] Found ${sessionsToProcess.length} sessions to process.\n`);
process.stdout.write(`[ProcessingScheduler] Found ${sessionsWithMessages.length} sessions to process.\n`);
let successCount = 0;
let errorCount = 0;
for (const session of sessionsToProcess) {
if (!session.transcriptContent) {
// Should not happen due to query, but good for type safety
process.stderr.write(`[ProcessingScheduler] Session ${session.id} has no transcript content, skipping.\n`);
for (const session of sessionsWithMessages) {
if (session.messages.length === 0) {
process.stderr.write(`[ProcessingScheduler] Session ${session.id} has no messages, skipping.\n`);
continue;
}
process.stdout.write(`[ProcessingScheduler] Processing transcript for session ${session.id}...\n`);
process.stdout.write(`[ProcessingScheduler] Processing messages for session ${session.id}...\n`);
try {
// Convert messages back to transcript format for OpenAI processing
const transcript = session.messages.map(msg =>
`[${new Date(msg.timestamp).toLocaleString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).replace(',', '')}] ${msg.role}: ${msg.content}`
).join('\n');
const processedData = await processTranscriptWithOpenAI(
session.id,
session.transcriptContent
transcript
);
// Map sentiment string to float value for compatibility with existing data