From 8ce0b8be3716b8f2a2cdb7a5f0828cb0d7feed37 Mon Sep 17 00:00:00 2001 From: Kaj Kowalski Date: Thu, 22 May 2025 05:44:09 +0200 Subject: [PATCH] Enhances session details with transcript viewer Adds a transcript viewer component to display transcript content within the session details page. This change introduces a new `TranscriptViewer` component that renders the transcript content if available. It also adds logic to fetch and store transcript content from the provided URL during session data refresh. The existing link-based transcript view is now used as a fallback when only the transcript URL is available. It also fixes an issue where session ID was not properly displayed. --- components/SessionDetails.tsx | 38 ++-- components/SessionDetails.tsx.new | 171 ++++++++++++++++++ components/TranscriptViewer.tsx | 137 ++++++++++++++ lib/types.ts | 1 + package-lock.json | 158 ++++++++++++++++ package.json | 1 + pages/api/admin/refresh-sessions.ts | 30 +++ .../migration.sql | 2 + prisma/schema.prisma | 1 + scripts/fetch_transcripts.ts | 87 +++++++++ tsconfig.json | 8 +- 11 files changed, 620 insertions(+), 14 deletions(-) create mode 100644 components/SessionDetails.tsx.new create mode 100644 components/TranscriptViewer.tsx create mode 100644 prisma/migrations/20250522030816_add_transcript_content/migration.sql create mode 100644 scripts/fetch_transcripts.ts diff --git a/components/SessionDetails.tsx b/components/SessionDetails.tsx index a7cee93..36b994a 100644 --- a/components/SessionDetails.tsx +++ b/components/SessionDetails.tsx @@ -3,6 +3,7 @@ import { ChatSession } from "../lib/types"; import LanguageDisplay from "./LanguageDisplay"; import CountryDisplay from "./CountryDisplay"; +import TranscriptViewer from "./TranscriptViewer"; interface SessionDetailsProps { session: ChatSession; @@ -19,7 +20,7 @@ export default function SessionDetails({ session }: SessionDetailsProps) {
Session ID: - {session.sessionId} + {session.sessionId || session.id}
@@ -142,19 +143,30 @@ export default function SessionDetails({ session }: SessionDetailsProps) {
)} - {session.fullTranscriptUrl && ( -
- Transcript: - - View Full Transcript - -
+ {/* Display transcript using TranscriptViewer if content is available */} + {session.transcriptContent && session.transcriptContent.length > 0 && ( + )} + + {/* Fallback to link only if we only have the URL but no content */} + {(!session.transcriptContent || + session.transcriptContent.length === 0) && + session.fullTranscriptUrl && ( +
+ Transcript: + + View Full Transcript + +
+ )}
); diff --git a/components/SessionDetails.tsx.new b/components/SessionDetails.tsx.new new file mode 100644 index 0000000..c09edd5 --- /dev/null +++ b/components/SessionDetails.tsx.new @@ -0,0 +1,171 @@ +"use client"; + +import { ChatSession } from "../lib/types"; +import LanguageDisplay from "./LanguageDisplay"; +import CountryDisplay from "./CountryDisplay"; +import TranscriptViewer from "./TranscriptViewer"; + +interface SessionDetailsProps { + session: ChatSession; +} + +/** + * Component to display session details with formatted country and language names + */ +export default function SessionDetails({ session }: SessionDetailsProps) { + return ( +
+

Session Details

+ +
+
+ Session ID: + {session.sessionId || session.id} +
+ +
+ Start Time: + + {new Date(session.startTime).toLocaleString()} + +
+ + {session.endTime && ( +
+ End Time: + + {new Date(session.endTime).toLocaleString()} + +
+ )} + + {session.category && ( +
+ Category: + {session.category} +
+ )} + + {session.language && ( +
+ Language: + + + + ({session.language.toUpperCase()}) + + +
+ )} + + {session.country && ( +
+ Country: + + + + ({session.country}) + + +
+ )} + + {session.sentiment !== null && session.sentiment !== undefined && ( +
+ Sentiment: + 0.3 + ? "text-green-500" + : session.sentiment < -0.3 + ? "text-red-500" + : "text-orange-500" + }`} + > + {session.sentiment > 0.3 + ? "Positive" + : session.sentiment < -0.3 + ? "Negative" + : "Neutral"}{" "} + ({session.sentiment.toFixed(2)}) + +
+ )} + +
+ Messages Sent: + {session.messagesSent || 0} +
+ + {typeof session.tokens === "number" && ( +
+ Tokens: + {session.tokens} +
+ )} + + {typeof session.tokensEur === "number" && ( +
+ Cost: + €{session.tokensEur.toFixed(4)} +
+ )} + + {session.avgResponseTime !== null && + session.avgResponseTime !== undefined && ( +
+ Avg Response Time: + + {session.avgResponseTime.toFixed(2)}s + +
+ )} + + {session.escalated !== null && session.escalated !== undefined && ( +
+ Escalated: + + {session.escalated ? "Yes" : "No"} + +
+ )} + + {session.forwardedHr !== null && session.forwardedHr !== undefined && ( +
+ Forwarded to HR: + + {session.forwardedHr ? "Yes" : "No"} + +
+ )} + + {/* Display transcript using TranscriptViewer if content is available */} + {session.transcriptContent && session.transcriptContent.length > 0 && ( + + )} + + {/* Fallback to link only if we only have the URL but no content */} + {(!session.transcriptContent || session.transcriptContent.length === 0) && session.fullTranscriptUrl && ( +
+ Transcript: + + View Full Transcript + +
+ )} +
+
+ ); +} diff --git a/components/TranscriptViewer.tsx b/components/TranscriptViewer.tsx new file mode 100644 index 0000000..114205e --- /dev/null +++ b/components/TranscriptViewer.tsx @@ -0,0 +1,137 @@ +"use client"; + +import { useState } from "react"; + +interface TranscriptViewerProps { + transcriptContent: string; + transcriptUrl?: string | null; +} + +/** + * Format the transcript content into a more readable format with styling + */ +function formatTranscript(content: string): React.ReactNode[] { + if (!content.trim()) { + return [

No transcript content available.

]; + } + + // Split the transcript by lines + const lines = content.split("\n"); + + const elements: React.ReactNode[] = []; + let currentSpeaker: string | null = null; + let currentMessages: string[] = []; + + // Process each line + lines.forEach((line) => { + line = line.trim(); + if (!line) { + // Empty line, ignore + return; + } + + // Check if this is a new speaker line + if (line.startsWith("User:") || line.startsWith("Assistant:")) { + // If we have accumulated messages for a previous speaker, add them + if (currentSpeaker && currentMessages.length > 0) { + elements.push( +
+
+ {currentMessages.map((msg, i) => ( +

{msg}

+ ))} +
+
+ ); + currentMessages = []; + } + + // Set the new current speaker + currentSpeaker = line.startsWith("User:") ? "User" : "Assistant"; + // Add the content after "User:" or "Assistant:" + const messageContent = line.substring(line.indexOf(":") + 1).trim(); + if (messageContent) { + currentMessages.push(messageContent); + } + } else if (currentSpeaker) { + // This is a continuation of the current speaker's message + currentMessages.push(line); + } + }); + + // Add any remaining messages + if (currentSpeaker && currentMessages.length > 0) { + elements.push( +
+
+ {currentMessages.map((msg, i) => ( +

{msg}

+ ))} +
+
+ ); + } + + return elements; +} + +/** + * Component to display a chat transcript + */ +export default function TranscriptViewer({ + transcriptContent, + transcriptUrl, +}: TranscriptViewerProps) { + const [showTranscript, setShowTranscript] = useState(false); + + return ( +
+
+ Transcript: +
+ + {transcriptUrl && ( + + View Source + + )} +
+
+ + {/* Display transcript content if expanded */} + {showTranscript && ( +
+
{formatTranscript(transcriptContent)}
+
+ )} +
+ ); +} diff --git a/lib/types.ts b/lib/types.ts index fb8060e..f1add99 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -59,6 +59,7 @@ export interface ChatSession { tokensEur?: number; initialMsg?: string; fullTranscriptUrl?: string | null; + transcriptContent?: string | null; } export interface DayMetrics { diff --git a/package-lock.json b/package-lock.json index 98aa8ae..2818d43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "prettier": "^3.5.3", "prisma": "^6.8.2", "tailwindcss": "^4.1.7", + "ts-node": "^10.9.2", "typescript": "^5.0.0" } }, @@ -88,6 +89,30 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@emnapi/core": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", @@ -1438,6 +1463,34 @@ "tailwindcss": "4.1.7" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -2299,6 +2352,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2332,6 +2398,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2832,6 +2905,13 @@ "node": ">= 0.6" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3441,6 +3521,16 @@ "integrity": "sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==", "license": "MIT" }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5591,6 +5681,13 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7185,6 +7282,50 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -7386,6 +7527,13 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -7516,6 +7664,16 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 3c3e184..a53d292 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "prettier": "^3.5.3", "prisma": "^6.8.2", "tailwindcss": "^4.1.7", + "ts-node": "^10.9.2", "typescript": "^5.0.0" } } diff --git a/pages/api/admin/refresh-sessions.ts b/pages/api/admin/refresh-sessions.ts index 14fd642..c50fb0d 100644 --- a/pages/api/admin/refresh-sessions.ts +++ b/pages/api/admin/refresh-sessions.ts @@ -11,6 +11,27 @@ interface SessionCreateData { [key: string]: unknown; } +/** + * Fetches transcript content from a URL + * @param url The URL to fetch the transcript from + * @returns The transcript content or null if fetching fails + */ +async function fetchTranscriptContent(url: string): Promise { + 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( req: NextApiRequest, res: NextApiResponse @@ -86,6 +107,14 @@ 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 + ); + } + // Only include fields that are properly typed for Prisma await prisma.session.create({ data: { @@ -107,6 +136,7 @@ export default async function handler( ? session.forwardedHr : null, fullTranscriptUrl: session.fullTranscriptUrl || null, + transcriptContent: transcriptContent, // Add the transcript content avgResponseTime: typeof session.avgResponseTime === "number" ? session.avgResponseTime diff --git a/prisma/migrations/20250522030816_add_transcript_content/migration.sql b/prisma/migrations/20250522030816_add_transcript_content/migration.sql new file mode 100644 index 0000000..93c52ae --- /dev/null +++ b/prisma/migrations/20250522030816_add_transcript_content/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Session" ADD COLUMN "transcriptContent" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e9db3d2..8f56443 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -47,6 +47,7 @@ model Session { escalated Boolean? forwardedHr Boolean? fullTranscriptUrl String? + transcriptContent String? // Added to store the fetched transcript avgResponseTime Float? tokens Int? tokensEur Float? diff --git a/scripts/fetch_transcripts.ts b/scripts/fetch_transcripts.ts new file mode 100644 index 0000000..9a301d0 --- /dev/null +++ b/scripts/fetch_transcripts.ts @@ -0,0 +1,87 @@ +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +async function main() { + console.log("Starting to fetch missing transcripts..."); + + const sessionsToUpdate = await prisma.session.findMany({ + where: { + AND: [ + { fullTranscriptUrl: { not: null } }, + { fullTranscriptUrl: { not: "" } }, // Ensure URL is not an empty string + { transcriptContent: null }, + ], + }, + select: { + id: true, + fullTranscriptUrl: true, + }, + }); + + if (sessionsToUpdate.length === 0) { + console.log("No sessions found requiring transcript fetching."); + return; + } + + console.log(`Found ${sessionsToUpdate.length} sessions to update.`); + let successCount = 0; + let errorCount = 0; + + for (const session of sessionsToUpdate) { + if (!session.fullTranscriptUrl) { + // Should not happen due to query, but good for type safety + console.warn(`Session ${session.id} has no fullTranscriptUrl, skipping.`); + continue; + } + + console.log( + `Fetching transcript for session ${session.id} from ${session.fullTranscriptUrl}...` + ); + try { + const response = await fetch(session.fullTranscriptUrl); + if (!response.ok) { + console.error( + `Failed to fetch transcript for session ${session.id}: ${response.status} ${response.statusText}` + ); + const errorBody = await response.text(); + console.error(`Error details: ${errorBody.substring(0, 500)}`); // Log first 500 chars of error + errorCount++; + continue; + } + + const transcriptText = await response.text(); + + if (transcriptText.trim() === "") { + console.warn( + `Fetched empty transcript for session ${session.id}. Storing as empty string.` + ); + } + + await prisma.session.update({ + where: { id: session.id }, + data: { transcriptContent: transcriptText }, + }); + console.log( + `Successfully fetched and stored transcript for session ${session.id}.` + ); + successCount++; + } catch (error) { + console.error(`Error processing session ${session.id}:`, error); + errorCount++; + } + } + + console.log("Transcript fetching complete."); + console.log(`Successfully updated: ${successCount} sessions.`); + console.log(`Failed to update: ${errorCount} sessions.`); +} + +main() + .catch((e) => { + console.error("An error occurred during the script execution:", e); + process.exitCode = 1; + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/tsconfig.json b/tsconfig.json index 29dce0c..19a89ba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,6 +25,12 @@ }, "strictNullChecks": true }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "components/SessionDetails.tsx.bak" + ], "exclude": ["node_modules"] }