added audio field enhancements in MockDreams
, improved chart handling for chip
inputs (EEG, vitals, and movement visualization) using d3
, and expanded multimedia support in DreamPage
UI components
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
This commit is contained in:
@@ -38,12 +38,12 @@ export const mockDreams: Dream[] = [
|
|||||||
input: {
|
input: {
|
||||||
inputType: 'audio',
|
inputType: 'audio',
|
||||||
transcript: 'Ich besuchte ein Konzert unter dem Ozean. Die Musiker waren Delfine, die Melodien anklickten, während Korallenpolypen im Takt zum Rhythmus mit Licht pulsierten.',
|
transcript: 'Ich besuchte ein Konzert unter dem Ozean. Die Musiker waren Delfine, die Melodien anklickten, während Korallenpolypen im Takt zum Rhythmus mit Licht pulsierten.',
|
||||||
audio: '02.mp3',
|
audio: 'in_02.mp3'
|
||||||
|
|
||||||
} as AudioInput,
|
} as AudioInput,
|
||||||
ai: {
|
ai: {
|
||||||
interpretation: 'Das Unterwasserkonzert kann als Ausdruck von Kreativität und Harmonie gedeutet werden. Delfine symbolisieren Intelligenz und Spielfreude, Korallenlicht verweist auf emotionale Schwingungen und die Verbindung zum Inneren.',
|
interpretation: 'Das Unterwasserkonzert kann als Ausdruck von Kreativität und Harmonie gedeutet werden. Delfine symbolisieren Intelligenz und Spielfreude, Korallenlicht verweist auf emotionale Schwingungen und die Verbindung zum Inneren.',
|
||||||
image: '02.png',
|
image: '02.png',
|
||||||
|
audio: '02.mp3',
|
||||||
video: '02.mp4'
|
video: '02.mp4'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -64,7 +64,8 @@ export const mockDreams: Dream[] = [
|
|||||||
ai: {
|
ai: {
|
||||||
interpretation: 'Der Wüstenzug steht für eine Reise durch unbewusste Räume und persönliche Herausforderungen (Dünen). Der violette Himmel deutet auf Spiritualität hin, während die Laternen der Kamele Hoffnung und Wegweisung symbolisieren.',
|
interpretation: 'Der Wüstenzug steht für eine Reise durch unbewusste Räume und persönliche Herausforderungen (Dünen). Der violette Himmel deutet auf Spiritualität hin, während die Laternen der Kamele Hoffnung und Wegweisung symbolisieren.',
|
||||||
image: '03.png',
|
image: '03.png',
|
||||||
video: '03.mp4'
|
video: '03.mp4',
|
||||||
|
audio: '03.mp3'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -78,11 +79,12 @@ export const mockDreams: Dream[] = [
|
|||||||
input: {
|
input: {
|
||||||
inputType: 'audio',
|
inputType: 'audio',
|
||||||
transcript: 'Ich saß mit Freunden auf einer Wolke zu einer Teeparty. Jede Tasse war mit Sternenstaub gefüllt, und der Himmel um uns herum schimmerte in pastelligen Regenbogenfarben.',
|
transcript: 'Ich saß mit Freunden auf einer Wolke zu einer Teeparty. Jede Tasse war mit Sternenstaub gefüllt, und der Himmel um uns herum schimmerte in pastelligen Regenbogenfarben.',
|
||||||
audio: '04.mp3',
|
audio: 'in_04.mp3',
|
||||||
} as AudioInput,
|
} as AudioInput,
|
||||||
ai: {
|
ai: {
|
||||||
interpretation: 'Diese Szene steht für Geborgenheit und Gemeinschaft auf einer höheren Ebene. Der Sternenstaub in den Tassen symbolisiert geteilte Träume, die pastelligen Regenbögen zeigen eine optimistische Grundstimmung und Leichtigkeit.',
|
interpretation: 'Diese Szene steht für Geborgenheit und Gemeinschaft auf einer höheren Ebene. Der Sternenstaub in den Tassen symbolisiert geteilte Träume, die pastelligen Regenbögen zeigen eine optimistische Grundstimmung und Leichtigkeit.',
|
||||||
image: '04.png'
|
image: '04.png',
|
||||||
|
audio: '04.mp3',
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -190,7 +192,10 @@ export const mockDreams: Dream[] = [
|
|||||||
]
|
]
|
||||||
} as ChipInput,
|
} as ChipInput,
|
||||||
ai: {
|
ai: {
|
||||||
interpretation: 'Das neuronale Netzwerk spiegelt dein Interesse an künstlicher Intelligenz und dem menschlichen Bewusstsein wider. Die Fähigkeit, Gedanken zu formen, deutet auf deinen Wunsch nach Kontrolle über deine mentalen Prozesse hin.'
|
interpretation: 'Das neuronale Netzwerk spiegelt dein Interesse an künstlicher Intelligenz und dem menschlichen Bewusstsein wider. Die Fähigkeit, Gedanken zu formen, deutet auf deinen Wunsch nach Kontrolle über deine mentalen Prozesse hin.',
|
||||||
|
audio: '10.mp3',
|
||||||
|
image: '10.png',
|
||||||
|
video: '10.mp4'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@@ -9,14 +9,325 @@ import {formatDateNumeric, formatDateWithTime} from '../utils/DateUtils';
|
|||||||
import Slider from 'react-slick';
|
import Slider from 'react-slick';
|
||||||
import 'slick-carousel/slick/slick.css';
|
import 'slick-carousel/slick/slick.css';
|
||||||
import 'slick-carousel/slick/slick-theme.css';
|
import 'slick-carousel/slick/slick-theme.css';
|
||||||
|
import {useEffect, useRef} from 'react';
|
||||||
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
export default function DreamPage() {
|
export default function DreamPage() {
|
||||||
const {id} = useParams<{ id: string }>();
|
const {id} = useParams<{ id: string }>();
|
||||||
const navigate: NavigateFunction = useNavigate();
|
const navigate: NavigateFunction = useNavigate();
|
||||||
|
const eegChartRef = useRef<HTMLDivElement>(null);
|
||||||
|
const vitalsChartRef = useRef<HTMLDivElement>(null);
|
||||||
|
const bewegungChartRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const dream: Dream | undefined = mockDreams.find(d => d.id === Number(id));
|
const dream: Dream | undefined = mockDreams.find(d => d.id === Number(id));
|
||||||
const user: User | undefined = dream ? MockUsers.find(u => u.id === dream.userId) : undefined;
|
const user: User | undefined = dream ? MockUsers.find(u => u.id === dream.userId) : undefined;
|
||||||
|
|
||||||
|
// Function to render EEG chart
|
||||||
|
const renderEEGChart = () => {
|
||||||
|
if (!dream || dream.input.inputType !== 'chip' || !eegChartRef.current) return;
|
||||||
|
|
||||||
|
const chipInput = dream.input;
|
||||||
|
const eegData = [
|
||||||
|
{name: 'Alpha', values: chipInput.eeg.alpha},
|
||||||
|
{name: 'Beta', values: chipInput.eeg.beta},
|
||||||
|
{name: 'Theta', values: chipInput.eeg.theta},
|
||||||
|
{name: 'Delta', values: chipInput.eeg.delta}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Clear previous chart
|
||||||
|
d3.select(eegChartRef.current).selectAll('*').remove();
|
||||||
|
|
||||||
|
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||||
|
const width = eegChartRef.current.clientWidth - margin.left - margin.right;
|
||||||
|
const height = eegChartRef.current.clientHeight - margin.top - margin.bottom;
|
||||||
|
|
||||||
|
const svg = d3.select(eegChartRef.current)
|
||||||
|
.append('svg')
|
||||||
|
.attr('width', width + margin.left + margin.right)
|
||||||
|
.attr('height', height + margin.top + margin.bottom)
|
||||||
|
.append('g')
|
||||||
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||||
|
|
||||||
|
// X scale
|
||||||
|
const x = d3.scaleLinear()
|
||||||
|
.domain([0, chipInput.eeg.alpha.length - 1])
|
||||||
|
.range([0, width]);
|
||||||
|
|
||||||
|
// Y scale
|
||||||
|
const y = d3.scaleLinear()
|
||||||
|
.domain([0, d3.max(eegData.flatMap(d => d.values)) || 50])
|
||||||
|
.range([height, 0]);
|
||||||
|
|
||||||
|
// Line generator
|
||||||
|
const line = d3.line<number>()
|
||||||
|
.x((_d, i) => x(i))
|
||||||
|
.y(d => y(d))
|
||||||
|
.curve(d3.curveMonotoneX);
|
||||||
|
|
||||||
|
// Color scale
|
||||||
|
const color = d3.scaleOrdinal<string>()
|
||||||
|
.domain(eegData.map(d => d.name))
|
||||||
|
.range(['#8884d8', '#82ca9d', '#ffc658', '#ff8042']);
|
||||||
|
|
||||||
|
// Add X axis
|
||||||
|
svg.append('g')
|
||||||
|
.attr('transform', `translate(0,${height})`)
|
||||||
|
.call(d3.axisBottom(x))
|
||||||
|
.selectAll('text')
|
||||||
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
|
// Add Y axis
|
||||||
|
svg.append('g')
|
||||||
|
.call(d3.axisLeft(y))
|
||||||
|
.selectAll('text')
|
||||||
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
|
// Title removed as per requirements
|
||||||
|
|
||||||
|
// Add lines
|
||||||
|
eegData.forEach(d => {
|
||||||
|
svg.append('path')
|
||||||
|
.datum(d.values)
|
||||||
|
.attr('fill', 'none')
|
||||||
|
.attr('stroke', color(d.name))
|
||||||
|
.attr('stroke-width', 1.5)
|
||||||
|
.attr('d', line);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add legend at the top right
|
||||||
|
const legendItemHeight = 20; // Height allocated for each legend item
|
||||||
|
|
||||||
|
const legend = svg.append('g')
|
||||||
|
.attr('font-family', 'sans-serif')
|
||||||
|
.attr('font-size', 10)
|
||||||
|
.attr('text-anchor', 'end')
|
||||||
|
.selectAll('g')
|
||||||
|
.data(eegData)
|
||||||
|
.enter().append('g')
|
||||||
|
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||||
|
|
||||||
|
legend.append('rect')
|
||||||
|
.attr('x', -15)
|
||||||
|
.attr('width', 15)
|
||||||
|
.attr('height', 15)
|
||||||
|
.attr('fill', d => color(d.name));
|
||||||
|
|
||||||
|
legend.append('text')
|
||||||
|
.attr('x', -20)
|
||||||
|
.attr('y', 7.5)
|
||||||
|
.attr('dy', '0.32em')
|
||||||
|
.style('fill', 'var(--text)')
|
||||||
|
.text(d => d.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to render vitals chart
|
||||||
|
const renderVitalsChart = () => {
|
||||||
|
if (!dream || dream.input.inputType !== 'chip' || !vitalsChartRef.current) return;
|
||||||
|
|
||||||
|
const chipInput = dream.input;
|
||||||
|
const vitalsData = [
|
||||||
|
{name: 'Puls', values: chipInput.puls},
|
||||||
|
{name: 'HRV', values: chipInput.hrv}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Clear previous chart
|
||||||
|
d3.select(vitalsChartRef.current).selectAll('*').remove();
|
||||||
|
|
||||||
|
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||||
|
const width = vitalsChartRef.current.clientWidth - margin.left - margin.right;
|
||||||
|
const height = vitalsChartRef.current.clientHeight - margin.top - margin.bottom;
|
||||||
|
|
||||||
|
const svg = d3.select(vitalsChartRef.current)
|
||||||
|
.append('svg')
|
||||||
|
.attr('width', width + margin.left + margin.right)
|
||||||
|
.attr('height', height + margin.top + margin.bottom)
|
||||||
|
.append('g')
|
||||||
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||||
|
|
||||||
|
// X scale
|
||||||
|
const x = d3.scaleLinear()
|
||||||
|
.domain([0, chipInput.puls.length - 1])
|
||||||
|
.range([0, width]);
|
||||||
|
|
||||||
|
// Y scale
|
||||||
|
const minValue = d3.min(vitalsData.flatMap(d => d.values)) || 0;
|
||||||
|
const maxValue = d3.max(vitalsData.flatMap(d => d.values)) || 100;
|
||||||
|
const y = d3.scaleLinear()
|
||||||
|
.domain([Math.max(0, minValue - 10), maxValue + 10])
|
||||||
|
.range([height, 0]);
|
||||||
|
|
||||||
|
// Line generator
|
||||||
|
const line = d3.line<number>()
|
||||||
|
.x((_d, i) => x(i))
|
||||||
|
.y(d => y(d))
|
||||||
|
.curve(d3.curveMonotoneX);
|
||||||
|
|
||||||
|
// Color scale
|
||||||
|
const color = d3.scaleOrdinal<string>()
|
||||||
|
.domain(vitalsData.map(d => d.name))
|
||||||
|
.range(['#ff6b6b', '#48dbfb']);
|
||||||
|
|
||||||
|
// Add X axis
|
||||||
|
svg.append('g')
|
||||||
|
.attr('transform', `translate(0,${height})`)
|
||||||
|
.call(d3.axisBottom(x))
|
||||||
|
.selectAll('text')
|
||||||
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
|
// Add Y axis
|
||||||
|
svg.append('g')
|
||||||
|
.call(d3.axisLeft(y))
|
||||||
|
.selectAll('text')
|
||||||
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
|
// Title removed as per requirements
|
||||||
|
|
||||||
|
// Add lines
|
||||||
|
vitalsData.forEach(d => {
|
||||||
|
svg.append('path')
|
||||||
|
.datum(d.values)
|
||||||
|
.attr('fill', 'none')
|
||||||
|
.attr('stroke', color(d.name))
|
||||||
|
.attr('stroke-width', 1.5)
|
||||||
|
.attr('d', line);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add legend at the top right
|
||||||
|
const legendItemHeight = 20; // Height allocated for each legend item
|
||||||
|
|
||||||
|
const legend = svg.append('g')
|
||||||
|
.attr('font-family', 'sans-serif')
|
||||||
|
.attr('font-size', 10)
|
||||||
|
.attr('text-anchor', 'end')
|
||||||
|
.selectAll('g')
|
||||||
|
.data(vitalsData)
|
||||||
|
.enter().append('g')
|
||||||
|
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||||
|
|
||||||
|
legend.append('rect')
|
||||||
|
.attr('x', -15)
|
||||||
|
.attr('width', 15)
|
||||||
|
.attr('height', 15)
|
||||||
|
.attr('fill', d => color(d.name));
|
||||||
|
|
||||||
|
legend.append('text')
|
||||||
|
.attr('x', -20)
|
||||||
|
.attr('y', 7.5)
|
||||||
|
.attr('dy', '0.32em')
|
||||||
|
.style('fill', 'var(--text)')
|
||||||
|
.text(d => d.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to render Bewegung chart
|
||||||
|
const renderBewegungChart = () => {
|
||||||
|
if (!dream || dream.input.inputType !== 'chip' || !bewegungChartRef.current) return;
|
||||||
|
|
||||||
|
const chipInput = dream.input;
|
||||||
|
const bewegungData = [
|
||||||
|
{name: 'Bewegung', values: chipInput.bewegung.map(v => v * 100)} // Scale movement for better visibility
|
||||||
|
];
|
||||||
|
|
||||||
|
// Clear previous chart
|
||||||
|
d3.select(bewegungChartRef.current).selectAll('*').remove();
|
||||||
|
|
||||||
|
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||||
|
const width = bewegungChartRef.current.clientWidth - margin.left - margin.right;
|
||||||
|
const height = bewegungChartRef.current.clientHeight - margin.top - margin.bottom;
|
||||||
|
|
||||||
|
const svg = d3.select(bewegungChartRef.current)
|
||||||
|
.append('svg')
|
||||||
|
.attr('width', width + margin.left + margin.right)
|
||||||
|
.attr('height', height + margin.top + margin.bottom)
|
||||||
|
.append('g')
|
||||||
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||||
|
|
||||||
|
// X scale
|
||||||
|
const x = d3.scaleLinear()
|
||||||
|
.domain([0, chipInput.bewegung.length - 1])
|
||||||
|
.range([0, width]);
|
||||||
|
|
||||||
|
// Y scale
|
||||||
|
const y = d3.scaleLinear()
|
||||||
|
.domain([0, d3.max(bewegungData[0].values) || 100])
|
||||||
|
.range([height, 0]);
|
||||||
|
|
||||||
|
// Line generator
|
||||||
|
const line = d3.line<number>()
|
||||||
|
.x((_d, i) => x(i))
|
||||||
|
.y(d => y(d))
|
||||||
|
.curve(d3.curveMonotoneX);
|
||||||
|
|
||||||
|
// Color scale
|
||||||
|
const color = '#1dd1a1'; // Use the same color as before for consistency
|
||||||
|
|
||||||
|
// Add X axis
|
||||||
|
svg.append('g')
|
||||||
|
.attr('transform', `translate(0,${height})`)
|
||||||
|
.call(d3.axisBottom(x))
|
||||||
|
.selectAll('text')
|
||||||
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
|
// Add Y axis
|
||||||
|
svg.append('g')
|
||||||
|
.call(d3.axisLeft(y))
|
||||||
|
.selectAll('text')
|
||||||
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
|
// Title removed as per requirements
|
||||||
|
|
||||||
|
// Add line
|
||||||
|
svg.append('path')
|
||||||
|
.datum(bewegungData[0].values)
|
||||||
|
.attr('fill', 'none')
|
||||||
|
.attr('stroke', color)
|
||||||
|
.attr('stroke-width', 1.5)
|
||||||
|
.attr('d', line);
|
||||||
|
|
||||||
|
// Add legend at the top right
|
||||||
|
const legendItemHeight = 20; // Height allocated for each legend item
|
||||||
|
|
||||||
|
const legend = svg.append('g')
|
||||||
|
.attr('font-family', 'sans-serif')
|
||||||
|
.attr('font-size', 10)
|
||||||
|
.attr('text-anchor', 'end')
|
||||||
|
.selectAll('g')
|
||||||
|
.data(bewegungData)
|
||||||
|
.enter().append('g')
|
||||||
|
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||||
|
|
||||||
|
legend.append('rect')
|
||||||
|
.attr('x', -15)
|
||||||
|
.attr('width', 15)
|
||||||
|
.attr('height', 15)
|
||||||
|
.attr('fill', () => color);
|
||||||
|
|
||||||
|
legend.append('text')
|
||||||
|
.attr('x', -20)
|
||||||
|
.attr('y', 7.5)
|
||||||
|
.attr('dy', '0.32em')
|
||||||
|
.style('fill', 'var(--text)')
|
||||||
|
.text(d => d.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render charts when component mounts or dream changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (dream && dream.input.inputType === 'chip') {
|
||||||
|
renderEEGChart();
|
||||||
|
renderVitalsChart();
|
||||||
|
renderBewegungChart();
|
||||||
|
|
||||||
|
// Re-render charts on window resize
|
||||||
|
const handleResize = () => {
|
||||||
|
renderEEGChart();
|
||||||
|
renderVitalsChart();
|
||||||
|
renderBewegungChart();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}
|
||||||
|
}, [dream, renderEEGChart, renderVitalsChart, renderBewegungChart]);
|
||||||
|
|
||||||
if (!dream) {
|
if (!dream) {
|
||||||
return (<div className="page p-4">
|
return (<div className="page p-4">
|
||||||
<button
|
<button
|
||||||
@@ -64,17 +375,61 @@ export default function DreamPage() {
|
|||||||
<div className="flex items-center mb-4">
|
<div className="flex items-center mb-4">
|
||||||
<span className="font-medium dreamy-text">Traum-Beschreibung</span>
|
<span className="font-medium dreamy-text">Traum-Beschreibung</span>
|
||||||
</div>
|
</div>
|
||||||
{(dream.input.inputType === 'image' || dream.input.inputType === 'audio') && (
|
|
||||||
<div className="flex justify-center mb-1">
|
{dream.input.inputType === 'image' && (
|
||||||
{dream.input.inputType === 'audio' && (<audio></audio>)}
|
<div className="flex flex-col sm:flex-row gap-4 mb-4">
|
||||||
{dream.input.inputType === 'image' && (<img alt={dream.input.imgAlt}></img>)}
|
<div className="sm:w-1/3">
|
||||||
</div>)}
|
<img
|
||||||
<p className="leading-relaxed text-base sm:text-lg">
|
src={`/assets/dreams/images/${dream.input.img}`}
|
||||||
{(dream.input.inputType === 'text' && dream.input.input)
|
alt={dream.input.imgAlt}
|
||||||
|| (dream.input.inputType === 'audio' && dream.input.transcript)
|
className="w-full rounded-lg shadow-lg object-cover"
|
||||||
|| (dream.input.inputType === 'image' && dream.input.description)
|
/>
|
||||||
|| (dream.input.inputType === 'chip' && dream.input.text)}
|
</div>
|
||||||
</p>
|
<div className="sm:w-2/3">
|
||||||
|
<p className="leading-relaxed text-base sm:text-lg">
|
||||||
|
{dream.input.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{dream.input.inputType === 'audio' && (
|
||||||
|
<div className="flex flex-col gap-4 mb-4">
|
||||||
|
<div className="w-full">
|
||||||
|
<audio
|
||||||
|
controls
|
||||||
|
src={`/assets/dreams/audio/${dream.input.audio}`}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Ihr Browser unterstützt das Audio-Element nicht.
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<p className="leading-relaxed text-base sm:text-lg">
|
||||||
|
{dream.input.transcript}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{dream.input.inputType === 'text' && (
|
||||||
|
<p className="leading-relaxed text-base sm:text-lg">
|
||||||
|
{dream.input.input}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{dream.input.inputType === 'chip' && (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<p className="leading-relaxed text-base sm:text-lg">
|
||||||
|
{dream.input.text}
|
||||||
|
</p>
|
||||||
|
<div ref={eegChartRef} className="w-full h-64 mt-4 border border-gray-200 rounded-lg p-2"></div>
|
||||||
|
<div ref={vitalsChartRef}
|
||||||
|
className="w-full h-64 mt-4 border border-gray-200 rounded-lg p-2"></div>
|
||||||
|
<div ref={bewegungChartRef}
|
||||||
|
className="w-full h-64 mt-4 border border-gray-200 rounded-lg p-2"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{dream.ai?.interpretation && dream.ai.interpretation !== '' && (<div
|
{dream.ai?.interpretation && dream.ai.interpretation !== '' && (<div
|
||||||
|
Reference in New Issue
Block a user