teamsbot ux fixes
This commit is contained in:
parent
0d8e6501d3
commit
230055a4fb
3 changed files with 268 additions and 38 deletions
|
|
@ -921,6 +921,68 @@
|
||||||
color: var(--text-primary, #333);
|
color: var(--text-primary, #333);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.responseText h1,
|
||||||
|
.responseText h2,
|
||||||
|
.responseText h3 {
|
||||||
|
margin: 0.6em 0 0.3em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responseText p {
|
||||||
|
margin: 0.3em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responseText ul,
|
||||||
|
.responseText ol {
|
||||||
|
margin: 0.3em 0;
|
||||||
|
padding-left: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responseText code {
|
||||||
|
background: var(--bg-tertiary, #f0f0f0);
|
||||||
|
padding: 0.1em 0.3em;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responseText pre {
|
||||||
|
background: var(--bg-tertiary, #f0f0f0);
|
||||||
|
padding: 0.6em;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 0.4em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responseText pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responseText table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 0.4em 0;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responseText th,
|
||||||
|
.responseText td {
|
||||||
|
border: 1px solid var(--border-color, #ddd);
|
||||||
|
padding: 0.3em 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responseText th {
|
||||||
|
background: var(--bg-secondary, #f5f5f5);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responseText blockquote {
|
||||||
|
border-left: 3px solid var(--border-color, #ddd);
|
||||||
|
margin: 0.4em 0;
|
||||||
|
padding: 0.2em 0.8em;
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
.responseReasoning {
|
.responseReasoning {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
|
@ -948,9 +1010,18 @@
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: var(--text-primary, #333);
|
color: var(--text-primary, #333);
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.summaryText p { margin: 0.3em 0; }
|
||||||
|
.summaryText ul, .summaryText ol { margin: 0.3em 0; padding-left: 1.4em; }
|
||||||
|
.summaryText h1, .summaryText h2, .summaryText h3 { margin: 0.6em 0 0.3em; font-size: 1em; font-weight: 600; }
|
||||||
|
.summaryText code { background: var(--bg-tertiary, #f0f0f0); padding: 0.1em 0.3em; border-radius: 3px; font-size: 0.85em; }
|
||||||
|
.summaryText pre { background: var(--bg-tertiary, #f0f0f0); padding: 0.6em; border-radius: 4px; overflow-x: auto; }
|
||||||
|
.summaryText pre code { background: none; padding: 0; }
|
||||||
|
.summaryText table { border-collapse: collapse; margin: 0.4em 0; font-size: 0.85em; }
|
||||||
|
.summaryText th, .summaryText td { border: 1px solid var(--border-color, #ddd); padding: 0.3em 0.6em; }
|
||||||
|
.summaryText th { background: var(--bg-secondary, #f5f5f5); font-weight: 600; }
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
Settings View
|
Settings View
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|
@ -1354,6 +1425,36 @@
|
||||||
animation: agentPulse 1s ease-in-out infinite;
|
animation: agentPulse 1s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agentProgressLog {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agentProgressEntry {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.15rem 0;
|
||||||
|
border-bottom: 1px solid var(--border-color, #eee);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agentProgressEntry:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agentProgressTime {
|
||||||
|
color: var(--text-tertiary, #999);
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agentProgressText {
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.statsCards {
|
.statsCards {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
@ -1543,16 +1644,49 @@
|
||||||
border-top: 1px solid var(--border-color, #e0e0e0);
|
border-top: 1px solid var(--border-color, #e0e0e0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sessionRow {
|
.sessionTable {
|
||||||
display: flex;
|
border-collapse: collapse;
|
||||||
gap: 1rem;
|
font-size: 0.85rem;
|
||||||
padding: 0.4rem 0;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sessionRow:hover {
|
.sessionTable th {
|
||||||
color: var(--primary-color, #4A90D9);
|
text-align: left;
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
border-bottom: 1px solid var(--border-color, #ddd);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sessionTableRow {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sessionTableRow td {
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
border-bottom: 1px solid var(--border-color, #eee);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sessionTableRow:hover td {
|
||||||
|
background: rgba(74, 144, 217, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sessionDeleteBtn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #b91c1c;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.1rem 0.35rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sessionDeleteBtn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background: rgba(185, 28, 28, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sessionStatus {
|
.sessionStatus {
|
||||||
|
|
|
||||||
|
|
@ -188,16 +188,34 @@ export const TeamsbotModulesView: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const _formatSessionDate = (startedAt?: string | number) => {
|
const _formatSessionDateTime = (ts?: string | number): string => {
|
||||||
if (startedAt == null) return '-';
|
if (ts == null) return '-';
|
||||||
if (typeof startedAt === 'number') {
|
const ms = typeof ts === 'number' ? ts * 1000 : Date.parse(String(ts));
|
||||||
return new Date(startedAt * 1000).toLocaleDateString('de-CH');
|
if (Number.isNaN(ms)) return '-';
|
||||||
}
|
const d = new Date(ms);
|
||||||
const ms = Date.parse(String(startedAt));
|
const pad = (n: number) => String(n).padStart(2, '0');
|
||||||
if (!Number.isNaN(ms)) return new Date(ms).toLocaleDateString('de-CH');
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||||
return '-';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _calcDurationMin = (startedAt?: string | number, endedAt?: string | number): string => {
|
||||||
|
if (!startedAt) return '-';
|
||||||
|
const startMs = typeof startedAt === 'number' ? startedAt * 1000 : Date.parse(String(startedAt));
|
||||||
|
if (Number.isNaN(startMs)) return '-';
|
||||||
|
const endMs = endedAt
|
||||||
|
? (typeof endedAt === 'number' ? endedAt * 1000 : Date.parse(String(endedAt)))
|
||||||
|
: Date.now();
|
||||||
|
if (Number.isNaN(endMs)) return '-';
|
||||||
|
const mins = Math.round((endMs - startMs) / 60000);
|
||||||
|
return `${mins} min`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _sortedSessions = (sessions: TeamsbotSession[]): TeamsbotSession[] =>
|
||||||
|
[...sessions].sort((a, b) => {
|
||||||
|
const ta = a.startedAt ? (typeof a.startedAt === 'number' ? a.startedAt * 1000 : Date.parse(String(a.startedAt))) : 0;
|
||||||
|
const tb = b.startedAt ? (typeof b.startedAt === 'number' ? b.startedAt * 1000 : Date.parse(String(b.startedAt))) : 0;
|
||||||
|
return tb - ta;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.modulesContainer}>
|
<div className={styles.modulesContainer}>
|
||||||
<div className={styles.modulesHeader}>
|
<div className={styles.modulesHeader}>
|
||||||
|
|
@ -248,17 +266,50 @@ export const TeamsbotModulesView: React.FC = () => {
|
||||||
{(moduleSessions[mod.id] || []).length === 0 ? (
|
{(moduleSessions[mod.id] || []).length === 0 ? (
|
||||||
<p className={styles.noSessions}>{t('Keine Sitzungen')}</p>
|
<p className={styles.noSessions}>{t('Keine Sitzungen')}</p>
|
||||||
) : (
|
) : (
|
||||||
(moduleSessions[mod.id] || []).map((sess) => (
|
<table className={styles.sessionTable}>
|
||||||
<div
|
<thead>
|
||||||
key={sess.id}
|
<tr>
|
||||||
className={styles.sessionRow}
|
<th style={{ width: 32 }}></th>
|
||||||
onClick={() => navigate(`/mandates/${mandateId}/teamsbot/${instanceId}/sessions?sessionId=${sess.id}`)}
|
<th>{t('Datum')}</th>
|
||||||
>
|
<th>{t('Dauer')}</th>
|
||||||
<span>{sess.botName || 'Bot'}</span>
|
<th>{t('Status')}</th>
|
||||||
<span className={styles.sessionStatus}>{sess.status}</span>
|
</tr>
|
||||||
<span>{_formatSessionDate(sess.startedAt)}</span>
|
</thead>
|
||||||
</div>
|
<tbody>
|
||||||
))
|
{_sortedSessions(moduleSessions[mod.id] || []).map((sess) => (
|
||||||
|
<tr
|
||||||
|
key={sess.id}
|
||||||
|
className={styles.sessionTableRow}
|
||||||
|
onClick={() => navigate(`/mandates/${mandateId}/teamsbot/${instanceId}/sessions?sessionId=${sess.id}`)}
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.sessionDeleteBtn}
|
||||||
|
title={t('Sitzung loeschen')}
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
try {
|
||||||
|
await teamsbotApi.deleteSession(instanceId, sess.id);
|
||||||
|
setModuleSessions((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[mod.id]: (prev[mod.id] || []).filter((s) => s.id !== sess.id),
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Delete session failed:', err);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td>{_formatSessionDateTime(sess.startedAt)}</td>
|
||||||
|
<td>{_calcDurationMin(sess.startedAt, sess.endedAt)}</td>
|
||||||
|
<td><span className={styles.sessionStatus}>{sess.status}</span></td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import { useFileContext } from '../../../contexts/FileContext';
|
||||||
import styles from './Teamsbot.module.css';
|
import styles from './Teamsbot.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import remarkGfm from 'remark-gfm';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TeamsbotSessionView - Live session view with real-time transcript and bot responses.
|
* TeamsbotSessionView - Live session view with real-time transcript and bot responses.
|
||||||
|
|
@ -61,6 +63,8 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
|
|
||||||
const [agentStatus, setAgentStatus] = useState<{ toolName?: string; status?: string; reason?: string } | null>(null);
|
const [agentStatus, setAgentStatus] = useState<{ toolName?: string; status?: string; reason?: string } | null>(null);
|
||||||
const agentStatusTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const agentStatusTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const [agentProgressLog, setAgentProgressLog] = useState<Array<{ id: number; text: string; ts: string }>>([]);
|
||||||
|
const agentProgressIdRef = useRef(0);
|
||||||
const [sessionStats, setSessionStats] = useState<any>(null);
|
const [sessionStats, setSessionStats] = useState<any>(null);
|
||||||
const [reconnectTick, setReconnectTick] = useState(0);
|
const [reconnectTick, setReconnectTick] = useState(0);
|
||||||
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
@ -311,6 +315,22 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
setAgentStatus({ toolName: data.toolName, status: data.status, reason: data.reason });
|
setAgentStatus({ toolName: data.toolName, status: data.status, reason: data.reason });
|
||||||
if (agentStatusTimerRef.current) clearTimeout(agentStatusTimerRef.current);
|
if (agentStatusTimerRef.current) clearTimeout(agentStatusTimerRef.current);
|
||||||
agentStatusTimerRef.current = setTimeout(() => setAgentStatus(null), 15000);
|
agentStatusTimerRef.current = setTimeout(() => setAgentStatus(null), 15000);
|
||||||
|
} else if (data.status === 'toolCall') {
|
||||||
|
const label = data.displayLabel || data.toolName || '';
|
||||||
|
setAgentStatus({ toolName: label, status: 'running' });
|
||||||
|
if (agentStatusTimerRef.current) clearTimeout(agentStatusTimerRef.current);
|
||||||
|
agentStatusTimerRef.current = setTimeout(() => setAgentStatus(null), 15000);
|
||||||
|
agentProgressIdRef.current += 1;
|
||||||
|
const entry = { id: agentProgressIdRef.current, text: `${label}`, ts: new Date().toLocaleTimeString() };
|
||||||
|
setAgentProgressLog((prev) => [...prev.slice(-19), entry]);
|
||||||
|
} else if (data.status === 'toolResult') {
|
||||||
|
const summary = data.summary ? `: ${data.summary.substring(0, 120)}` : '';
|
||||||
|
agentProgressIdRef.current += 1;
|
||||||
|
const entry = { id: agentProgressIdRef.current, text: `${data.toolName || ''}${summary}`, ts: new Date().toLocaleTimeString() };
|
||||||
|
setAgentProgressLog((prev) => [...prev.slice(-19), entry]);
|
||||||
|
} else if (data.status === 'completed') {
|
||||||
|
if (agentStatusTimerRef.current) clearTimeout(agentStatusTimerRef.current);
|
||||||
|
setAgentStatus(null);
|
||||||
} else {
|
} else {
|
||||||
if (agentStatusTimerRef.current) clearTimeout(agentStatusTimerRef.current);
|
if (agentStatusTimerRef.current) clearTimeout(agentStatusTimerRef.current);
|
||||||
agentStatusTimerRef.current = setTimeout(() => setAgentStatus(null), 2000);
|
agentStatusTimerRef.current = setTimeout(() => setAgentStatus(null), 2000);
|
||||||
|
|
@ -807,14 +827,13 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Agent Status Bubble (F-fix-2) */}
|
{/* Agent Status Bubble + Progress Log */}
|
||||||
{agentStatus && (
|
{agentStatus && (
|
||||||
<div className={styles.agentStatusBubble}>
|
<div className={styles.agentStatusBubble}>
|
||||||
<span className={styles.agentStatusDot} />
|
<span className={styles.agentStatusDot} />
|
||||||
<span>{t('Agent denkt nach')}{agentStatus.toolName ? `: ${agentStatus.toolName}` : '...'}</span>
|
<span>{t('Agent denkt nach')}{agentStatus.toolName ? `: ${agentStatus.toolName}` : '...'}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Stats Cards (F-fix-3) */}
|
{/* Stats Cards (F-fix-3) */}
|
||||||
{sessionStats && (
|
{sessionStats && (
|
||||||
<div className={styles.statsCards}>
|
<div className={styles.statsCards}>
|
||||||
|
|
@ -1093,7 +1112,8 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
<div className={styles.directorHistoryText}>{p.text}</div>
|
<div className={styles.directorHistoryText}>{p.text}</div>
|
||||||
{p.responseText && (
|
{p.responseText && (
|
||||||
<div className={styles.directorHistoryText} style={{ opacity: 0.85 }}>
|
<div className={styles.directorHistoryText} style={{ opacity: 0.85 }}>
|
||||||
<em>{t('Antwort')}:</em> {p.responseText}
|
<em>{t('Antwort')}:</em>
|
||||||
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{p.responseText}</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{p.statusMessage && p.status === 'failed' && (
|
{p.statusMessage && p.status === 'failed' && (
|
||||||
|
|
@ -1145,7 +1165,9 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
<span className={styles.responseIntent}>{r.detectedIntent}</span>
|
<span className={styles.responseIntent}>{r.detectedIntent}</span>
|
||||||
<span className={styles.responseTime}>{_formatTime(r.timestamp || '')}</span>
|
<span className={styles.responseTime}>{_formatTime(r.timestamp || '')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.responseText}>{r.responseText}</div>
|
<div className={styles.responseText}>
|
||||||
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{r.responseText || ''}</ReactMarkdown>
|
||||||
|
</div>
|
||||||
{r.reasoning && (
|
{r.reasoning && (
|
||||||
<div className={styles.responseReasoning}>
|
<div className={styles.responseReasoning}>
|
||||||
<em>{t('Begründung: {text}', { text: r.reasoning })}</em>
|
<em>{t('Begründung: {text}', { text: r.reasoning })}</em>
|
||||||
|
|
@ -1171,17 +1193,40 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
{session.summary && (
|
{session.summary && (
|
||||||
<div className={styles.summaryCard}>
|
<div className={styles.summaryCard}>
|
||||||
<h4 className={styles.panelTitle}>{t('Meeting-Zusammenfassung')}</h4>
|
<h4 className={styles.panelTitle}>{t('Meeting-Zusammenfassung')}</h4>
|
||||||
<div className={styles.summaryText}>{session.summary}</div>
|
<div className={styles.summaryText}>
|
||||||
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{session.summary || ''}</ReactMarkdown>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* TTS Delivery Debug */}
|
{/* Agent Progress Log (collapsed by default) */}
|
||||||
<div className={styles.summaryCard}>
|
<details className={styles.summaryCard}>
|
||||||
<h4 className={styles.panelTitle}>{t('TTS-Lieferstatus')}</h4>
|
<summary className={styles.panelTitle} style={{ cursor: 'pointer' }}>
|
||||||
|
{t('Agent-Fortschritt')} ({agentProgressLog.length})
|
||||||
|
</summary>
|
||||||
|
{agentProgressLog.length === 0 ? (
|
||||||
|
<div className={styles.emptyState}>{t('Noch keine Agent-Aktivitaet')}</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.agentProgressLog}>
|
||||||
|
{agentProgressLog.map((entry) => (
|
||||||
|
<div key={entry.id} className={styles.agentProgressEntry}>
|
||||||
|
<span className={styles.agentProgressTime}>{entry.ts}</span>
|
||||||
|
<span className={styles.agentProgressText}>{entry.text}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</details>
|
||||||
|
|
||||||
|
{/* TTS Delivery Debug (collapsed by default) */}
|
||||||
|
<details className={styles.summaryCard}>
|
||||||
|
<summary className={styles.panelTitle} style={{ cursor: 'pointer' }}>
|
||||||
|
{t('TTS-Lieferstatus')} ({ttsStatusEvents.length})
|
||||||
|
</summary>
|
||||||
{ttsStatusEvents.length === 0 ? (
|
{ttsStatusEvents.length === 0 ? (
|
||||||
<div className={styles.emptyState}>{t('Noch keine TTS-Events')}</div>
|
<div className={styles.emptyState}>{t('Noch keine TTS-Events')}</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', padding: '0.5rem 0' }}>
|
||||||
{ttsStatusEvents.slice(-10).reverse().map((ev, idx) => (
|
{ttsStatusEvents.slice(-10).reverse().map((ev, idx) => (
|
||||||
<div key={`${ev.timestamp}-${idx}`} className={styles.responseMeta}>
|
<div key={`${ev.timestamp}-${idx}`} className={styles.responseMeta}>
|
||||||
<span>{_formatTime(ev.timestamp)}</span>
|
<span>{_formatTime(ev.timestamp)}</span>
|
||||||
|
|
@ -1192,7 +1237,7 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</details>
|
||||||
|
|
||||||
{/* Debug Log (SSE/Transcript/Chat) */}
|
{/* Debug Log (SSE/Transcript/Chat) */}
|
||||||
<div style={{ position: 'fixed', bottom: 0, right: 0, zIndex: 9999 }}>
|
<div style={{ position: 'fixed', bottom: 0, right: 0, zIndex: 9999 }}>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue