feat: add debug screenshot viewer in session view (SysAdmin only)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ValueOn AG 2026-02-18 22:00:35 +01:00
parent 32c071911a
commit 2cf296dee6
2 changed files with 91 additions and 1 deletions

View file

@ -378,3 +378,25 @@ export function createSessionStream(instanceId: string, sessionId: string): Even
const url = `${baseUrl}/api/teamsbot/${instanceId}/sessions/${sessionId}/stream`; const url = `${baseUrl}/api/teamsbot/${instanceId}/sessions/${sessionId}/stream`;
return new EventSource(url, { withCredentials: true }); return new EventSource(url, { withCredentials: true });
} }
// =========================================================================
// Debug Screenshots (SysAdmin only)
// =========================================================================
export interface ScreenshotInfo {
name: string;
step: string;
timestamp: number;
sizeBytes: number;
url: string;
}
export async function listScreenshots(instanceId: string, sessionId: string): Promise<{ screenshots: ScreenshotInfo[] }> {
const response = await api.get(`/api/teamsbot/${instanceId}/sessions/${sessionId}/screenshots`);
return response.data;
}
export function getScreenshotUrl(instanceId: string, filename: string): string {
const baseUrl = api.defaults.baseURL || '';
return `${baseUrl}/api/teamsbot/${instanceId}/screenshots/${filename}`;
}

View file

@ -2,7 +2,8 @@ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { useCurrentInstance } from '../../../hooks/useCurrentInstance'; import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
import * as teamsbotApi from '../../../api/teamsbotApi'; import * as teamsbotApi from '../../../api/teamsbotApi';
import type { TeamsbotSession, TeamsbotTranscript, TeamsbotBotResponse, TeamsbotSSEEvent } from '../../../api/teamsbotApi'; import type { TeamsbotSession, TeamsbotTranscript, TeamsbotBotResponse, TeamsbotSSEEvent, ScreenshotInfo } from '../../../api/teamsbotApi';
import { getUserDataCache } from '../../../utils/userCache';
import styles from './Teamsbot.module.css'; import styles from './Teamsbot.module.css';
/** /**
@ -14,6 +15,9 @@ export const TeamsbotSessionView: React.FC = () => {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const sessionId = searchParams.get('sessionId') || ''; const sessionId = searchParams.get('sessionId') || '';
const cachedUser = getUserDataCache();
const _isSysAdmin = cachedUser?.isSysAdmin === true;
const [session, setSession] = useState<TeamsbotSession | null>(null); const [session, setSession] = useState<TeamsbotSession | null>(null);
const [allSessions, setAllSessions] = useState<TeamsbotSession[]>([]); const [allSessions, setAllSessions] = useState<TeamsbotSession[]>([]);
const [transcripts, setTranscripts] = useState<TeamsbotTranscript[]>([]); const [transcripts, setTranscripts] = useState<TeamsbotTranscript[]>([]);
@ -23,6 +27,10 @@ export const TeamsbotSessionView: React.FC = () => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [isLive, setIsLive] = useState(false); const [isLive, setIsLive] = useState(false);
const [screenshots, setScreenshots] = useState<ScreenshotInfo[]>([]);
const [screenshotsLoading, setScreenshotsLoading] = useState(false);
const [screenshotsLoaded, setScreenshotsLoaded] = useState(false);
const transcriptEndRef = useRef<HTMLDivElement>(null); const transcriptEndRef = useRef<HTMLDivElement>(null);
const eventSourceRef = useRef<EventSource | null>(null); const eventSourceRef = useRef<EventSource | null>(null);
@ -294,6 +302,66 @@ export const TeamsbotSessionView: React.FC = () => {
<div className={styles.summaryText}>{session.summary}</div> <div className={styles.summaryText}>{session.summary}</div>
</div> </div>
)} )}
{/* Debug Screenshots (SysAdmin only) */}
{_isSysAdmin && (
<div className={styles.summaryCard}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
<h4 className={styles.panelTitle} style={{ margin: 0 }}>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 ? 'Laden...' : screenshotsLoaded ? 'Aktualisieren' : 'Screenshots laden'}
</button>
</div>
{screenshotsLoaded && screenshots.length === 0 && (
<div className={styles.emptyState}>Keine Screenshots fuer diese Session.</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>
)}
</div>
)}
</div> </div>
); );
}; };