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:
parent
bc5bae7c34
commit
294710b415
2 changed files with 30 additions and 31 deletions
|
|
@ -35,7 +35,6 @@ export const TeamsbotDashboardView: React.FC = () => {
|
||||||
const [showCredentialForm, setShowCredentialForm] = useState(false);
|
const [showCredentialForm, setShowCredentialForm] = useState(false);
|
||||||
const [credEmail, setCredEmail] = useState('');
|
const [credEmail, setCredEmail] = useState('');
|
||||||
const [credPassword, setCredPassword] = useState('');
|
const [credPassword, setCredPassword] = useState('');
|
||||||
const [saveCredentials, setSaveCredentials] = useState(true);
|
|
||||||
const [savingCredentials, setSavingCredentials] = useState(false);
|
const [savingCredentials, setSavingCredentials] = useState(false);
|
||||||
|
|
||||||
// MFA state
|
// MFA state
|
||||||
|
|
@ -128,9 +127,8 @@ export const TeamsbotDashboardView: React.FC = () => {
|
||||||
return;
|
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 (joinMode === 'userAccount' && showCredentialForm && credEmail && credPassword) {
|
||||||
if (saveCredentials) {
|
|
||||||
try {
|
try {
|
||||||
setSavingCredentials(true);
|
setSavingCredentials(true);
|
||||||
await teamsbotApi.saveUserAccount(instanceId, credEmail, credPassword);
|
await teamsbotApi.saveUserAccount(instanceId, credEmail, credPassword);
|
||||||
|
|
@ -142,7 +140,6 @@ export const TeamsbotDashboardView: React.FC = () => {
|
||||||
} finally {
|
} finally {
|
||||||
setSavingCredentials(false);
|
setSavingCredentials(false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
setShowCredentialForm(false);
|
setShowCredentialForm(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -373,15 +370,9 @@ export const TeamsbotDashboardView: React.FC = () => {
|
||||||
disabled={savingCredentials}
|
disabled={savingCredentials}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.checkboxRow}>
|
<span style={{ fontSize: '12px', color: '#888', marginTop: '4px', display: 'block' }}>
|
||||||
<input
|
Zugangsdaten werden verschluesselt gespeichert.
|
||||||
type="checkbox"
|
</span>
|
||||||
id="saveCredentials"
|
|
||||||
checked={saveCredentials}
|
|
||||||
onChange={(e) => setSaveCredentials(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label htmlFor="saveCredentials">Zugangsdaten speichern (verschluesselt)</label>
|
|
||||||
</div>
|
|
||||||
{userAccount?.hasSavedCredentials && (
|
{userAccount?.hasSavedCredentials && (
|
||||||
<button
|
<button
|
||||||
className={styles.viewButton}
|
className={styles.viewButton}
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,16 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
_loadSession();
|
_loadSession();
|
||||||
}, [_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(() => {
|
useEffect(() => {
|
||||||
if (!instanceId || !sessionId || !session) return;
|
if (!instanceId || !sessionId || !session) return;
|
||||||
if (!['active', 'joining', 'pending'].includes(session.status)) 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);
|
const eventSource = teamsbotApi.createSessionStream(instanceId, sessionId);
|
||||||
eventSourceRef.current = eventSource;
|
eventSourceRef.current = eventSource;
|
||||||
|
|
@ -110,11 +116,12 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
if (['ended', 'error'].includes(sseEvent.data.status)) {
|
if (['ended', 'error'].includes(sseEvent.data.status)) {
|
||||||
setIsLive(false);
|
setIsLive(false);
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
|
eventSourceRef.current = null;
|
||||||
|
sseSessionRef.current = null;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'analysis':
|
case 'analysis':
|
||||||
// Debug info - could show in UI
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ttsDeliveryStatus': {
|
case 'ttsDeliveryStatus': {
|
||||||
|
|
@ -132,7 +139,6 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'suggestedResponse':
|
case 'suggestedResponse':
|
||||||
// Manual mode: show suggested response
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ping':
|
case 'ping':
|
||||||
|
|
@ -150,16 +156,18 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
return () => {
|
return () => {
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
eventSourceRef.current = null;
|
eventSourceRef.current = null;
|
||||||
|
sseSessionRef.current = null;
|
||||||
setIsLive(false);
|
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 pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||||
const isActive = useMemo(() => session && ['pending', 'joining', 'active'].includes(session.status), [session]);
|
const isActive = useMemo(() => session && ['pending', 'joining', 'active'].includes(session.status), [session]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isActive && instanceId && sessionId && !isLive) {
|
if (instanceId && sessionId && (isActive || !session)) {
|
||||||
pollRef.current = setInterval(async () => {
|
pollRef.current = setInterval(async () => {
|
||||||
|
if (isLive) return;
|
||||||
try {
|
try {
|
||||||
const result = await teamsbotApi.getSession(instanceId, sessionId);
|
const result = await teamsbotApi.getSession(instanceId, sessionId);
|
||||||
setSession(result.session);
|
setSession(result.session);
|
||||||
|
|
@ -169,7 +177,7 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
return () => { if (pollRef.current) clearInterval(pollRef.current); };
|
return () => { if (pollRef.current) clearInterval(pollRef.current); };
|
||||||
}, [isActive, instanceId, sessionId, isLive]);
|
}, [isActive, instanceId, sessionId, isLive, session]);
|
||||||
|
|
||||||
// Auto-scroll transcript
|
// Auto-scroll transcript
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -254,7 +262,7 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
{isLive && <span className={styles.liveBadge}>LIVE</span>}
|
{isLive && <span className={styles.liveBadge}>LIVE</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.sessionControls}>
|
<div className={styles.sessionControls}>
|
||||||
{['active', 'joining'].includes(session.status) && (
|
{['active', 'joining', 'pending'].includes(session.status) && (
|
||||||
<button className={styles.stopButton} onClick={_handleStop}>Sitzung beenden</button>
|
<button className={styles.stopButton} onClick={_handleStop}>Sitzung beenden</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue