diff --git a/src/pages/views/teamsbot/Teamsbot.module.css b/src/pages/views/teamsbot/Teamsbot.module.css index eb06248..418b3a1 100644 --- a/src/pages/views/teamsbot/Teamsbot.module.css +++ b/src/pages/views/teamsbot/Teamsbot.module.css @@ -921,6 +921,68 @@ 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 { margin-top: 0.5rem; font-size: 0.8rem; @@ -948,9 +1010,18 @@ font-size: 0.9rem; line-height: 1.6; 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 ============================================================================ */ @@ -1354,6 +1425,36 @@ 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 { display: flex; gap: 1rem; @@ -1543,16 +1644,49 @@ border-top: 1px solid var(--border-color, #e0e0e0); } -.sessionRow { - display: flex; - gap: 1rem; - padding: 0.4rem 0; - cursor: pointer; - font-size: 0.9rem; +.sessionTable { + border-collapse: collapse; + font-size: 0.85rem; } -.sessionRow:hover { - color: var(--primary-color, #4A90D9); +.sessionTable th { + 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 { diff --git a/src/pages/views/teamsbot/TeamsbotModulesView.tsx b/src/pages/views/teamsbot/TeamsbotModulesView.tsx index 9e64240..7a9db37 100644 --- a/src/pages/views/teamsbot/TeamsbotModulesView.tsx +++ b/src/pages/views/teamsbot/TeamsbotModulesView.tsx @@ -188,16 +188,34 @@ export const TeamsbotModulesView: React.FC = () => { } }; - const _formatSessionDate = (startedAt?: string | number) => { - if (startedAt == null) return '-'; - if (typeof startedAt === 'number') { - return new Date(startedAt * 1000).toLocaleDateString('de-CH'); - } - const ms = Date.parse(String(startedAt)); - if (!Number.isNaN(ms)) return new Date(ms).toLocaleDateString('de-CH'); - return '-'; + const _formatSessionDateTime = (ts?: string | number): string => { + if (ts == null) return '-'; + const ms = typeof ts === 'number' ? ts * 1000 : Date.parse(String(ts)); + if (Number.isNaN(ms)) return '-'; + const d = new Date(ms); + const pad = (n: number) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; }; + 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 (
@@ -248,17 +266,50 @@ export const TeamsbotModulesView: React.FC = () => { {(moduleSessions[mod.id] || []).length === 0 ? (

{t('Keine Sitzungen')}

) : ( - (moduleSessions[mod.id] || []).map((sess) => ( -
navigate(`/mandates/${mandateId}/teamsbot/${instanceId}/sessions?sessionId=${sess.id}`)} - > - {sess.botName || 'Bot'} - {sess.status} - {_formatSessionDate(sess.startedAt)} -
- )) + + + + + + + + + + + {_sortedSessions(moduleSessions[mod.id] || []).map((sess) => ( + navigate(`/mandates/${mandateId}/teamsbot/${instanceId}/sessions?sessionId=${sess.id}`)} + > + + + + + + ))} + +
{t('Datum')}{t('Dauer')}{t('Status')}
+ + {_formatSessionDateTime(sess.startedAt)}{_calcDurationMin(sess.startedAt, sess.endedAt)}{sess.status}
)}
)} diff --git a/src/pages/views/teamsbot/TeamsbotSessionView.tsx b/src/pages/views/teamsbot/TeamsbotSessionView.tsx index 5de3056..e1b382f 100644 --- a/src/pages/views/teamsbot/TeamsbotSessionView.tsx +++ b/src/pages/views/teamsbot/TeamsbotSessionView.tsx @@ -23,6 +23,8 @@ import { useFileContext } from '../../../contexts/FileContext'; import styles from './Teamsbot.module.css'; 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. @@ -61,6 +63,8 @@ export const TeamsbotSessionView: React.FC = () => { const [agentStatus, setAgentStatus] = useState<{ toolName?: string; status?: string; reason?: string } | null>(null); const agentStatusTimerRef = useRef | null>(null); + const [agentProgressLog, setAgentProgressLog] = useState>([]); + const agentProgressIdRef = useRef(0); const [sessionStats, setSessionStats] = useState(null); const [reconnectTick, setReconnectTick] = useState(0); const reconnectTimerRef = useRef | null>(null); @@ -311,6 +315,22 @@ export const TeamsbotSessionView: React.FC = () => { setAgentStatus({ toolName: data.toolName, status: data.status, reason: data.reason }); if (agentStatusTimerRef.current) clearTimeout(agentStatusTimerRef.current); 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 { if (agentStatusTimerRef.current) clearTimeout(agentStatusTimerRef.current); agentStatusTimerRef.current = setTimeout(() => setAgentStatus(null), 2000); @@ -807,14 +827,13 @@ export const TeamsbotSessionView: React.FC = () => {
)} - {/* Agent Status Bubble (F-fix-2) */} + {/* Agent Status Bubble + Progress Log */} {agentStatus && (
{t('Agent denkt nach')}{agentStatus.toolName ? `: ${agentStatus.toolName}` : '...'}
)} - {/* Stats Cards (F-fix-3) */} {sessionStats && (
@@ -1093,7 +1112,8 @@ export const TeamsbotSessionView: React.FC = () => {
{p.text}
{p.responseText && (
- {t('Antwort')}: {p.responseText} + {t('Antwort')}: + {p.responseText}
)} {p.statusMessage && p.status === 'failed' && ( @@ -1145,7 +1165,9 @@ export const TeamsbotSessionView: React.FC = () => { {r.detectedIntent} {_formatTime(r.timestamp || '')}
-
{r.responseText}
+
+ {r.responseText || ''} +
{r.reasoning && (
{t('Begründung: {text}', { text: r.reasoning })} @@ -1171,17 +1193,40 @@ export const TeamsbotSessionView: React.FC = () => { {session.summary && (

{t('Meeting-Zusammenfassung')}

-
{session.summary}
+
+ {session.summary || ''} +
)} - {/* TTS Delivery Debug */} -
-

{t('TTS-Lieferstatus')}

+ {/* Agent Progress Log (collapsed by default) */} +
+ + {t('Agent-Fortschritt')} ({agentProgressLog.length}) + + {agentProgressLog.length === 0 ? ( +
{t('Noch keine Agent-Aktivitaet')}
+ ) : ( +
+ {agentProgressLog.map((entry) => ( +
+ {entry.ts} + {entry.text} +
+ ))} +
+ )} +
+ + {/* TTS Delivery Debug (collapsed by default) */} +
+ + {t('TTS-Lieferstatus')} ({ttsStatusEvents.length}) + {ttsStatusEvents.length === 0 ? (
{t('Noch keine TTS-Events')}
) : ( -
+
{ttsStatusEvents.slice(-10).reverse().map((ev, idx) => (
{_formatTime(ev.timestamp)} @@ -1192,7 +1237,7 @@ export const TeamsbotSessionView: React.FC = () => { ))}
)} -
+
{/* Debug Log (SSE/Transcript/Chat) */}