228 lines
8.2 KiB
TypeScript
228 lines
8.2 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
|
|
import * as teamsbotApi from '../../../api/teamsbotApi';
|
|
import type { TeamsbotSession, StartSessionRequest } from '../../../api/teamsbotApi';
|
|
import styles from './Teamsbot.module.css';
|
|
|
|
/**
|
|
* TeamsbotDashboardView - Overview of all Teams Bot sessions.
|
|
* Allows starting new sessions and viewing active/past sessions.
|
|
*/
|
|
export const TeamsbotDashboardView: React.FC = () => {
|
|
const { instance } = useCurrentInstance();
|
|
const instanceId = instance?.id || '';
|
|
|
|
const [sessions, setSessions] = useState<TeamsbotSession[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// New session form
|
|
const [meetingLink, setMeetingLink] = useState('');
|
|
const [botName, setBotName] = useState('');
|
|
const [isStarting, setIsStarting] = useState(false);
|
|
|
|
const _loadSessions = useCallback(async () => {
|
|
if (!instanceId) return;
|
|
try {
|
|
setLoading(true);
|
|
const result = await teamsbotApi.listSessions(instanceId);
|
|
setSessions(result.sessions || []);
|
|
setError(null);
|
|
} catch (err: any) {
|
|
setError(err.message || 'Fehler beim Laden der Sitzungen');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [instanceId]);
|
|
|
|
useEffect(() => {
|
|
_loadSessions();
|
|
}, [_loadSessions]);
|
|
|
|
const _handleStartSession = async () => {
|
|
if (!meetingLink.trim()) return;
|
|
|
|
setIsStarting(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const request: StartSessionRequest = {
|
|
meetingLink: meetingLink.trim(),
|
|
botName: botName.trim() || undefined,
|
|
};
|
|
|
|
await teamsbotApi.startSession(instanceId, request);
|
|
setMeetingLink('');
|
|
setBotName('');
|
|
await _loadSessions();
|
|
} catch (err: any) {
|
|
setError(err.message || 'Fehler beim Starten der Sitzung');
|
|
} finally {
|
|
setIsStarting(false);
|
|
}
|
|
};
|
|
|
|
const _handleStopSession = async (sessionId: string) => {
|
|
try {
|
|
await teamsbotApi.stopSession(instanceId, sessionId);
|
|
await _loadSessions();
|
|
} catch (err: any) {
|
|
setError(err.message || 'Fehler beim Stoppen der Sitzung');
|
|
}
|
|
};
|
|
|
|
const _handleDeleteSession = async (sessionId: string) => {
|
|
try {
|
|
await teamsbotApi.deleteSession(instanceId, sessionId);
|
|
await _loadSessions();
|
|
} catch (err: any) {
|
|
setError(err.message || 'Fehler beim Loeschen der Sitzung');
|
|
}
|
|
};
|
|
|
|
const _getStatusBadgeClass = (status: string) => {
|
|
switch (status) {
|
|
case 'active': return styles.statusActive;
|
|
case 'joining': return styles.statusJoining;
|
|
case 'pending': return styles.statusPending;
|
|
case 'ended': return styles.statusEnded;
|
|
case 'error': return styles.statusError;
|
|
case 'leaving': return styles.statusLeaving;
|
|
default: return '';
|
|
}
|
|
};
|
|
|
|
const _getStatusLabel = (status: string) => {
|
|
const labels: Record<string, string> = {
|
|
pending: 'Wartend',
|
|
joining: 'Beitritt...',
|
|
active: 'Aktiv',
|
|
leaving: 'Verlassen...',
|
|
ended: 'Beendet',
|
|
error: 'Fehler',
|
|
};
|
|
return labels[status] || status;
|
|
};
|
|
|
|
const activeSessions = sessions.filter(s => ['pending', 'joining', 'active'].includes(s.status));
|
|
const pastSessions = sessions.filter(s => ['ended', 'error', 'leaving'].includes(s.status));
|
|
|
|
return (
|
|
<div className={styles.dashboardContainer}>
|
|
{/* Start New Session Card */}
|
|
<div className={styles.startSessionCard}>
|
|
<h3 className={styles.cardTitle}>Neue Bot-Sitzung starten</h3>
|
|
<p className={styles.cardDescription}>
|
|
Fuege den Teams Meeting-Link ein, um den AI-Bot in ein Meeting einzuschleusen.
|
|
</p>
|
|
|
|
<div className={styles.formGroup}>
|
|
<label className={styles.label}>Teams Meeting-Link *</label>
|
|
<input
|
|
type="url"
|
|
className={styles.input}
|
|
placeholder="https://teams.microsoft.com/l/meetup-join/..."
|
|
value={meetingLink}
|
|
onChange={(e) => setMeetingLink(e.target.value)}
|
|
disabled={isStarting}
|
|
/>
|
|
</div>
|
|
|
|
<div className={styles.formGroup}>
|
|
<label className={styles.label}>Bot-Name (optional)</label>
|
|
<input
|
|
type="text"
|
|
className={styles.input}
|
|
placeholder="AI Assistant"
|
|
value={botName}
|
|
onChange={(e) => setBotName(e.target.value)}
|
|
disabled={isStarting}
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
className={styles.startButton}
|
|
onClick={_handleStartSession}
|
|
disabled={isStarting || !meetingLink.trim()}
|
|
>
|
|
{isStarting ? 'Wird gestartet...' : 'Bot ins Meeting senden'}
|
|
</button>
|
|
</div>
|
|
|
|
{error && <div className={styles.errorBanner}>{error}</div>}
|
|
|
|
{/* Active Sessions */}
|
|
{activeSessions.length > 0 && (
|
|
<div className={styles.sectionContainer}>
|
|
<h3 className={styles.sectionTitle}>Aktive Sitzungen</h3>
|
|
<div className={styles.sessionList}>
|
|
{activeSessions.map((session) => (
|
|
<div key={session.id} className={styles.sessionCard}>
|
|
<div className={styles.sessionHeader}>
|
|
<span className={styles.sessionBotName}>{session.botName}</span>
|
|
<span className={`${styles.statusBadge} ${_getStatusBadgeClass(session.status)}`}>
|
|
{_getStatusLabel(session.status)}
|
|
</span>
|
|
</div>
|
|
<div className={styles.sessionMeta}>
|
|
<span>{session.transcriptSegmentCount} Segmente</span>
|
|
<span>{session.botResponseCount} Antworten</span>
|
|
{session.startedAt && <span>Seit: {new Date(session.startedAt).toLocaleTimeString('de-CH')}</span>}
|
|
</div>
|
|
<div className={styles.sessionActions}>
|
|
<a href={`sessions?sessionId=${session.id}`} className={styles.viewButton}>Live ansehen</a>
|
|
{session.status === 'active' && (
|
|
<button className={styles.stopButton} onClick={() => _handleStopSession(session.id)}>
|
|
Stoppen
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Past Sessions */}
|
|
<div className={styles.sectionContainer}>
|
|
<h3 className={styles.sectionTitle}>
|
|
{loading ? 'Lade Sitzungen...' : `Vergangene Sitzungen (${pastSessions.length})`}
|
|
</h3>
|
|
{pastSessions.length === 0 && !loading && (
|
|
<p className={styles.emptyState}>Noch keine vergangenen Sitzungen.</p>
|
|
)}
|
|
<div className={styles.sessionList}>
|
|
{pastSessions.map((session) => (
|
|
<div key={session.id} className={styles.sessionCard}>
|
|
<div className={styles.sessionHeader}>
|
|
<span className={styles.sessionBotName}>{session.botName}</span>
|
|
<span className={`${styles.statusBadge} ${_getStatusBadgeClass(session.status)}`}>
|
|
{_getStatusLabel(session.status)}
|
|
</span>
|
|
</div>
|
|
<div className={styles.sessionMeta}>
|
|
<span>{session.transcriptSegmentCount} Segmente</span>
|
|
<span>{session.botResponseCount} Antworten</span>
|
|
{session.startedAt && <span>{new Date(session.startedAt).toLocaleDateString('de-CH')}</span>}
|
|
</div>
|
|
{session.summary && (
|
|
<div className={styles.sessionSummary}>{session.summary.substring(0, 200)}...</div>
|
|
)}
|
|
{session.errorMessage && (
|
|
<div className={styles.sessionError}>{session.errorMessage}</div>
|
|
)}
|
|
<div className={styles.sessionActions}>
|
|
<a href={`sessions?sessionId=${session.id}`} className={styles.viewButton}>Details</a>
|
|
<button className={styles.deleteButton} onClick={() => _handleDeleteSession(session.id)}>
|
|
Loeschen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default TeamsbotDashboardView;
|