mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 09:52:09 +01:00
Enhances session and dashboard components with authentication checks and loading states
This commit is contained in:
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { signOut, useSession } from "next-auth/react";
|
import { signOut, useSession } from "next-auth/react";
|
||||||
|
import { useRouter } from "next/navigation"; // Import useRouter
|
||||||
import {
|
import {
|
||||||
SessionsLineChart,
|
SessionsLineChart,
|
||||||
CategoriesBarChart,
|
CategoriesBarChart,
|
||||||
@ -20,29 +21,36 @@ import WelcomeBanner from "../../components/WelcomeBanner";
|
|||||||
|
|
||||||
// Safely wrapped component with useSession
|
// Safely wrapped component with useSession
|
||||||
function DashboardContent() {
|
function DashboardContent() {
|
||||||
const { data: session } = useSession();
|
const { data: session, status } = useSession(); // Add status from useSession
|
||||||
|
const router = useRouter(); // Initialize useRouter
|
||||||
const [metrics, setMetrics] = useState<MetricsResult | null>(null);
|
const [metrics, setMetrics] = useState<MetricsResult | null>(null);
|
||||||
const [company, setCompany] = useState<Company | null>(null);
|
const [company, setCompany] = useState<Company | null>(null);
|
||||||
const [, setLoading] = useState<boolean>(false);
|
const [, setLoading] = useState<boolean>(false);
|
||||||
// Remove unused csvUrl state variable
|
|
||||||
const [refreshing, setRefreshing] = useState<boolean>(false);
|
const [refreshing, setRefreshing] = useState<boolean>(false);
|
||||||
|
|
||||||
const isAdmin = session?.user?.role === "admin";
|
const isAdmin = session?.user?.role === "admin";
|
||||||
const isAuditor = session?.user?.role === "auditor";
|
const isAuditor = session?.user?.role === "auditor";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fetch metrics and company on mount
|
// Redirect if not authenticated
|
||||||
const fetchData = async () => {
|
if (status === "unauthenticated") {
|
||||||
setLoading(true);
|
router.push("/login");
|
||||||
const res = await fetch("/api/dashboard/metrics");
|
return; // Stop further execution in this effect
|
||||||
const data = await res.json();
|
}
|
||||||
setMetrics(data.metrics);
|
|
||||||
setCompany(data.company);
|
// Fetch metrics and company on mount if authenticated
|
||||||
// Removed unused csvUrl assignment
|
if (status === "authenticated") {
|
||||||
setLoading(false);
|
const fetchData = async () => {
|
||||||
};
|
setLoading(true);
|
||||||
fetchData();
|
const res = await fetch("/api/dashboard/metrics");
|
||||||
}, []);
|
const data = await res.json();
|
||||||
|
setMetrics(data.metrics);
|
||||||
|
setCompany(data.company);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
}, [status, router]); // Add status and router to dependency array
|
||||||
|
|
||||||
async function handleRefresh() {
|
async function handleRefresh() {
|
||||||
if (isAuditor) return; // Prevent auditors from refreshing
|
if (isAuditor) return; // Prevent auditors from refreshing
|
||||||
@ -51,7 +59,6 @@ function DashboardContent() {
|
|||||||
|
|
||||||
// Make sure we have a company ID to send
|
// Make sure we have a company ID to send
|
||||||
if (!company?.id) {
|
if (!company?.id) {
|
||||||
// Use a more appropriate error handling approach
|
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
alert("Cannot refresh: Company ID is missing");
|
alert("Cannot refresh: Company ID is missing");
|
||||||
return;
|
return;
|
||||||
@ -70,7 +77,6 @@ function DashboardContent() {
|
|||||||
setMetrics(data.metrics);
|
setMetrics(data.metrics);
|
||||||
} else {
|
} else {
|
||||||
const errorData = await res.json();
|
const errorData = await res.json();
|
||||||
// Use alert instead of console.error for user feedback
|
|
||||||
alert(`Failed to refresh sessions: ${errorData.error}`);
|
alert(`Failed to refresh sessions: ${errorData.error}`);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -82,7 +88,6 @@ function DashboardContent() {
|
|||||||
const getSentimentData = () => {
|
const getSentimentData = () => {
|
||||||
if (!metrics) return { positive: 0, neutral: 0, negative: 0 };
|
if (!metrics) return { positive: 0, neutral: 0, negative: 0 };
|
||||||
|
|
||||||
// If we have the new sentiment count fields, use those
|
|
||||||
if (
|
if (
|
||||||
metrics.sentimentPositiveCount !== undefined &&
|
metrics.sentimentPositiveCount !== undefined &&
|
||||||
metrics.sentimentNeutralCount !== undefined &&
|
metrics.sentimentNeutralCount !== undefined &&
|
||||||
@ -95,12 +100,11 @@ function DashboardContent() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to estimating based on total
|
|
||||||
const total = metrics.totalSessions || 1;
|
const total = metrics.totalSessions || 1;
|
||||||
return {
|
return {
|
||||||
positive: Math.round(total * 0.6), // 60% positive as fallback
|
positive: Math.round(total * 0.6),
|
||||||
neutral: Math.round(total * 0.3), // 30% neutral as fallback
|
neutral: Math.round(total * 0.3),
|
||||||
negative: Math.round(total * 0.1), // 10% negative as fallback
|
negative: Math.round(total * 0.1),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -111,7 +115,6 @@ function DashboardContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const days = Object.keys(metrics.tokensByDay).sort();
|
const days = Object.keys(metrics.tokensByDay).sort();
|
||||||
// Get the last 7 days if available
|
|
||||||
const labels = days.slice(-7);
|
const labels = days.slice(-7);
|
||||||
const values = labels.map((day) => metrics.tokensByDay?.[day] || 0);
|
const values = labels.map((day) => metrics.tokensByDay?.[day] || 0);
|
||||||
const costs = labels.map((day) => metrics.tokensCostByDay?.[day] || 0);
|
const costs = labels.map((day) => metrics.tokensCostByDay?.[day] || 0);
|
||||||
@ -119,6 +122,16 @@ function DashboardContent() {
|
|||||||
return { labels, values, costs };
|
return { labels, values, costs };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Show loading state while session status is being determined
|
||||||
|
if (status === "loading") {
|
||||||
|
return <div className="text-center py-10">Loading session...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If unauthenticated and not redirected yet (should be handled by useEffect, but as a fallback)
|
||||||
|
if (status === "unauthenticated") {
|
||||||
|
return <div className="text-center py-10">Redirecting to login...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
if (!metrics || !company) {
|
if (!metrics || !company) {
|
||||||
return <div className="text-center py-10">Loading dashboard...</div>;
|
return <div className="text-center py-10">Loading dashboard...</div>;
|
||||||
}
|
}
|
||||||
@ -130,12 +143,11 @@ function DashboardContent() {
|
|||||||
.map(([text, value]) => ({ text, value }))
|
.map(([text, value]) => ({ text, value }))
|
||||||
.filter((item) => item.text.trim() !== "")
|
.filter((item) => item.text.trim() !== "")
|
||||||
.sort((a, b) => b.value - a.value)
|
.sort((a, b) => b.value - a.value)
|
||||||
.slice(0, 30); // Limit to top 30 categories
|
.slice(0, 30);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to prepare country data for the map - using simulated/dummy data
|
// Function to prepare country data for the map - using simulated/dummy data
|
||||||
const getCountryData = () => {
|
const getCountryData = () => {
|
||||||
// Use dummy country data as the actual metrics doesn't contain session-level country data
|
|
||||||
return {
|
return {
|
||||||
US: 42,
|
US: 42,
|
||||||
GB: 25,
|
GB: 25,
|
||||||
@ -156,14 +168,10 @@ function DashboardContent() {
|
|||||||
|
|
||||||
// Function to prepare response time distribution data
|
// Function to prepare response time distribution data
|
||||||
const getResponseTimeData = () => {
|
const getResponseTimeData = () => {
|
||||||
// Since we have aggregated avgResponseTime, we'll create a simulated distribution
|
|
||||||
// based on the average response time
|
|
||||||
const avgTime = metrics.avgResponseTime || 1.5;
|
const avgTime = metrics.avgResponseTime || 1.5;
|
||||||
const simulatedData: number[] = [];
|
const simulatedData: number[] = [];
|
||||||
|
|
||||||
// Generate response times that average to our avgResponseTime
|
|
||||||
for (let i = 0; i < 50; i++) {
|
for (let i = 0; i < 50; i++) {
|
||||||
// Random value that's mostly close to the average
|
|
||||||
const randomFactor = 0.5 + Math.random();
|
const randomFactor = 0.5 + Math.random();
|
||||||
simulatedData.push(avgTime * randomFactor);
|
simulatedData.push(avgTime * randomFactor);
|
||||||
}
|
}
|
||||||
@ -173,30 +181,20 @@ function DashboardContent() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{" "}
|
|
||||||
{/* Increased spacing */}
|
|
||||||
{/* Welcome Banner */}
|
|
||||||
<WelcomeBanner companyName={company.name} />
|
<WelcomeBanner companyName={company.name} />
|
||||||
{/* Header with company info */}
|
|
||||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center bg-white p-6 rounded-2xl shadow-lg ring-1 ring-slate-200/50">
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center bg-white p-6 rounded-2xl shadow-lg ring-1 ring-slate-200/50">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-slate-800">{company.name}</h1>
|
<h1 className="text-3xl font-bold text-slate-800">{company.name}</h1>
|
||||||
<p className="text-slate-500 mt-1">
|
<p className="text-slate-500 mt-1">
|
||||||
{" "}
|
|
||||||
{/* Adjusted text color and margin */}
|
|
||||||
Dashboard updated{" "}
|
Dashboard updated{" "}
|
||||||
<span className="font-medium text-slate-600">
|
<span className="font-medium text-slate-600">
|
||||||
{" "}
|
|
||||||
{/* Adjusted text color */}
|
|
||||||
{new Date(metrics.lastUpdated || Date.now()).toLocaleString()}
|
{new Date(metrics.lastUpdated || Date.now()).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 mt-4 sm:mt-0">
|
<div className="flex items-center gap-3 mt-4 sm:mt-0">
|
||||||
{" "}
|
|
||||||
{/* Adjusted gap and responsive margin */}
|
|
||||||
<button
|
<button
|
||||||
className="bg-sky-600 text-white py-2 px-5 rounded-lg shadow hover:bg-sky-700 transition-colors disabled:opacity-60 disabled:cursor-not-allowed flex items-center text-sm font-medium" // Adjusted padding, shadow, colors, and added font style
|
className="bg-sky-600 text-white py-2 px-5 rounded-lg shadow hover:bg-sky-700 transition-colors disabled:opacity-60 disabled:cursor-not-allowed flex items-center text-sm font-medium"
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
disabled={refreshing || isAuditor}
|
disabled={refreshing || isAuditor}
|
||||||
>
|
>
|
||||||
@ -227,7 +225,7 @@ function DashboardContent() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<svg
|
<svg
|
||||||
className="w-4 h-4 mr-2" /* Adjusted margin */
|
className="w-4 h-4 mr-2"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@ -245,11 +243,11 @@ function DashboardContent() {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="bg-slate-100 text-slate-700 py-2 px-5 rounded-lg shadow hover:bg-slate-200 transition-colors flex items-center text-sm font-medium" // Adjusted padding, colors, and added font style
|
className="bg-slate-100 text-slate-700 py-2 px-5 rounded-lg shadow hover:bg-slate-200 transition-colors flex items-center text-sm font-medium"
|
||||||
onClick={() => signOut()}
|
onClick={() => signOut()}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-4 h-4 mr-2" /* Adjusted margin */
|
className="w-4 h-4 mr-2"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@ -266,7 +264,6 @@ function DashboardContent() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Key Performance Metrics */}
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<MetricCard
|
<MetricCard
|
||||||
title="Total Sessions"
|
title="Total Sessions"
|
||||||
@ -303,7 +300,6 @@ function DashboardContent() {
|
|||||||
variant="success"
|
variant="success"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Sentiment & Escalation Metrics */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<div className="bg-white p-6 rounded-xl shadow lg:col-span-1">
|
<div className="bg-white p-6 rounded-xl shadow lg:col-span-1">
|
||||||
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
||||||
@ -318,9 +314,9 @@ function DashboardContent() {
|
|||||||
getSentimentData().negative,
|
getSentimentData().negative,
|
||||||
],
|
],
|
||||||
colors: [
|
colors: [
|
||||||
"rgba(34, 197, 94, 0.8)", // green
|
"rgba(34, 197, 94, 0.8)",
|
||||||
"rgba(249, 115, 22, 0.8)", // orange
|
"rgba(249, 115, 22, 0.8)",
|
||||||
"rgba(239, 68, 68, 0.8)", // red
|
"rgba(239, 68, 68, 0.8)",
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
centerText={{
|
centerText={{
|
||||||
@ -367,7 +363,6 @@ function DashboardContent() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Charts Row */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<div className="bg-white p-6 rounded-xl shadow">
|
<div className="bg-white p-6 rounded-xl shadow">
|
||||||
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
||||||
@ -382,7 +377,6 @@ function DashboardContent() {
|
|||||||
<CategoriesBarChart categories={metrics.categories || {}} />
|
<CategoriesBarChart categories={metrics.categories || {}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Word Cloud and World Map */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<div className="bg-white p-6 rounded-xl shadow overflow-hidden">
|
<div className="bg-white p-6 rounded-xl shadow overflow-hidden">
|
||||||
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
||||||
@ -397,7 +391,6 @@ function DashboardContent() {
|
|||||||
<GeographicMap countries={getCountryData()} height={300} />
|
<GeographicMap countries={getCountryData()} height={300} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Response Time Distribution and Language Distribution */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<div className="bg-white p-6 rounded-xl shadow">
|
<div className="bg-white p-6 rounded-xl shadow">
|
||||||
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
||||||
@ -413,7 +406,6 @@ function DashboardContent() {
|
|||||||
<LanguagePieChart languages={metrics.languages || {}} />
|
<LanguagePieChart languages={metrics.languages || {}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Token Usage */}
|
|
||||||
<div className="bg-white p-6 rounded-xl shadow">
|
<div className="bg-white p-6 rounded-xl shadow">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="font-bold text-lg text-gray-800">
|
<h3 className="font-bold text-lg text-gray-800">
|
||||||
@ -432,7 +424,6 @@ function DashboardContent() {
|
|||||||
</div>
|
</div>
|
||||||
<TokenUsageChart tokenData={getTokenData()} />
|
<TokenUsageChart tokenData={getTokenData()} />
|
||||||
</div>
|
</div>
|
||||||
{/* Admin Controls */}
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<>
|
<>
|
||||||
<DashboardSettings company={company} session={session} />
|
<DashboardSettings company={company} session={session} />
|
||||||
@ -445,14 +436,9 @@ function DashboardContent() {
|
|||||||
|
|
||||||
// Our exported component
|
// Our exported component
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
// We don't use useSession here to avoid the error outside the provider
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-sky-100 p-4 md:p-6">
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-sky-100 p-4 md:p-6">
|
||||||
{" "}
|
|
||||||
{/* Added gradient background */}
|
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
{" "}
|
|
||||||
{/* Added inner container for content alignment */}
|
|
||||||
<DashboardContent />
|
<DashboardContent />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation"; // Import useRouter
|
||||||
|
import { useSession } from "next-auth/react"; // Import useSession
|
||||||
import SessionDetails from "../../../../components/SessionDetails";
|
import SessionDetails from "../../../../components/SessionDetails";
|
||||||
import TranscriptViewer from "../../../../components/TranscriptViewer";
|
import TranscriptViewer from "../../../../components/TranscriptViewer";
|
||||||
import { ChatSession } from "../../../../lib/types";
|
import { ChatSession } from "../../../../lib/types";
|
||||||
@ -9,15 +10,22 @@ import Link from "next/link";
|
|||||||
|
|
||||||
export default function SessionViewPage() {
|
export default function SessionViewPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const router = useRouter(); // Initialize useRouter
|
||||||
|
const { status } = useSession(); // Get session status, removed unused sessionData
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
const [session, setSession] = useState<ChatSession | null>(null);
|
const [session, setSession] = useState<ChatSession | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true); // This will now primarily be for data fetching
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (status === "unauthenticated") {
|
||||||
|
router.push("/login");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "authenticated" && id) {
|
||||||
const fetchSession = async () => {
|
const fetchSession = async () => {
|
||||||
setLoading(true);
|
if (!session) setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/dashboard/session/${id}`);
|
const response = await fetch(`/api/dashboard/session/${id}`);
|
||||||
@ -40,10 +48,29 @@ export default function SessionViewPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchSession();
|
fetchSession();
|
||||||
|
} else if (status === "authenticated" && !id) {
|
||||||
|
setError("Session ID is missing.");
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [id]);
|
}, [id, status, router, session]);
|
||||||
|
|
||||||
if (loading) {
|
if (status === "loading") {
|
||||||
|
return (
|
||||||
|
<div className="p-4 md:p-6 flex justify-center items-center min-h-screen">
|
||||||
|
<p className="text-gray-600 text-lg">Loading session...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "unauthenticated") {
|
||||||
|
return (
|
||||||
|
<div className="p-4 md:p-6 flex justify-center items-center min-h-screen">
|
||||||
|
<p className="text-gray-600 text-lg">Redirecting to login...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading && status === "authenticated") {
|
||||||
return (
|
return (
|
||||||
<div className="p-4 md:p-6 flex justify-center items-center min-h-screen">
|
<div className="p-4 md:p-6 flex justify-center items-center min-h-screen">
|
||||||
<p className="text-gray-600 text-lg">Loading session details...</p>
|
<p className="text-gray-600 text-lg">Loading session details...</p>
|
||||||
@ -112,8 +139,6 @@ export default function SessionViewPage() {
|
|||||||
{session.transcriptContent &&
|
{session.transcriptContent &&
|
||||||
session.transcriptContent.trim() !== "" ? (
|
session.transcriptContent.trim() !== "" ? (
|
||||||
<div className="mt-0">
|
<div className="mt-0">
|
||||||
{" "}
|
|
||||||
{/* Adjusted margin, TranscriptViewer has its own top margin */}
|
|
||||||
<TranscriptViewer
|
<TranscriptViewer
|
||||||
transcriptContent={session.transcriptContent}
|
transcriptContent={session.transcriptContent}
|
||||||
transcriptUrl={session.fullTranscriptUrl}
|
transcriptUrl={session.fullTranscriptUrl}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import ReactMarkdown from "react-markdown"; // Import ReactMarkdown
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import rehypeRaw from "rehype-raw"; // Import rehype-raw
|
||||||
|
|
||||||
interface TranscriptViewerProps {
|
interface TranscriptViewerProps {
|
||||||
transcriptContent: string;
|
transcriptContent: string;
|
||||||
@ -51,6 +52,7 @@ function formatTranscript(content: string): React.ReactNode[] {
|
|||||||
// Use ReactMarkdown to render each message part
|
// Use ReactMarkdown to render each message part
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
key={i}
|
key={i}
|
||||||
|
rehypePlugins={[rehypeRaw]} // Add rehypeRaw to plugins
|
||||||
components={{
|
components={{
|
||||||
p: "span",
|
p: "span",
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
@ -102,6 +104,7 @@ function formatTranscript(content: string): React.ReactNode[] {
|
|||||||
// Use ReactMarkdown to render each message part
|
// Use ReactMarkdown to render each message part
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
key={i}
|
key={i}
|
||||||
|
rehypePlugins={[rehypeRaw]} // Add rehypeRaw to plugins
|
||||||
components={{
|
components={{
|
||||||
p: "span",
|
p: "span",
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
|||||||
180
package-lock.json
generated
180
package-lock.json
generated
@ -30,7 +30,8 @@
|
|||||||
"react-chartjs-2": "^5.0.0",
|
"react-chartjs-2": "^5.0.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-leaflet": "^5.0.0",
|
"react-leaflet": "^5.0.0",
|
||||||
"react-markdown": "^10.1.0"
|
"react-markdown": "^10.1.0",
|
||||||
|
"rehype-raw": "^7.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
@ -3722,6 +3723,18 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-abstract": {
|
"node_modules/es-abstract": {
|
||||||
"version": "1.23.9",
|
"version": "1.23.9",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
|
||||||
@ -4896,6 +4909,64 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hast-util-from-parse5": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"@types/unist": "^3.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
|
"hastscript": "^9.0.0",
|
||||||
|
"property-information": "^7.0.0",
|
||||||
|
"vfile": "^6.0.0",
|
||||||
|
"vfile-location": "^5.0.0",
|
||||||
|
"web-namespaces": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hast-util-parse-selector": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hast-util-raw": {
|
||||||
|
"version": "9.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz",
|
||||||
|
"integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"@types/unist": "^3.0.0",
|
||||||
|
"@ungap/structured-clone": "^1.0.0",
|
||||||
|
"hast-util-from-parse5": "^8.0.0",
|
||||||
|
"hast-util-to-parse5": "^8.0.0",
|
||||||
|
"html-void-elements": "^3.0.0",
|
||||||
|
"mdast-util-to-hast": "^13.0.0",
|
||||||
|
"parse5": "^7.0.0",
|
||||||
|
"unist-util-position": "^5.0.0",
|
||||||
|
"unist-util-visit": "^5.0.0",
|
||||||
|
"vfile": "^6.0.0",
|
||||||
|
"web-namespaces": "^2.0.0",
|
||||||
|
"zwitch": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hast-util-to-jsx-runtime": {
|
"node_modules/hast-util-to-jsx-runtime": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
||||||
@ -4923,6 +4994,35 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hast-util-to-parse5": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"comma-separated-tokens": "^2.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
|
"property-information": "^6.0.0",
|
||||||
|
"space-separated-tokens": "^2.0.0",
|
||||||
|
"web-namespaces": "^2.0.0",
|
||||||
|
"zwitch": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hast-util-to-parse5/node_modules/property-information": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hast-util-whitespace": {
|
"node_modules/hast-util-whitespace": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
||||||
@ -4936,6 +5036,23 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hastscript": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"comma-separated-tokens": "^2.0.0",
|
||||||
|
"hast-util-parse-selector": "^4.0.0",
|
||||||
|
"property-information": "^7.0.0",
|
||||||
|
"space-separated-tokens": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-url-attributes": {
|
"node_modules/html-url-attributes": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||||
@ -4946,6 +5063,16 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-void-elements": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/i18n-iso-countries": {
|
"node_modules/i18n-iso-countries": {
|
||||||
"version": "7.14.0",
|
"version": "7.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.14.0.tgz",
|
||||||
@ -7169,6 +7296,18 @@
|
|||||||
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
|
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/parse5": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"entities": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-exists": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
@ -7523,6 +7662,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rehype-raw": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"hast-util-raw": "^9.0.0",
|
||||||
|
"vfile": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark-parse": {
|
"node_modules/remark-parse": {
|
||||||
"version": "11.0.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||||
@ -8680,6 +8834,20 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vfile-location": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^3.0.0",
|
||||||
|
"vfile": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vfile-message": {
|
"node_modules/vfile-message": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
|
||||||
@ -8694,6 +8862,16 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/web-namespaces": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/web-streams-polyfill": {
|
"node_modules/web-streams-polyfill": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||||
|
|||||||
@ -37,7 +37,8 @@
|
|||||||
"react-chartjs-2": "^5.0.0",
|
"react-chartjs-2": "^5.0.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-leaflet": "^5.0.0",
|
"react-leaflet": "^5.0.0",
|
||||||
"react-markdown": "^10.1.0"
|
"react-markdown": "^10.1.0",
|
||||||
|
"rehype-raw": "^7.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user