mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 08:52:10 +01:00
style: remove unnecessary whitespace in multiple files for cleaner code
This commit is contained in:
@ -44,7 +44,7 @@ export async function fetchAndParseCsv(
|
|||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
headers: authHeader ? { Authorization: authHeader } : {},
|
headers: authHeader ? { Authorization: authHeader } : {},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(`Failed to fetch CSV: ${res.status} ${res.statusText}`);
|
throw new Error(`Failed to fetch CSV: ${res.status} ${res.statusText}`);
|
||||||
}
|
}
|
||||||
|
|||||||
12
lib/env.ts
12
lib/env.ts
@ -8,22 +8,22 @@ import { dirname, join } from "path";
|
|||||||
*/
|
*/
|
||||||
function parseEnvValue(value: string | undefined): string {
|
function parseEnvValue(value: string | undefined): string {
|
||||||
if (!value) return '';
|
if (!value) return '';
|
||||||
|
|
||||||
// Trim whitespace
|
// Trim whitespace
|
||||||
let cleaned = value.trim();
|
let cleaned = value.trim();
|
||||||
|
|
||||||
// Remove inline comments (everything after #)
|
// Remove inline comments (everything after #)
|
||||||
const commentIndex = cleaned.indexOf('#');
|
const commentIndex = cleaned.indexOf('#');
|
||||||
if (commentIndex !== -1) {
|
if (commentIndex !== -1) {
|
||||||
cleaned = cleaned.substring(0, commentIndex).trim();
|
cleaned = cleaned.substring(0, commentIndex).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove surrounding quotes (both single and double)
|
// Remove surrounding quotes (both single and double)
|
||||||
if ((cleaned.startsWith('"') && cleaned.endsWith('"')) ||
|
if ((cleaned.startsWith('"') && cleaned.endsWith('"')) ||
|
||||||
(cleaned.startsWith("'") && cleaned.endsWith("'"))) {
|
(cleaned.startsWith("'") && cleaned.endsWith("'"))) {
|
||||||
cleaned = cleaned.slice(1, -1);
|
cleaned = cleaned.slice(1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cleaned;
|
return cleaned;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ function parseEnvValue(value: string | undefined): string {
|
|||||||
function parseIntWithDefault(value: string | undefined, defaultValue: number): number {
|
function parseIntWithDefault(value: string | undefined, defaultValue: number): number {
|
||||||
const cleaned = parseEnvValue(value);
|
const cleaned = parseEnvValue(value);
|
||||||
if (!cleaned) return defaultValue;
|
if (!cleaned) return defaultValue;
|
||||||
|
|
||||||
const parsed = parseInt(cleaned, 10);
|
const parsed = parseInt(cleaned, 10);
|
||||||
return isNaN(parsed) ? defaultValue : parsed;
|
return isNaN(parsed) ? defaultValue : parsed;
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ export function logEnvConfig(): void {
|
|||||||
console.log(` NEXTAUTH_URL: ${env.NEXTAUTH_URL}`);
|
console.log(` NEXTAUTH_URL: ${env.NEXTAUTH_URL}`);
|
||||||
console.log(` SCHEDULER_ENABLED: ${env.SCHEDULER_ENABLED}`);
|
console.log(` SCHEDULER_ENABLED: ${env.SCHEDULER_ENABLED}`);
|
||||||
console.log(` PORT: ${env.PORT}`);
|
console.log(` PORT: ${env.PORT}`);
|
||||||
|
|
||||||
if (env.SCHEDULER_ENABLED) {
|
if (env.SCHEDULER_ENABLED) {
|
||||||
console.log(' Scheduler intervals:');
|
console.log(' Scheduler intervals:');
|
||||||
console.log(` CSV Import: ${env.CSV_IMPORT_INTERVAL}`);
|
console.log(` CSV Import: ${env.CSV_IMPORT_INTERVAL}`);
|
||||||
|
|||||||
@ -17,13 +17,13 @@ function parseEuropeanDate(dateStr: string): Date {
|
|||||||
|
|
||||||
// Handle format: "DD.MM.YYYY HH:mm:ss"
|
// Handle format: "DD.MM.YYYY HH:mm:ss"
|
||||||
const [datePart, timePart] = dateStr.trim().split(' ');
|
const [datePart, timePart] = dateStr.trim().split(' ');
|
||||||
|
|
||||||
if (!datePart || !timePart) {
|
if (!datePart || !timePart) {
|
||||||
throw new Error(`Invalid date format: ${dateStr}. Expected format: DD.MM.YYYY HH:mm:ss`);
|
throw new Error(`Invalid date format: ${dateStr}. Expected format: DD.MM.YYYY HH:mm:ss`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [day, month, year] = datePart.split('.');
|
const [day, month, year] = datePart.split('.');
|
||||||
|
|
||||||
if (!day || !month || !year) {
|
if (!day || !month || !year) {
|
||||||
throw new Error(`Invalid date part: ${datePart}. Expected format: DD.MM.YYYY`);
|
throw new Error(`Invalid date part: ${datePart}. Expected format: DD.MM.YYYY`);
|
||||||
}
|
}
|
||||||
@ -31,11 +31,11 @@ function parseEuropeanDate(dateStr: string): Date {
|
|||||||
// Convert to ISO format: YYYY-MM-DD HH:mm:ss
|
// Convert to ISO format: YYYY-MM-DD HH:mm:ss
|
||||||
const isoDateStr = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')} ${timePart}`;
|
const isoDateStr = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')} ${timePart}`;
|
||||||
const date = new Date(isoDateStr);
|
const date = new Date(isoDateStr);
|
||||||
|
|
||||||
if (isNaN(date.getTime())) {
|
if (isNaN(date.getTime())) {
|
||||||
throw new Error(`Failed to parse date: ${dateStr} -> ${isoDateStr}`);
|
throw new Error(`Failed to parse date: ${dateStr} -> ${isoDateStr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ function parseEuropeanDate(dateStr: string): Date {
|
|||||||
*/
|
*/
|
||||||
function parseFallbackSentiment(sentimentRaw: string | null): SentimentCategory | null {
|
function parseFallbackSentiment(sentimentRaw: string | null): SentimentCategory | null {
|
||||||
if (!sentimentRaw) return null;
|
if (!sentimentRaw) return null;
|
||||||
|
|
||||||
const sentimentStr = sentimentRaw.toLowerCase();
|
const sentimentStr = sentimentRaw.toLowerCase();
|
||||||
if (sentimentStr.includes('positive')) {
|
if (sentimentStr.includes('positive')) {
|
||||||
return SentimentCategory.POSITIVE;
|
return SentimentCategory.POSITIVE;
|
||||||
@ -83,7 +83,7 @@ async function parseTranscriptIntoMessages(sessionId: string, transcriptContent:
|
|||||||
// Try to parse different formats:
|
// Try to parse different formats:
|
||||||
// Format 1: "User: message" or "Assistant: message"
|
// Format 1: "User: message" or "Assistant: message"
|
||||||
// Format 2: "[timestamp] User: message" or "[timestamp] Assistant: message"
|
// Format 2: "[timestamp] User: message" or "[timestamp] Assistant: message"
|
||||||
|
|
||||||
let role = 'unknown';
|
let role = 'unknown';
|
||||||
let content = trimmedLine;
|
let content = trimmedLine;
|
||||||
let timestamp: Date | null = null;
|
let timestamp: Date | null = null;
|
||||||
@ -136,7 +136,7 @@ async function parseTranscriptIntoMessages(sessionId: string, transcriptContent:
|
|||||||
*/
|
*/
|
||||||
async function processSingleImport(importRecord: any): Promise<{ success: boolean; error?: string }> {
|
async function processSingleImport(importRecord: any): Promise<{ success: boolean; error?: string }> {
|
||||||
let sessionId: string | null = null;
|
let sessionId: string | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Parse dates using European format parser
|
// Parse dates using European format parser
|
||||||
const startTime = parseEuropeanDate(importRecord.startTimeRaw);
|
const startTime = parseEuropeanDate(importRecord.startTimeRaw);
|
||||||
@ -183,12 +183,12 @@ async function processSingleImport(importRecord: any): Promise<{ success: boolea
|
|||||||
|
|
||||||
// Handle transcript fetching
|
// Handle transcript fetching
|
||||||
let transcriptContent = importRecord.rawTranscriptContent;
|
let transcriptContent = importRecord.rawTranscriptContent;
|
||||||
|
|
||||||
if (!transcriptContent && importRecord.fullTranscriptUrl && isValidTranscriptUrl(importRecord.fullTranscriptUrl)) {
|
if (!transcriptContent && importRecord.fullTranscriptUrl && isValidTranscriptUrl(importRecord.fullTranscriptUrl)) {
|
||||||
await ProcessingStatusManager.startStage(sessionId, ProcessingStage.TRANSCRIPT_FETCH);
|
await ProcessingStatusManager.startStage(sessionId, ProcessingStage.TRANSCRIPT_FETCH);
|
||||||
|
|
||||||
console.log(`[Import Processor] Fetching transcript for ${importRecord.externalSessionId}...`);
|
console.log(`[Import Processor] Fetching transcript for ${importRecord.externalSessionId}...`);
|
||||||
|
|
||||||
// Get company credentials for transcript fetching
|
// Get company credentials for transcript fetching
|
||||||
const company = await prisma.company.findUnique({
|
const company = await prisma.company.findUnique({
|
||||||
where: { id: importRecord.companyId },
|
where: { id: importRecord.companyId },
|
||||||
@ -204,7 +204,7 @@ async function processSingleImport(importRecord: any): Promise<{ success: boolea
|
|||||||
if (transcriptResult.success) {
|
if (transcriptResult.success) {
|
||||||
transcriptContent = transcriptResult.content;
|
transcriptContent = transcriptResult.content;
|
||||||
console.log(`[Import Processor] ✓ Fetched transcript for ${importRecord.externalSessionId} (${transcriptContent?.length} chars)`);
|
console.log(`[Import Processor] ✓ Fetched transcript for ${importRecord.externalSessionId} (${transcriptContent?.length} chars)`);
|
||||||
|
|
||||||
// Update the import record with the fetched content
|
// Update the import record with the fetched content
|
||||||
await prisma.sessionImport.update({
|
await prisma.sessionImport.update({
|
||||||
where: { id: importRecord.id },
|
where: { id: importRecord.id },
|
||||||
@ -232,7 +232,7 @@ async function processSingleImport(importRecord: any): Promise<{ success: boolea
|
|||||||
|
|
||||||
// Handle session creation (parse messages)
|
// Handle session creation (parse messages)
|
||||||
await ProcessingStatusManager.startStage(sessionId, ProcessingStage.SESSION_CREATION);
|
await ProcessingStatusManager.startStage(sessionId, ProcessingStage.SESSION_CREATION);
|
||||||
|
|
||||||
if (transcriptContent) {
|
if (transcriptContent) {
|
||||||
await parseTranscriptIntoMessages(sessionId, transcriptContent);
|
await parseTranscriptIntoMessages(sessionId, transcriptContent);
|
||||||
}
|
}
|
||||||
@ -245,7 +245,7 @@ async function processSingleImport(importRecord: any): Promise<{ success: boolea
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
// Mark the current stage as failed if we have a sessionId
|
// Mark the current stage as failed if we have a sessionId
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
// Determine which stage failed based on the error
|
// Determine which stage failed based on the error
|
||||||
@ -306,7 +306,7 @@ export async function processQueuedImports(batchSize: number = 50): Promise<void
|
|||||||
// Process each import in this batch
|
// Process each import in this batch
|
||||||
for (const importRecord of unprocessedImports) {
|
for (const importRecord of unprocessedImports) {
|
||||||
const result = await processSingleImport(importRecord);
|
const result = await processSingleImport(importRecord);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
batchSuccessCount++;
|
batchSuccessCount++;
|
||||||
totalSuccessCount++;
|
totalSuccessCount++;
|
||||||
@ -334,7 +334,7 @@ export async function processQueuedImports(batchSize: number = 50): Promise<void
|
|||||||
*/
|
*/
|
||||||
export function startImportProcessingScheduler(): void {
|
export function startImportProcessingScheduler(): void {
|
||||||
const config = getSchedulerConfig();
|
const config = getSchedulerConfig();
|
||||||
|
|
||||||
if (!config.enabled) {
|
if (!config.enabled) {
|
||||||
console.log('[Import Processing Scheduler] Disabled via configuration');
|
console.log('[Import Processing Scheduler] Disabled via configuration');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -345,8 +345,8 @@ export function sessionMetrics(
|
|||||||
let sentimentPositiveCount = 0;
|
let sentimentPositiveCount = 0;
|
||||||
let sentimentNeutralCount = 0;
|
let sentimentNeutralCount = 0;
|
||||||
let sentimentNegativeCount = 0;
|
let sentimentNegativeCount = 0;
|
||||||
let totalTokens = 0;
|
const totalTokens = 0;
|
||||||
let totalTokensEur = 0;
|
const totalTokensEur = 0;
|
||||||
const wordCounts: { [key: string]: number } = {};
|
const wordCounts: { [key: string]: number } = {};
|
||||||
let alerts = 0;
|
let alerts = 0;
|
||||||
|
|
||||||
|
|||||||
@ -91,23 +91,23 @@ async function recordAIProcessingRequest(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const usage = openaiResponse.usage;
|
const usage = openaiResponse.usage;
|
||||||
const model = openaiResponse.model;
|
const model = openaiResponse.model;
|
||||||
|
|
||||||
// Get current pricing from database
|
// Get current pricing from database
|
||||||
const pricing = await getCurrentModelPricing(model);
|
const pricing = await getCurrentModelPricing(model);
|
||||||
|
|
||||||
// Fallback pricing if not found in database
|
// Fallback pricing if not found in database
|
||||||
const fallbackPricing = {
|
const fallbackPricing = {
|
||||||
promptTokenCost: 0.00001, // $10.00 per 1M tokens (gpt-4-turbo rate)
|
promptTokenCost: 0.00001, // $10.00 per 1M tokens (gpt-4-turbo rate)
|
||||||
completionTokenCost: 0.00003, // $30.00 per 1M tokens
|
completionTokenCost: 0.00003, // $30.00 per 1M tokens
|
||||||
};
|
};
|
||||||
|
|
||||||
const finalPricing = pricing || fallbackPricing;
|
const finalPricing = pricing || fallbackPricing;
|
||||||
|
|
||||||
const promptCost = usage.prompt_tokens * finalPricing.promptTokenCost;
|
const promptCost = usage.prompt_tokens * finalPricing.promptTokenCost;
|
||||||
const completionCost = usage.completion_tokens * finalPricing.completionTokenCost;
|
const completionCost = usage.completion_tokens * finalPricing.completionTokenCost;
|
||||||
const totalCostUsd = promptCost + completionCost;
|
const totalCostUsd = promptCost + completionCost;
|
||||||
const totalCostEur = totalCostUsd * USD_TO_EUR_RATE;
|
const totalCostEur = totalCostUsd * USD_TO_EUR_RATE;
|
||||||
|
|
||||||
await prisma.aIProcessingRequest.create({
|
await prisma.aIProcessingRequest.create({
|
||||||
data: {
|
data: {
|
||||||
sessionId,
|
sessionId,
|
||||||
@ -115,11 +115,11 @@ async function recordAIProcessingRequest(
|
|||||||
model: openaiResponse.model,
|
model: openaiResponse.model,
|
||||||
serviceTier: openaiResponse.service_tier,
|
serviceTier: openaiResponse.service_tier,
|
||||||
systemFingerprint: openaiResponse.system_fingerprint,
|
systemFingerprint: openaiResponse.system_fingerprint,
|
||||||
|
|
||||||
promptTokens: usage.prompt_tokens,
|
promptTokens: usage.prompt_tokens,
|
||||||
completionTokens: usage.completion_tokens,
|
completionTokens: usage.completion_tokens,
|
||||||
totalTokens: usage.total_tokens,
|
totalTokens: usage.total_tokens,
|
||||||
|
|
||||||
// Detailed breakdown
|
// Detailed breakdown
|
||||||
cachedTokens: usage.prompt_tokens_details?.cached_tokens || null,
|
cachedTokens: usage.prompt_tokens_details?.cached_tokens || null,
|
||||||
audioTokensPrompt: usage.prompt_tokens_details?.audio_tokens || null,
|
audioTokensPrompt: usage.prompt_tokens_details?.audio_tokens || null,
|
||||||
@ -127,11 +127,11 @@ async function recordAIProcessingRequest(
|
|||||||
audioTokensCompletion: usage.completion_tokens_details?.audio_tokens || null,
|
audioTokensCompletion: usage.completion_tokens_details?.audio_tokens || null,
|
||||||
acceptedPredictionTokens: usage.completion_tokens_details?.accepted_prediction_tokens || null,
|
acceptedPredictionTokens: usage.completion_tokens_details?.accepted_prediction_tokens || null,
|
||||||
rejectedPredictionTokens: usage.completion_tokens_details?.rejected_prediction_tokens || null,
|
rejectedPredictionTokens: usage.completion_tokens_details?.rejected_prediction_tokens || null,
|
||||||
|
|
||||||
promptTokenCost: finalPricing.promptTokenCost,
|
promptTokenCost: finalPricing.promptTokenCost,
|
||||||
completionTokenCost: finalPricing.completionTokenCost,
|
completionTokenCost: finalPricing.completionTokenCost,
|
||||||
totalCostEur,
|
totalCostEur,
|
||||||
|
|
||||||
processingType,
|
processingType,
|
||||||
success: true,
|
success: true,
|
||||||
completedAt: new Date(),
|
completedAt: new Date(),
|
||||||
@ -178,14 +178,14 @@ async function processQuestions(sessionId: string, questions: string[]): Promise
|
|||||||
for (let index = 0; index < questions.length; index++) {
|
for (let index = 0; index < questions.length; index++) {
|
||||||
const questionText = questions[index];
|
const questionText = questions[index];
|
||||||
if (!questionText.trim()) continue; // Skip empty questions
|
if (!questionText.trim()) continue; // Skip empty questions
|
||||||
|
|
||||||
// Find or create question
|
// Find or create question
|
||||||
const question = await prisma.question.upsert({
|
const question = await prisma.question.upsert({
|
||||||
where: { content: questionText.trim() },
|
where: { content: questionText.trim() },
|
||||||
create: { content: questionText.trim() },
|
create: { content: questionText.trim() },
|
||||||
update: {}
|
update: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Link to session
|
// Link to session
|
||||||
await prisma.sessionQuestion.create({
|
await prisma.sessionQuestion.create({
|
||||||
data: {
|
data: {
|
||||||
@ -202,7 +202,7 @@ async function processQuestions(sessionId: string, questions: string[]): Promise
|
|||||||
*/
|
*/
|
||||||
async function calculateMessagesSent(sessionId: string): Promise<number> {
|
async function calculateMessagesSent(sessionId: string): Promise<number> {
|
||||||
const userMessageCount = await prisma.message.count({
|
const userMessageCount = await prisma.message.count({
|
||||||
where: {
|
where: {
|
||||||
sessionId,
|
sessionId,
|
||||||
role: { in: ['user', 'User'] } // Handle both cases
|
role: { in: ['user', 'User'] } // Handle both cases
|
||||||
}
|
}
|
||||||
@ -218,7 +218,7 @@ async function calculateEndTime(sessionId: string, fallbackEndTime: Date): Promi
|
|||||||
where: { sessionId },
|
where: { sessionId },
|
||||||
orderBy: { timestamp: 'desc' }
|
orderBy: { timestamp: 'desc' }
|
||||||
});
|
});
|
||||||
|
|
||||||
return latestMessage?.timestamp || fallbackEndTime;
|
return latestMessage?.timestamp || fallbackEndTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,10 +291,10 @@ async function processTranscriptWithOpenAI(sessionId: string, transcript: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openaiResponse: any = await response.json();
|
const openaiResponse: any = await response.json();
|
||||||
|
|
||||||
// Record the AI processing request for cost tracking
|
// Record the AI processing request for cost tracking
|
||||||
await recordAIProcessingRequest(sessionId, openaiResponse, 'session_analysis');
|
await recordAIProcessingRequest(sessionId, openaiResponse, 'session_analysis');
|
||||||
|
|
||||||
const processedData = JSON.parse(openaiResponse.choices[0].message.content);
|
const processedData = JSON.parse(openaiResponse.choices[0].message.content);
|
||||||
|
|
||||||
// Validate the response against our expected schema
|
// Validate the response against our expected schema
|
||||||
@ -304,11 +304,11 @@ async function processTranscriptWithOpenAI(sessionId: string, transcript: string
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Record failed request
|
// Record failed request
|
||||||
await recordFailedAIProcessingRequest(
|
await recordFailedAIProcessingRequest(
|
||||||
sessionId,
|
sessionId,
|
||||||
'session_analysis',
|
'session_analysis',
|
||||||
error instanceof Error ? error.message : String(error)
|
error instanceof Error ? error.message : String(error)
|
||||||
);
|
);
|
||||||
|
|
||||||
process.stderr.write(`Error processing transcript with OpenAI: ${error}\n`);
|
process.stderr.write(`Error processing transcript with OpenAI: ${error}\n`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -319,7 +319,7 @@ async function processTranscriptWithOpenAI(sessionId: string, transcript: string
|
|||||||
*/
|
*/
|
||||||
function validateOpenAIResponse(data: any): void {
|
function validateOpenAIResponse(data: any): void {
|
||||||
const requiredFields = [
|
const requiredFields = [
|
||||||
"language", "sentiment", "escalated", "forwarded_hr",
|
"language", "sentiment", "escalated", "forwarded_hr",
|
||||||
"category", "questions", "summary", "session_id"
|
"category", "questions", "summary", "session_id"
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -406,7 +406,7 @@ async function processSingleSession(session: any): Promise<ProcessingResult> {
|
|||||||
|
|
||||||
// Calculate messagesSent from actual Message records
|
// Calculate messagesSent from actual Message records
|
||||||
const messagesSent = await calculateMessagesSent(session.id);
|
const messagesSent = await calculateMessagesSent(session.id);
|
||||||
|
|
||||||
// Calculate endTime from latest Message timestamp
|
// Calculate endTime from latest Message timestamp
|
||||||
const calculatedEndTime = await calculateEndTime(session.id, session.endTime);
|
const calculatedEndTime = await calculateEndTime(session.id, session.endTime);
|
||||||
|
|
||||||
@ -451,8 +451,8 @@ async function processSingleSession(session: any): Promise<ProcessingResult> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Mark AI analysis as failed
|
// Mark AI analysis as failed
|
||||||
await ProcessingStatusManager.failStage(
|
await ProcessingStatusManager.failStage(
|
||||||
session.id,
|
session.id,
|
||||||
ProcessingStage.AI_ANALYSIS,
|
ProcessingStage.AI_ANALYSIS,
|
||||||
error instanceof Error ? error.message : String(error)
|
error instanceof Error ? error.message : String(error)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -597,7 +597,7 @@ export async function getAIProcessingCosts(): Promise<{
|
|||||||
*/
|
*/
|
||||||
export function startProcessingScheduler(): void {
|
export function startProcessingScheduler(): void {
|
||||||
const config = getSchedulerConfig();
|
const config = getSchedulerConfig();
|
||||||
|
|
||||||
if (!config.enabled) {
|
if (!config.enabled) {
|
||||||
console.log('[Processing Scheduler] Disabled via configuration');
|
console.log('[Processing Scheduler] Disabled via configuration');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ const prisma = new PrismaClient();
|
|||||||
* Centralized processing status management
|
* Centralized processing status management
|
||||||
*/
|
*/
|
||||||
export class ProcessingStatusManager {
|
export class ProcessingStatusManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize processing status for a session with all stages set to PENDING
|
* Initialize processing status for a session with all stages set to PENDING
|
||||||
*/
|
*/
|
||||||
@ -34,8 +34,8 @@ export class ProcessingStatusManager {
|
|||||||
* Start a processing stage
|
* Start a processing stage
|
||||||
*/
|
*/
|
||||||
static async startStage(
|
static async startStage(
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
stage: ProcessingStage,
|
stage: ProcessingStage,
|
||||||
metadata?: any
|
metadata?: any
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await prisma.sessionProcessingStatus.upsert({
|
await prisma.sessionProcessingStatus.upsert({
|
||||||
@ -62,8 +62,8 @@ export class ProcessingStatusManager {
|
|||||||
* Complete a processing stage successfully
|
* Complete a processing stage successfully
|
||||||
*/
|
*/
|
||||||
static async completeStage(
|
static async completeStage(
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
stage: ProcessingStage,
|
stage: ProcessingStage,
|
||||||
metadata?: any
|
metadata?: any
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await prisma.sessionProcessingStatus.upsert({
|
await prisma.sessionProcessingStatus.upsert({
|
||||||
@ -91,8 +91,8 @@ export class ProcessingStatusManager {
|
|||||||
* Mark a processing stage as failed
|
* Mark a processing stage as failed
|
||||||
*/
|
*/
|
||||||
static async failStage(
|
static async failStage(
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
stage: ProcessingStage,
|
stage: ProcessingStage,
|
||||||
errorMessage: string,
|
errorMessage: string,
|
||||||
metadata?: any
|
metadata?: any
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@ -124,8 +124,8 @@ export class ProcessingStatusManager {
|
|||||||
* Skip a processing stage (e.g., no transcript URL available)
|
* Skip a processing stage (e.g., no transcript URL available)
|
||||||
*/
|
*/
|
||||||
static async skipStage(
|
static async skipStage(
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
stage: ProcessingStage,
|
stage: ProcessingStage,
|
||||||
reason: string
|
reason: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await prisma.sessionProcessingStatus.upsert({
|
await prisma.sessionProcessingStatus.upsert({
|
||||||
@ -198,7 +198,7 @@ export class ProcessingStatusManager {
|
|||||||
|
|
||||||
// Organize the data
|
// Organize the data
|
||||||
const pipeline: Record<string, Record<string, number>> = {};
|
const pipeline: Record<string, Record<string, number>> = {};
|
||||||
|
|
||||||
for (const { stage, status, _count } of statusCounts) {
|
for (const { stage, status, _count } of statusCounts) {
|
||||||
if (!pipeline[stage]) {
|
if (!pipeline[stage]) {
|
||||||
pipeline[stage] = {};
|
pipeline[stage] = {};
|
||||||
@ -219,7 +219,7 @@ export class ProcessingStatusManager {
|
|||||||
const where: any = {
|
const where: any = {
|
||||||
status: ProcessingStatus.FAILED,
|
status: ProcessingStatus.FAILED,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (stage) {
|
if (stage) {
|
||||||
where.stage = stage;
|
where.stage = stage;
|
||||||
}
|
}
|
||||||
@ -284,7 +284,7 @@ export class ProcessingStatusManager {
|
|||||||
|
|
||||||
// Check if all previous stages are completed
|
// Check if all previous stages are completed
|
||||||
const previousStages = stageOrder.slice(0, currentStageIndex);
|
const previousStages = stageOrder.slice(0, currentStageIndex);
|
||||||
|
|
||||||
for (const prevStage of previousStages) {
|
for (const prevStage of previousStages) {
|
||||||
const isCompleted = await this.hasCompletedStage(sessionId, prevStage);
|
const isCompleted = await this.hasCompletedStage(sessionId, prevStage);
|
||||||
if (!isCompleted) return false;
|
if (!isCompleted) return false;
|
||||||
|
|||||||
@ -6,14 +6,14 @@ import { getSchedulerConfig } from "./schedulerConfig";
|
|||||||
|
|
||||||
export function startCsvImportScheduler() {
|
export function startCsvImportScheduler() {
|
||||||
const config = getSchedulerConfig();
|
const config = getSchedulerConfig();
|
||||||
|
|
||||||
if (!config.enabled) {
|
if (!config.enabled) {
|
||||||
console.log('[CSV Import Scheduler] Disabled via configuration');
|
console.log('[CSV Import Scheduler] Disabled via configuration');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[CSV Import Scheduler] Starting with interval: ${config.csvImport.interval}`);
|
console.log(`[CSV Import Scheduler] Starting with interval: ${config.csvImport.interval}`);
|
||||||
|
|
||||||
cron.schedule(config.csvImport.interval, async () => {
|
cron.schedule(config.csvImport.interval, async () => {
|
||||||
const companies = await prisma.company.findMany();
|
const companies = await prisma.company.findMany();
|
||||||
for (const company of companies) {
|
for (const company of companies) {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export interface SchedulerConfig {
|
|||||||
*/
|
*/
|
||||||
export function getSchedulerConfig(): SchedulerConfig {
|
export function getSchedulerConfig(): SchedulerConfig {
|
||||||
const config = getEnvSchedulerConfig();
|
const config = getEnvSchedulerConfig();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabled: config.enabled,
|
enabled: config.enabled,
|
||||||
csvImport: {
|
csvImport: {
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export async function fetchTranscriptContent(
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
// Handle common network errors
|
// Handle common network errors
|
||||||
if (errorMessage.includes('ENOTFOUND')) {
|
if (errorMessage.includes('ENOTFOUND')) {
|
||||||
return {
|
return {
|
||||||
@ -84,14 +84,14 @@ export async function fetchTranscriptContent(
|
|||||||
error: 'Domain not found',
|
error: 'Domain not found',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMessage.includes('ECONNREFUSED')) {
|
if (errorMessage.includes('ECONNREFUSED')) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Connection refused',
|
error: 'Connection refused',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMessage.includes('timeout')) {
|
if (errorMessage.includes('timeout')) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@ -62,7 +62,7 @@ export function parseTranscriptToMessages(
|
|||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const trimmedLine = line.trim();
|
const trimmedLine = line.trim();
|
||||||
|
|
||||||
// Skip empty lines
|
// Skip empty lines
|
||||||
if (!trimmedLine) {
|
if (!trimmedLine) {
|
||||||
continue;
|
continue;
|
||||||
@ -70,10 +70,10 @@ export function parseTranscriptToMessages(
|
|||||||
|
|
||||||
// Check if line starts with a timestamp and role [DD.MM.YYYY HH:MM:SS] Role: content
|
// Check if line starts with a timestamp and role [DD.MM.YYYY HH:MM:SS] Role: content
|
||||||
const timestampRoleMatch = trimmedLine.match(/^\[(\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}:\d{2})\]\s+(User|Assistant|System|user|assistant|system):\s*(.*)$/i);
|
const timestampRoleMatch = trimmedLine.match(/^\[(\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}:\d{2})\]\s+(User|Assistant|System|user|assistant|system):\s*(.*)$/i);
|
||||||
|
|
||||||
// Check if line starts with just a role (User:, Assistant:, System:, etc.)
|
// Check if line starts with just a role (User:, Assistant:, System:, etc.)
|
||||||
const roleMatch = trimmedLine.match(/^(User|Assistant|System|user|assistant|system):\s*(.*)$/i);
|
const roleMatch = trimmedLine.match(/^(User|Assistant|System|user|assistant|system):\s*(.*)$/i);
|
||||||
|
|
||||||
if (timestampRoleMatch) {
|
if (timestampRoleMatch) {
|
||||||
// Save previous message if exists
|
// Save previous message if exists
|
||||||
if (currentMessage) {
|
if (currentMessage) {
|
||||||
@ -90,7 +90,7 @@ export function parseTranscriptToMessages(
|
|||||||
const timestamp = timestampRoleMatch[1];
|
const timestamp = timestampRoleMatch[1];
|
||||||
const role = timestampRoleMatch[2].charAt(0).toUpperCase() + timestampRoleMatch[2].slice(1).toLowerCase();
|
const role = timestampRoleMatch[2].charAt(0).toUpperCase() + timestampRoleMatch[2].slice(1).toLowerCase();
|
||||||
const content = timestampRoleMatch[3] || '';
|
const content = timestampRoleMatch[3] || '';
|
||||||
|
|
||||||
currentMessage = {
|
currentMessage = {
|
||||||
role,
|
role,
|
||||||
content,
|
content,
|
||||||
@ -111,7 +111,7 @@ export function parseTranscriptToMessages(
|
|||||||
// Start new message without timestamp
|
// Start new message without timestamp
|
||||||
const role = roleMatch[1].charAt(0).toUpperCase() + roleMatch[1].slice(1).toLowerCase();
|
const role = roleMatch[1].charAt(0).toUpperCase() + roleMatch[1].slice(1).toLowerCase();
|
||||||
const content = roleMatch[2] || '';
|
const content = roleMatch[2] || '';
|
||||||
|
|
||||||
currentMessage = {
|
currentMessage = {
|
||||||
role,
|
role,
|
||||||
content
|
content
|
||||||
@ -143,7 +143,7 @@ export function parseTranscriptToMessages(
|
|||||||
|
|
||||||
// Calculate timestamps - use parsed timestamps if available, otherwise distribute across session duration
|
// Calculate timestamps - use parsed timestamps if available, otherwise distribute across session duration
|
||||||
const hasTimestamps = messages.some(msg => (msg as any).timestamp);
|
const hasTimestamps = messages.some(msg => (msg as any).timestamp);
|
||||||
|
|
||||||
if (hasTimestamps) {
|
if (hasTimestamps) {
|
||||||
// Use parsed timestamps from the transcript
|
// Use parsed timestamps from the transcript
|
||||||
messages.forEach((message, index) => {
|
messages.forEach((message, index) => {
|
||||||
|
|||||||
@ -123,8 +123,8 @@ export default async function handler(
|
|||||||
where: { companyId: company.id }
|
where: { companyId: company.id }
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
ok: true,
|
ok: true,
|
||||||
imported: importedCount,
|
imported: importedCount,
|
||||||
total: rawSessionData.length,
|
total: rawSessionData.length,
|
||||||
sessions: sessionCount,
|
sessions: sessionCount,
|
||||||
|
|||||||
Reference in New Issue
Block a user