Improves transcript viewer with Markdown support

Adds Markdown rendering to the transcript viewer for enhanced formatting.

This change integrates `react-markdown` to parse and render transcript content, allowing for richer text formatting, including links and other Markdown elements.  It also adds a toggle to switch between raw and formatted text, and displays a message when transcript content is unavailable. The UI of the transcript viewer has been improved to be more user-friendly with titles and descriptions.
This commit is contained in:
2025-05-22 06:13:00 +02:00
parent 8ce0b8be37
commit ee212e3eea
10 changed files with 1740 additions and 116 deletions

View File

@ -17,19 +17,19 @@ interface SessionCreateData {
* @returns The transcript content or null if fetching fails
*/
async function fetchTranscriptContent(url: string): Promise<string | null> {
try {
const response = await fetch(url);
if (!response.ok) {
process.stderr.write(
`Error fetching transcript: ${response.statusText}\n`
);
return null;
}
return await response.text();
} catch (error) {
process.stderr.write(`Failed to fetch transcript: ${error}\n`);
return null;
try {
const response = await fetch(url);
if (!response.ok) {
process.stderr.write(
`Error fetching transcript: ${response.statusText}\n`
);
return null;
}
return await response.text();
} catch (error) {
process.stderr.write(`Failed to fetch transcript: ${error}\n`);
return null;
}
}
export default async function handler(
@ -107,13 +107,13 @@ export default async function handler(
? session.endTime
: new Date();
// Fetch transcript content if URL is available
let transcriptContent: string | null = null;
if (session.fullTranscriptUrl) {
transcriptContent = await fetchTranscriptContent(
session.fullTranscriptUrl
);
}
// Fetch transcript content if URL is available
let transcriptContent: string | null = null;
if (session.fullTranscriptUrl) {
transcriptContent = await fetchTranscriptContent(
session.fullTranscriptUrl
);
}
// Only include fields that are properly typed for Prisma
await prisma.session.create({
@ -136,7 +136,7 @@ export default async function handler(
? session.forwardedHr
: null,
fullTranscriptUrl: session.fullTranscriptUrl || null,
transcriptContent: transcriptContent, // Add the transcript content
transcriptContent: transcriptContent, // Add the transcript content
avgResponseTime:
typeof session.avgResponseTime === "number"
? session.avgResponseTime

View File

@ -0,0 +1,62 @@
import { NextApiRequest, NextApiResponse } from "next";
import { prisma } from "../../../../lib/prisma";
import { ChatSession } from "../../../../lib/types";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const { id } = req.query;
if (!id || typeof id !== "string") {
return res.status(400).json({ error: "Session ID is required" });
}
try {
const prismaSession = await prisma.session.findUnique({
where: { id },
});
if (!prismaSession) {
return res.status(404).json({ error: "Session not found" });
}
// Map Prisma session object to ChatSession type
const session: ChatSession = {
...prismaSession,
sessionId: prismaSession.id, // Assuming ChatSession's sessionId is Prisma's id
startTime: new Date(prismaSession.startTime),
endTime: prismaSession.endTime ? new Date(prismaSession.endTime) : null,
createdAt: new Date(prismaSession.createdAt),
updatedAt: new Date(prismaSession.updatedAt),
userId: prismaSession.userId === undefined ? null : prismaSession.userId,
category: prismaSession.category === undefined ? null : prismaSession.category,
language: prismaSession.language === undefined ? null : prismaSession.language,
country: prismaSession.country === undefined ? null : prismaSession.country,
ipAddress: prismaSession.ipAddress === undefined ? null : prismaSession.ipAddress,
sentiment: prismaSession.sentiment === undefined ? null : prismaSession.sentiment,
messagesSent: prismaSession.messagesSent === undefined ? undefined : prismaSession.messagesSent,
avgResponseTime: prismaSession.avgResponseTime === undefined ? null : prismaSession.avgResponseTime,
escalated: prismaSession.escalated === undefined ? undefined : prismaSession.escalated,
forwardedHr: prismaSession.forwardedHr === undefined ? undefined : prismaSession.forwardedHr,
tokens: prismaSession.tokens === undefined ? undefined : prismaSession.tokens,
tokensEur: prismaSession.tokensEur === undefined ? undefined : prismaSession.tokensEur,
initialMsg: prismaSession.initialMsg === undefined ? null : prismaSession.initialMsg,
fullTranscriptUrl: prismaSession.fullTranscriptUrl === undefined ? null : prismaSession.fullTranscriptUrl,
transcriptContent: prismaSession.transcriptContent === undefined ? null : prismaSession.transcriptContent,
};
return res.status(200).json({ session });
} catch (error) {
console.error(`Failed to fetch session ${id}:`, error);
const errorMessage =
error instanceof Error ? error.message : "An unknown error occurred";
return res
.status(500)
.json({ error: "Failed to fetch session", details: errorMessage });
}
}

View File

@ -0,0 +1,73 @@
import { NextApiRequest, NextApiResponse } from "next";
import { prisma } from "../../../lib/prisma";
import { ChatSession } from "../../../lib/types";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const { searchTerm } = req.query;
try {
let prismaSessions;
if (searchTerm && typeof searchTerm === "string" && searchTerm.trim() !== "") {
const searchConditions = [
{ id: { contains: searchTerm, mode: "insensitive" } },
{ category: { contains: searchTerm, mode: "insensitive" } },
{ initialMsg: { contains: searchTerm, mode: "insensitive" } },
{ transcriptContent: { contains: searchTerm, mode: "insensitive" } },
];
prismaSessions = await prisma.session.findMany({
where: {
OR: searchConditions,
},
orderBy: {
startTime: "desc",
},
});
} else {
prismaSessions = await prisma.session.findMany({
orderBy: {
startTime: "desc",
},
});
}
const sessions: ChatSession[] = prismaSessions.map(ps => ({
...ps,
sessionId: ps.id,
startTime: new Date(ps.startTime),
endTime: ps.endTime ? new Date(ps.endTime) : null,
createdAt: new Date(ps.createdAt),
updatedAt: new Date(ps.updatedAt),
userId: ps.userId === undefined ? null : ps.userId,
category: ps.category === undefined ? null : ps.category,
language: ps.language === undefined ? null : ps.language,
country: ps.country === undefined ? null : ps.country,
ipAddress: ps.ipAddress === undefined ? null : ps.ipAddress,
sentiment: ps.sentiment === undefined ? null : ps.sentiment,
messagesSent: ps.messagesSent === undefined ? undefined : ps.messagesSent,
avgResponseTime: ps.avgResponseTime === undefined ? null : ps.avgResponseTime,
escalated: ps.escalated === undefined ? undefined : ps.escalated,
forwardedHr: ps.forwardedHr === undefined ? undefined : ps.forwardedHr,
tokens: ps.tokens === undefined ? undefined : ps.tokens,
tokensEur: ps.tokensEur === undefined ? undefined : ps.tokensEur,
initialMsg: ps.initialMsg === undefined ? null : ps.initialMsg,
fullTranscriptUrl: ps.fullTranscriptUrl === undefined ? null : ps.fullTranscriptUrl,
transcriptContent: ps.transcriptContent === undefined ? null : ps.transcriptContent,
}));
return res.status(200).json({ sessions });
} catch (error) {
console.error("Failed to fetch sessions:", error);
const errorMessage =
error instanceof Error ? error.message : "An unknown error occurred";
return res.status(500).json({ error: "Failed to fetch sessions", details: errorMessage });
}
}