fix(teamsbot): always save user credentials before session start, fix SSE reconnect loop, add stop button for pending

Made-with: Cursor
This commit is contained in:
ValueOn AG 2026-03-01 09:26:46 +01:00
parent bc5bae7c34
commit 294710b415
2 changed files with 30 additions and 31 deletions

View file

@ -35,7 +35,6 @@ export const TeamsbotDashboardView: React.FC = () => {
const [showCredentialForm, setShowCredentialForm] = useState(false);
const [credEmail, setCredEmail] = useState('');
const [credPassword, setCredPassword] = useState('');
const [saveCredentials, setSaveCredentials] = useState(true);
const [savingCredentials, setSavingCredentials] = useState(false);
// MFA state
@ -128,9 +127,8 @@ export const TeamsbotDashboardView: React.FC = () => {
return;
}
// Save credentials if requested (first-time entry)
// userAccount with new credentials: always save to DB (backend loads from there)
if (joinMode === 'userAccount' && showCredentialForm && credEmail && credPassword) {
if (saveCredentials) {
try {
setSavingCredentials(true);
await teamsbotApi.saveUserAccount(instanceId, credEmail, credPassword);
@ -142,7 +140,6 @@ export const TeamsbotDashboardView: React.FC = () => {
} finally {
setSavingCredentials(false);
}
}
setShowCredentialForm(false);
}
@ -373,15 +370,9 @@ export const TeamsbotDashboardView: React.FC = () => {
disabled={savingCredentials}
/>
</div>
<div className={styles.checkboxRow}>
<input
type="checkbox"
id="saveCredentials"
checked={saveCredentials}
onChange={(e) => setSaveCredentials(e.target.checked)}
/>
<label htmlFor="saveCredentials">Zugangsdaten speichern (verschluesselt)</label>
</div>
<span style={{ fontSize: '12px', color: '#888', marginTop: '4px', display: 'block' }}>
Zugangsdaten werden verschluesselt gespeichert.
</span>
{userAccount?.hasSavedCredentials && (
<button
className={styles.viewButton}

View file

@ -83,10 +83,16 @@ export const TeamsbotSessionView: React.FC = () => {
_loadSession();
}, [_loadSession]);
// SSE Live Stream
// SSE Live Stream - connect once per session, don't re-create on status changes
const sseSessionRef = useRef<string | null>(null);
useEffect(() => {
if (!instanceId || !sessionId || !session) return;
if (!['active', 'joining', 'pending'].includes(session.status)) return;
// Avoid reconnecting if already streaming this session
if (sseSessionRef.current === sessionId && eventSourceRef.current) return;
eventSourceRef.current?.close();
sseSessionRef.current = sessionId;
const eventSource = teamsbotApi.createSessionStream(instanceId, sessionId);
eventSourceRef.current = eventSource;
@ -110,11 +116,12 @@ export const TeamsbotSessionView: React.FC = () => {
if (['ended', 'error'].includes(sseEvent.data.status)) {
setIsLive(false);
eventSource.close();
eventSourceRef.current = null;
sseSessionRef.current = null;
}
break;
case 'analysis':
// Debug info - could show in UI
break;
case 'ttsDeliveryStatus': {
@ -132,7 +139,6 @@ export const TeamsbotSessionView: React.FC = () => {
}
case 'suggestedResponse':
// Manual mode: show suggested response
break;
case 'ping':
@ -150,16 +156,18 @@ export const TeamsbotSessionView: React.FC = () => {
return () => {
eventSource.close();
eventSourceRef.current = null;
sseSessionRef.current = null;
setIsLive(false);
};
}, [instanceId, sessionId, session?.status]);
}, [instanceId, sessionId]);
// Polling fallback: refresh session data every 5s when session is active (in case SSE fails)
// Polling fallback: refresh session data every 5s when SSE is not connected
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
const isActive = useMemo(() => session && ['pending', 'joining', 'active'].includes(session.status), [session]);
useEffect(() => {
if (isActive && instanceId && sessionId && !isLive) {
if (instanceId && sessionId && (isActive || !session)) {
pollRef.current = setInterval(async () => {
if (isLive) return;
try {
const result = await teamsbotApi.getSession(instanceId, sessionId);
setSession(result.session);
@ -169,7 +177,7 @@ export const TeamsbotSessionView: React.FC = () => {
}, 5000);
}
return () => { if (pollRef.current) clearInterval(pollRef.current); };
}, [isActive, instanceId, sessionId, isLive]);
}, [isActive, instanceId, sessionId, isLive, session]);
// Auto-scroll transcript
useEffect(() => {
@ -254,7 +262,7 @@ export const TeamsbotSessionView: React.FC = () => {
{isLive && <span className={styles.liveBadge}>LIVE</span>}
</div>
<div className={styles.sessionControls}>
{['active', 'joining'].includes(session.status) && (
{['active', 'joining', 'pending'].includes(session.status) && (
<button className={styles.stopButton} onClick={_handleStop}>Sitzung beenden</button>
)}
</div>