fixed teamsbot issues
This commit is contained in:
parent
544f36460a
commit
791d575b7d
3 changed files with 75 additions and 142 deletions
|
|
@ -413,15 +413,12 @@
|
|||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* ----- Session Layout (UDB Sidebar + Main) ------------------------------- */
|
||||
|
||||
.sessionLayout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
|
@ -430,7 +427,6 @@
|
|||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
|
@ -820,8 +816,6 @@
|
|||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Transcript Panel */
|
||||
|
|
@ -833,6 +827,8 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
height: 50vh;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.panelTitle {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@ import type { TeamsbotSession, MeetingModule } from '../../../api/teamsbotApi';
|
|||
import styles from './Teamsbot.module.css';
|
||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||
|
||||
const PAST_LIMIT = 10;
|
||||
|
||||
/**
|
||||
* TeamsBot Dashboard — IA: KPIs, Modul-Aggregate, Quick-Actions, kompakte Session-Listen.
|
||||
* Neues Meeting: Assistent (Wizard). Kein paralleler Vollformular-Start hier.
|
||||
* TeamsBot Dashboard — IA: KPIs, Modul-Aggregate, Quick-Actions.
|
||||
* Neues Meeting: Assistent (Wizard). Sessions sind via Module erreichbar.
|
||||
*/
|
||||
export const TeamsbotDashboardView: React.FC = () => {
|
||||
const { t } = useLanguage();
|
||||
|
|
@ -20,18 +18,14 @@ export const TeamsbotDashboardView: React.FC = () => {
|
|||
|
||||
const [sessions, setSessions] = useState<TeamsbotSession[]>([]);
|
||||
const [modules, setModules] = useState<MeetingModule[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const sessionsRef = useRef<TeamsbotSession[]>([]);
|
||||
sessionsRef.current = sessions;
|
||||
const dashboardEsRef = useRef<EventSource | null>(null);
|
||||
const dashboardReconnectRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const dashboardReconnectRef = useRef<number | null>(null);
|
||||
|
||||
const applyDashboardPayload = useCallback((nextSessions: TeamsbotSession[], nextModules: MeetingModule[]) => {
|
||||
setSessions(nextSessions);
|
||||
setModules(nextModules);
|
||||
sessionsRef.current = nextSessions;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -48,7 +42,6 @@ export const TeamsbotDashboardView: React.FC = () => {
|
|||
const connect = () => {
|
||||
if (cancelled) return;
|
||||
dashboardEsRef.current?.close();
|
||||
setLoading(true);
|
||||
const es = teamsbotApi.createDashboardStream(instanceId);
|
||||
dashboardEsRef.current = es;
|
||||
|
||||
|
|
@ -62,7 +55,6 @@ export const TeamsbotDashboardView: React.FC = () => {
|
|||
if (msg.type === 'dashboardState' && Array.isArray(msg.sessions) && Array.isArray(msg.modules)) {
|
||||
applyDashboardPayload(msg.sessions, msg.modules);
|
||||
setError(null);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch {
|
||||
/* ignore malformed SSE */
|
||||
|
|
@ -73,7 +65,6 @@ export const TeamsbotDashboardView: React.FC = () => {
|
|||
es.close();
|
||||
dashboardEsRef.current = null;
|
||||
if (cancelled) return;
|
||||
setLoading(false);
|
||||
clearReconnect();
|
||||
dashboardReconnectRef.current = window.setTimeout(connect, 2500);
|
||||
};
|
||||
|
|
@ -92,19 +83,6 @@ export const TeamsbotDashboardView: React.FC = () => {
|
|||
() => sessions.filter((s) => ['pending', 'joining', 'active'].includes(s.status)),
|
||||
[sessions],
|
||||
);
|
||||
const pastSessions = useMemo(
|
||||
() =>
|
||||
sessions
|
||||
.filter((s) => ['ended', 'error', 'leaving'].includes(s.status))
|
||||
.sort((a, b) => {
|
||||
const ta = a.startedAt ? new Date(a.startedAt).getTime() : 0;
|
||||
const tb = b.startedAt ? new Date(b.startedAt).getTime() : 0;
|
||||
return tb - ta;
|
||||
}),
|
||||
[sessions],
|
||||
);
|
||||
const pastVisible = pastSessions.slice(0, PAST_LIMIT);
|
||||
|
||||
const moduleTitleById = useMemo(() => {
|
||||
const m = new Map<string, string>();
|
||||
modules.forEach((mod) => m.set(mod.id, mod.title));
|
||||
|
|
@ -165,10 +143,8 @@ export const TeamsbotDashboardView: React.FC = () => {
|
|||
teamsbotApi.listSessions(instanceId, true),
|
||||
teamsbotApi.listModules(instanceId),
|
||||
]);
|
||||
const nextSessions = r.sessions || [];
|
||||
setSessions(nextSessions);
|
||||
setSessions(r.sessions || []);
|
||||
setModules(m || []);
|
||||
sessionsRef.current = nextSessions;
|
||||
} catch { /* ignore */ }
|
||||
}, [instanceId]);
|
||||
|
||||
|
|
@ -181,15 +157,6 @@ export const TeamsbotDashboardView: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const _handleDeleteSession = async (sid: string) => {
|
||||
try {
|
||||
await teamsbotApi.deleteSession(instanceId, sid);
|
||||
await _refreshLists();
|
||||
} catch (err: any) {
|
||||
setError(err.message || t('Fehler beim Löschen'));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.tbDash}>
|
||||
<header className={styles.tbDashHero}>
|
||||
|
|
@ -306,52 +273,6 @@ export const TeamsbotDashboardView: React.FC = () => {
|
|||
</section>
|
||||
)}
|
||||
|
||||
<section className={styles.tbDashSection}>
|
||||
<div className={styles.tbDashSectionHead}>
|
||||
<h2 className={styles.tbDashSectionTitle}>
|
||||
{loading ? t('Laden…') : t('Letzte Sitzungen')}
|
||||
</h2>
|
||||
{pastSessions.length > PAST_LIMIT && (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.tbDashLinkBtn}
|
||||
onClick={() => navigate(`/mandates/${mandateId}/${featureCode}/${instanceId}/modules`)}
|
||||
>
|
||||
{t('Alle in Modulen')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{pastVisible.length === 0 && !loading ? (
|
||||
<p className={styles.emptyState}>{t('Noch keine beendeten Sitzungen')}</p>
|
||||
) : (
|
||||
<div className={styles.tbDashSessionList}>
|
||||
{pastVisible.map((session) => (
|
||||
<div key={session.id} className={styles.tbDashSessionRow}>
|
||||
<div className={styles.tbDashSessionMain}>
|
||||
<span className={styles.sessionBotName}>{session.botName}</span>
|
||||
<span className={`${styles.statusBadge} ${_getStatusBadgeClass(session.status)}`}>
|
||||
{_getStatusLabel(session.status)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.tbDashSessionMeta}>
|
||||
{session.startedAt && (
|
||||
<span>{new Date(session.startedAt).toLocaleString('de-CH')}</span>
|
||||
)}
|
||||
<span>{session.transcriptSegmentCount} {t('Segmente')}</span>
|
||||
</div>
|
||||
<div className={styles.tbDashSessionActions}>
|
||||
<button type="button" className={styles.viewButton} onClick={() => navigate(_sessionPath(session.id))}>
|
||||
{t('Details')}
|
||||
</button>
|
||||
<button type="button" className={styles.deleteButton} onClick={() => _handleDeleteSession(session.id)}>
|
||||
{t('Löschen')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export const TeamsbotSessionView: React.FC = () => {
|
|||
const [screenshots, setScreenshots] = useState<ScreenshotInfo[]>([]);
|
||||
const [screenshotsLoading, setScreenshotsLoading] = useState(false);
|
||||
const [screenshotsLoaded, setScreenshotsLoaded] = useState(false);
|
||||
const [screenshotsExpanded, setScreenshotsExpanded] = useState(false);
|
||||
const [ttsStatusEvents, setTtsStatusEvents] = useState<Array<{
|
||||
status: string;
|
||||
message?: string;
|
||||
|
|
@ -1193,62 +1194,77 @@ export const TeamsbotSessionView: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Debug Screenshots (SysAdmin only) */}
|
||||
{/* Debug Screenshots (SysAdmin only, collapsible) */}
|
||||
{_isSysAdmin && (
|
||||
<div className={styles.summaryCard}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
|
||||
<h4 className={styles.panelTitle} style={{ margin: 0 }}>{t('Debug-Screenshots')}</h4>
|
||||
<button
|
||||
className={styles.viewButton}
|
||||
onClick={async () => {
|
||||
setScreenshotsLoading(true);
|
||||
try {
|
||||
const result = await teamsbotApi.listScreenshots(instanceId, session.id);
|
||||
setScreenshots(result.screenshots || []);
|
||||
setScreenshotsLoaded(true);
|
||||
} catch (err: any) {
|
||||
setScreenshots([]);
|
||||
setScreenshotsLoaded(true);
|
||||
} finally {
|
||||
setScreenshotsLoading(false);
|
||||
}
|
||||
}}
|
||||
disabled={screenshotsLoading}
|
||||
>
|
||||
{screenshotsLoading ? t('Laden…') : screenshotsLoaded ? t('Aktualisieren') : t('Screenshots laden')}
|
||||
</button>
|
||||
</div>
|
||||
{screenshotsLoaded && screenshots.length === 0 && (
|
||||
<div className={styles.emptyState}>{t('Keine Screenshots für diese Sitzung')}</div>
|
||||
)}
|
||||
{screenshots.length > 0 && (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: '12px' }}>
|
||||
{screenshots.map((s) => {
|
||||
const imgUrl = teamsbotApi.getScreenshotUrl(instanceId, s.name);
|
||||
return (
|
||||
<a
|
||||
key={s.name}
|
||||
href={imgUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ display: 'block', textDecoration: 'none', color: 'inherit', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden', background: '#1a1a2e' }}
|
||||
>
|
||||
<img
|
||||
src={imgUrl}
|
||||
alt={s.step}
|
||||
style={{ width: '100%', height: '140px', objectFit: 'cover', display: 'block' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div style={{ padding: '8px', fontSize: '12px' }}>
|
||||
<div style={{ fontWeight: 600, marginBottom: '2px' }}>{s.step}</div>
|
||||
<div style={{ color: '#888' }}>
|
||||
{new Date(s.timestamp).toLocaleTimeString('de-CH')} — {(s.sizeBytes / 1024).toFixed(0)} KB
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setScreenshotsExpanded((v) => !v)}
|
||||
>
|
||||
<h4 className={styles.panelTitle} style={{ margin: 0 }}>
|
||||
{screenshotsExpanded ? '\u25BC' : '\u25B6'} {t('Debug-Screenshots')}
|
||||
{screenshotsLoaded && screenshots.length > 0 && ` (${screenshots.length})`}
|
||||
</h4>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
{screenshotsExpanded && (
|
||||
<button
|
||||
className={styles.viewButton}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
setScreenshotsLoading(true);
|
||||
try {
|
||||
const result = await teamsbotApi.listScreenshots(instanceId, session.id);
|
||||
setScreenshots(result.screenshots || []);
|
||||
setScreenshotsLoaded(true);
|
||||
} catch (err: any) {
|
||||
setScreenshots([]);
|
||||
setScreenshotsLoaded(true);
|
||||
} finally {
|
||||
setScreenshotsLoading(false);
|
||||
}
|
||||
}}
|
||||
disabled={screenshotsLoading}
|
||||
>
|
||||
{screenshotsLoading ? t('Laden…') : screenshotsLoaded ? t('Aktualisieren') : t('Screenshots laden')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{screenshotsExpanded && (
|
||||
<>
|
||||
{screenshotsLoaded && screenshots.length === 0 && (
|
||||
<div className={styles.emptyState} style={{ marginTop: '12px' }}>{t('Keine Screenshots für diese Sitzung')}</div>
|
||||
)}
|
||||
{screenshots.length > 0 && (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: '12px', marginTop: '12px' }}>
|
||||
{screenshots.map((s) => {
|
||||
const imgUrl = teamsbotApi.getScreenshotUrl(instanceId, s.name);
|
||||
return (
|
||||
<a
|
||||
key={s.name}
|
||||
href={imgUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ display: 'block', textDecoration: 'none', color: 'inherit', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden', background: '#1a1a2e' }}
|
||||
>
|
||||
<img
|
||||
src={imgUrl}
|
||||
alt={s.step}
|
||||
style={{ width: '100%', height: '140px', objectFit: 'cover', display: 'block' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div style={{ padding: '8px', fontSize: '12px' }}>
|
||||
<div style={{ fontWeight: 600, marginBottom: '2px' }}>{s.step}</div>
|
||||
<div style={{ color: '#888' }}>
|
||||
{new Date(s.timestamp).toLocaleTimeString('de-CH')} — {(s.sizeBytes / 1024).toFixed(0)} KB
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in a new issue