refactor: UI cleanup - remove test block, backgroundImage, botAccount; add transferMode
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2e1f3a8733
commit
41a4e8ffe1
2 changed files with 25 additions and 414 deletions
|
|
@ -10,7 +10,6 @@ export interface TeamsbotSession {
|
||||||
mandateId: string;
|
mandateId: string;
|
||||||
meetingLink: string;
|
meetingLink: string;
|
||||||
botName: string;
|
botName: string;
|
||||||
backgroundImageUrl?: string;
|
|
||||||
status: 'pending' | 'joining' | 'active' | 'leaving' | 'ended' | 'error';
|
status: 'pending' | 'joining' | 'active' | 'leaving' | 'ended' | 'error';
|
||||||
startedAt?: string;
|
startedAt?: string;
|
||||||
endedAt?: string;
|
endedAt?: string;
|
||||||
|
|
@ -53,17 +52,17 @@ export interface TeamsbotBotResponse {
|
||||||
export type TeamsbotResponseChannel = 'voice' | 'chat' | 'both';
|
export type TeamsbotResponseChannel = 'voice' | 'chat' | 'both';
|
||||||
export type TeamsbotJoinMode = 'systemBot' | 'anonymous' | 'userAccount';
|
export type TeamsbotJoinMode = 'systemBot' | 'anonymous' | 'userAccount';
|
||||||
|
|
||||||
|
export type TeamsbotTransferMode = 'caption' | 'audio' | 'auto';
|
||||||
|
|
||||||
export interface TeamsbotConfig {
|
export interface TeamsbotConfig {
|
||||||
botName: string;
|
botName: string;
|
||||||
backgroundImageUrl?: string;
|
|
||||||
aiSystemPrompt: string;
|
aiSystemPrompt: string;
|
||||||
responseMode: 'auto' | 'manual' | 'transcribeOnly';
|
responseMode: 'auto' | 'manual' | 'transcribeOnly';
|
||||||
responseChannel: TeamsbotResponseChannel;
|
responseChannel: TeamsbotResponseChannel;
|
||||||
|
transferMode: TeamsbotTransferMode;
|
||||||
language: string;
|
language: string;
|
||||||
voiceId?: string;
|
voiceId?: string;
|
||||||
browserBotUrl?: string;
|
browserBotUrl?: string;
|
||||||
botAccountEmail?: string;
|
|
||||||
botAccountPassword?: string;
|
|
||||||
triggerIntervalSeconds: number;
|
triggerIntervalSeconds: number;
|
||||||
triggerCooldownSeconds: number;
|
triggerCooldownSeconds: number;
|
||||||
contextWindowSegments: number;
|
contextWindowSegments: number;
|
||||||
|
|
@ -80,7 +79,6 @@ export interface TeamsbotSessionStats {
|
||||||
export interface StartSessionRequest {
|
export interface StartSessionRequest {
|
||||||
meetingLink: string;
|
meetingLink: string;
|
||||||
botName?: string;
|
botName?: string;
|
||||||
backgroundImageUrl?: string;
|
|
||||||
connectionId?: string;
|
connectionId?: string;
|
||||||
joinMode?: TeamsbotJoinMode;
|
joinMode?: TeamsbotJoinMode;
|
||||||
sessionContext?: string;
|
sessionContext?: string;
|
||||||
|
|
@ -88,15 +86,13 @@ export interface StartSessionRequest {
|
||||||
|
|
||||||
export interface ConfigUpdateRequest {
|
export interface ConfigUpdateRequest {
|
||||||
botName?: string;
|
botName?: string;
|
||||||
backgroundImageUrl?: string;
|
|
||||||
aiSystemPrompt?: string;
|
aiSystemPrompt?: string;
|
||||||
responseMode?: 'auto' | 'manual' | 'transcribeOnly';
|
responseMode?: 'auto' | 'manual' | 'transcribeOnly';
|
||||||
responseChannel?: TeamsbotResponseChannel;
|
responseChannel?: TeamsbotResponseChannel;
|
||||||
|
transferMode?: TeamsbotTransferMode;
|
||||||
language?: string;
|
language?: string;
|
||||||
voiceId?: string;
|
voiceId?: string;
|
||||||
browserBotUrl?: string;
|
browserBotUrl?: string;
|
||||||
botAccountEmail?: string;
|
|
||||||
botAccountPassword?: string;
|
|
||||||
triggerIntervalSeconds?: number;
|
triggerIntervalSeconds?: number;
|
||||||
triggerCooldownSeconds?: number;
|
triggerCooldownSeconds?: number;
|
||||||
contextWindowSegments?: number;
|
contextWindowSegments?: number;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
|
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
|
||||||
import * as teamsbotApi from '../../../api/teamsbotApi';
|
import * as teamsbotApi from '../../../api/teamsbotApi';
|
||||||
import type { TeamsbotConfig, ConfigUpdateRequest, VoiceOption, AuthTestResults, AuthTestResult } from '../../../api/teamsbotApi';
|
import type { TeamsbotConfig, ConfigUpdateRequest, VoiceOption } from '../../../api/teamsbotApi';
|
||||||
import { FaPlay, FaSpinner, FaFlask, FaImage, FaChevronDown, FaChevronRight } from 'react-icons/fa';
|
import { FaPlay, FaSpinner } from 'react-icons/fa';
|
||||||
import styles from './Teamsbot.module.css';
|
import styles from './Teamsbot.module.css';
|
||||||
|
|
||||||
/** Format voice name for display: "de-DE-Wavenet-A" -> "Wavenet A" + gender */
|
/** Format voice name for display: "de-DE-Wavenet-A" -> "Wavenet A" + gender */
|
||||||
|
|
@ -37,16 +37,6 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
const [voices, setVoices] = useState<VoiceOption[]>([]);
|
const [voices, setVoices] = useState<VoiceOption[]>([]);
|
||||||
const [loadingVoices, setLoadingVoices] = useState(false);
|
const [loadingVoices, setLoadingVoices] = useState(false);
|
||||||
|
|
||||||
// Auth detection test state
|
|
||||||
const [testMeetingUrl, setTestMeetingUrl] = useState('');
|
|
||||||
const [testBotEmail, setTestBotEmail] = useState('');
|
|
||||||
const [testBotPassword, setTestBotPassword] = useState('');
|
|
||||||
const [testRunning, setTestRunning] = useState(false);
|
|
||||||
const [testRunningVariant, setTestRunningVariant] = useState<string | null>(null);
|
|
||||||
const [testResults, setTestResults] = useState<AuthTestResults | null>(null);
|
|
||||||
const [testError, setTestError] = useState<string | null>(null);
|
|
||||||
const [screenshotPreview, setScreenshotPreview] = useState<{ src: string; caption: string } | null>(null);
|
|
||||||
const [expandedLogs, setExpandedLogs] = useState<Set<string>>(new Set());
|
|
||||||
|
|
||||||
const _loadConfig = useCallback(async () => {
|
const _loadConfig = useCallback(async () => {
|
||||||
if (!instanceId) return;
|
if (!instanceId) return;
|
||||||
|
|
@ -153,157 +143,6 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const _handleRunAuthTest = async () => {
|
|
||||||
if (!instanceId || !testMeetingUrl.trim()) return;
|
|
||||||
setTestRunning(true);
|
|
||||||
setTestResults(null);
|
|
||||||
setTestError(null);
|
|
||||||
setTestRunningVariant(null);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Step 1: Get available variants
|
|
||||||
const variants = await teamsbotApi.getTestAuthVariants(instanceId);
|
|
||||||
|
|
||||||
// Initialize results shell so UI shows progress
|
|
||||||
const initialResults: AuthTestResults = {
|
|
||||||
meetingUrl: testMeetingUrl.trim(),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
variants: variants.map(v => ({
|
|
||||||
variantId: v.id,
|
|
||||||
variantName: v.name,
|
|
||||||
success: false,
|
|
||||||
pageType: 'unknown' as const,
|
|
||||||
finalUrl: '',
|
|
||||||
hasSignInLink: false,
|
|
||||||
hasNameInput: false,
|
|
||||||
hasJoinButton: false,
|
|
||||||
authAttempted: false,
|
|
||||||
authSuccess: null,
|
|
||||||
durationMs: 0,
|
|
||||||
detectedSignals: [],
|
|
||||||
logs: ['Warte...'],
|
|
||||||
})),
|
|
||||||
recommendation: 'Test laeuft...',
|
|
||||||
};
|
|
||||||
setTestResults(initialResults);
|
|
||||||
|
|
||||||
// Step 2: Run each variant sequentially
|
|
||||||
const completedVariants: AuthTestResult[] = [];
|
|
||||||
for (const variantInfo of variants) {
|
|
||||||
setTestRunningVariant(variantInfo.name);
|
|
||||||
try {
|
|
||||||
const result = await teamsbotApi.testAuthSingleVariant(
|
|
||||||
instanceId,
|
|
||||||
variantInfo.id,
|
|
||||||
testMeetingUrl.trim(),
|
|
||||||
testBotEmail.trim() || undefined,
|
|
||||||
testBotPassword || undefined,
|
|
||||||
);
|
|
||||||
completedVariants.push(result);
|
|
||||||
} catch (err: any) {
|
|
||||||
completedVariants.push({
|
|
||||||
variantId: variantInfo.id,
|
|
||||||
variantName: variantInfo.name,
|
|
||||||
success: false,
|
|
||||||
pageType: 'error',
|
|
||||||
finalUrl: '',
|
|
||||||
hasSignInLink: false,
|
|
||||||
hasNameInput: false,
|
|
||||||
hasJoinButton: false,
|
|
||||||
authAttempted: false,
|
|
||||||
authSuccess: null,
|
|
||||||
durationMs: 0,
|
|
||||||
error: err.message || 'Fehler',
|
|
||||||
detectedSignals: [],
|
|
||||||
logs: [`[ERROR] ${err.message || 'Unbekannter Fehler'}`],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update UI with results so far
|
|
||||||
setTestResults(prev => prev ? {
|
|
||||||
...prev,
|
|
||||||
variants: [
|
|
||||||
...completedVariants,
|
|
||||||
...variants.slice(completedVariants.length).map(v => ({
|
|
||||||
variantId: v.id,
|
|
||||||
variantName: v.name,
|
|
||||||
success: false,
|
|
||||||
pageType: 'unknown' as const,
|
|
||||||
finalUrl: '',
|
|
||||||
hasSignInLink: false,
|
|
||||||
hasNameInput: false,
|
|
||||||
hasJoinButton: false,
|
|
||||||
authAttempted: false,
|
|
||||||
authSuccess: null,
|
|
||||||
durationMs: 0,
|
|
||||||
detectedSignals: [],
|
|
||||||
logs: ['Warte...'],
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
} : prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final update with recommendation
|
|
||||||
setTestResults({
|
|
||||||
meetingUrl: testMeetingUrl.trim(),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
variants: completedVariants,
|
|
||||||
recommendation: `Test abgeschlossen. ${completedVariants.filter(v => v.pageType === 'v2').length} von ${completedVariants.length} Varianten auf /v2/.`,
|
|
||||||
});
|
|
||||||
} catch (err: any) {
|
|
||||||
setTestError(err.message || 'Auth-Test fehlgeschlagen');
|
|
||||||
} finally {
|
|
||||||
setTestRunning(false);
|
|
||||||
setTestRunningVariant(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const _getPageTypeBadge = (result: AuthTestResult) => {
|
|
||||||
switch (result.pageType) {
|
|
||||||
case 'v2':
|
|
||||||
return <span className={styles.testBadgeV2}>/v2/</span>;
|
|
||||||
case 'lightMeetings':
|
|
||||||
return <span className={styles.testBadgeLightMeetings}>light-meetings</span>;
|
|
||||||
case 'error':
|
|
||||||
return <span className={styles.testBadgeError}>Fehler</span>;
|
|
||||||
default:
|
|
||||||
return <span className={styles.testBadgeUnknown}>Unbekannt</span>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const _getCheckmark = (value: boolean) => {
|
|
||||||
return value
|
|
||||||
? <span className={styles.testCheckmark}>✓</span>
|
|
||||||
: <span className={styles.testCross}>✗</span>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const _formatDuration = (ms: number) => {
|
|
||||||
if (ms < 1000) return `${ms}ms`;
|
|
||||||
return `${(ms / 1000).toFixed(1)}s`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const _toggleLogs = (variantId: string) => {
|
|
||||||
setExpandedLogs(prev => {
|
|
||||||
const next = new Set(prev);
|
|
||||||
if (next.has(variantId)) {
|
|
||||||
next.delete(variantId);
|
|
||||||
} else {
|
|
||||||
next.add(variantId);
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const _getLogLevelClass = (log: string): string => {
|
|
||||||
if (log.startsWith('[ERROR]') || log.startsWith('[CONSOLE:error]') || log.startsWith('[PAGE_ERROR]')) {
|
|
||||||
return styles.testLogError;
|
|
||||||
}
|
|
||||||
if (log.startsWith('[WARN]') || log.startsWith('[CONSOLE:warning]')) {
|
|
||||||
return styles.testLogWarn;
|
|
||||||
}
|
|
||||||
return styles.testLogInfo;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) return <div className={styles.loading}>Lade Konfiguration...</div>;
|
if (loading) return <div className={styles.loading}>Lade Konfiguration...</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -314,227 +153,6 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
{error && <div className={styles.errorBanner}>{error}</div>}
|
{error && <div className={styles.errorBanner}>{error}</div>}
|
||||||
{successMsg && <div className={styles.successBanner}>{successMsg}</div>}
|
{successMsg && <div className={styles.successBanner}>{successMsg}</div>}
|
||||||
|
|
||||||
{/* Auth Detection Test */}
|
|
||||||
<div className={styles.testSection}>
|
|
||||||
<div className={styles.testSectionHeader}>
|
|
||||||
<FaFlask />
|
|
||||||
<h4 className={styles.testSectionTitle}>Auth-Erkennung testen</h4>
|
|
||||||
</div>
|
|
||||||
<span className={styles.hint}>
|
|
||||||
Testet Chromium Minimal: MS-Login, Join a meeting, dann Join im Chat-Header.
|
|
||||||
Screenshots pro Schritt. Nur funktionierende Variante aktiv.
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div className={styles.testInputRow} style={{ marginTop: '0.75rem' }}>
|
|
||||||
<input
|
|
||||||
type="url"
|
|
||||||
className={styles.input}
|
|
||||||
value={testMeetingUrl}
|
|
||||||
onChange={(e) => setTestMeetingUrl(e.target.value)}
|
|
||||||
placeholder="https://teams.microsoft.com/meet/..."
|
|
||||||
disabled={testRunning}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className={styles.testButton}
|
|
||||||
onClick={_handleRunAuthTest}
|
|
||||||
disabled={testRunning || !testMeetingUrl.trim()}
|
|
||||||
>
|
|
||||||
{testRunning ? <FaSpinner className={styles.spinner} /> : <FaFlask />}
|
|
||||||
{testRunning ? 'Teste...' : 'Testen'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.testInputRow} style={{ marginTop: '0.5rem', gap: '0.5rem' }}>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
className={styles.input}
|
|
||||||
value={testBotEmail}
|
|
||||||
onChange={(e) => setTestBotEmail(e.target.value)}
|
|
||||||
placeholder="Bot-Email (z.B. nyla.larsson@poweron.swiss)"
|
|
||||||
disabled={testRunning}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
className={styles.input}
|
|
||||||
value={testBotPassword}
|
|
||||||
onChange={(e) => setTestBotPassword(e.target.value)}
|
|
||||||
placeholder="Bot-Passwort"
|
|
||||||
disabled={testRunning}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className={styles.hint}>
|
|
||||||
Optional: Bot-Credentials fuer Auth-Test. Werden beim ersten Test automatisch in der DB gespeichert.
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{testRunning && (
|
|
||||||
<div className={styles.testProgress}>
|
|
||||||
<FaSpinner className={styles.spinner} />
|
|
||||||
{testRunningVariant
|
|
||||||
? `Teste: ${testRunningVariant}...`
|
|
||||||
: 'Varianten werden geladen...'}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{testError && <div className={styles.errorBanner}>{testError}</div>}
|
|
||||||
|
|
||||||
{testResults && (
|
|
||||||
<>
|
|
||||||
<table className={styles.testResultsTable}>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Variante</th>
|
|
||||||
<th>Seite</th>
|
|
||||||
<th>Sign-in</th>
|
|
||||||
<th>Name-Input</th>
|
|
||||||
<th>Join</th>
|
|
||||||
<th>Auth</th>
|
|
||||||
<th>Dauer</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{testResults.variants.map((result) => (
|
|
||||||
<React.Fragment key={result.variantId}>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div className={styles.testVariantCell}>
|
|
||||||
{result.logs && result.logs.length > 0 && (
|
|
||||||
<button
|
|
||||||
className={styles.testLogToggle}
|
|
||||||
onClick={() => _toggleLogs(result.variantId)}
|
|
||||||
title="Logs anzeigen/ausblenden"
|
|
||||||
>
|
|
||||||
{expandedLogs.has(result.variantId) ? <FaChevronDown /> : <FaChevronRight />}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<span className={styles.testVariantName}>{result.variantName}</span>
|
|
||||||
</div>
|
|
||||||
{result.error && (
|
|
||||||
<div className={styles.testErrorText} title={result.error}>
|
|
||||||
{result.error.length > 120 ? result.error.substring(0, 120) + '...' : result.error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td>{_getPageTypeBadge(result)}</td>
|
|
||||||
<td>{result.success ? _getCheckmark(result.hasSignInLink) : <span className={styles.testDash}>—</span>}</td>
|
|
||||||
<td>{result.success ? _getCheckmark(result.hasNameInput) : <span className={styles.testDash}>—</span>}</td>
|
|
||||||
<td>{result.success ? _getCheckmark(result.hasJoinButton) : <span className={styles.testDash}>—</span>}</td>
|
|
||||||
<td>
|
|
||||||
{result.authAttempted
|
|
||||||
? (result.authSuccess ? <span className={styles.testCheckmark}>Erfolgreich</span> : <span className={styles.testCross}>Fehlgeschlagen</span>)
|
|
||||||
: <span className={styles.testDash}>—</span>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td><span className={styles.testDuration}>{_formatDuration(result.durationMs)}</span></td>
|
|
||||||
<td>
|
|
||||||
<div className={styles.testScreenshotButtons}>
|
|
||||||
{result.screenshots && result.screenshots.length > 0
|
|
||||||
? result.screenshots.map((ss, idx) => (
|
|
||||||
<button
|
|
||||||
key={idx}
|
|
||||||
className={styles.testScreenshotButton}
|
|
||||||
onClick={() => setScreenshotPreview({
|
|
||||||
src: `data:image/jpeg;base64,${ss.data}`,
|
|
||||||
caption: `${result.variantName} — ${ss.label}`,
|
|
||||||
})}
|
|
||||||
title={ss.label}
|
|
||||||
>
|
|
||||||
<FaImage /> {ss.label}
|
|
||||||
</button>
|
|
||||||
))
|
|
||||||
: result.screenshot && (
|
|
||||||
<button
|
|
||||||
className={styles.testScreenshotButton}
|
|
||||||
onClick={() => setScreenshotPreview({
|
|
||||||
src: `data:image/jpeg;base64,${result.screenshot}`,
|
|
||||||
caption: result.variantName,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<FaImage /> Bild
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{expandedLogs.has(result.variantId) && result.logs && result.logs.length > 0 && (
|
|
||||||
<tr className={styles.testLogRow}>
|
|
||||||
<td colSpan={8}>
|
|
||||||
<div className={styles.testLogContainer}>
|
|
||||||
{result.logs.map((log, idx) => (
|
|
||||||
<div key={idx} className={`${styles.testLogLine} ${_getLogLevelClass(log)}`}>
|
|
||||||
{log}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{(testResults.credentialsReceived || testResults.credentialDebug) && (
|
|
||||||
<div className={styles.testLogContainer} style={{ margin: '0.5rem 0' }}>
|
|
||||||
<div className={`${styles.testLogLine} ${styles.testLogInfo}`}>
|
|
||||||
<strong>Credential-Debug:</strong>
|
|
||||||
</div>
|
|
||||||
{testResults.credentialsReceived && (
|
|
||||||
<div className={`${styles.testLogLine} ${testResults.credentialsReceived.hasEmail ? styles.testLogInfo : styles.testLogError}`}>
|
|
||||||
Bot empfangen: Email={testResults.credentialsReceived.hasEmail ? 'ja' : 'NEIN'} | Passwort={testResults.credentialsReceived.hasPassword ? 'ja' : 'NEIN'}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{testResults.credentialDebug && (
|
|
||||||
<>
|
|
||||||
<div className={`${styles.testLogLine} ${styles.testLogInfo}`}>
|
|
||||||
Suche: mandateId={testResults.credentialDebug.mandateId} | Strategie={testResults.credentialDebug.searchStrategy}
|
|
||||||
</div>
|
|
||||||
<div className={`${styles.testLogLine} ${testResults.credentialDebug.botFound ? styles.testLogInfo : styles.testLogError}`}>
|
|
||||||
Bot gefunden: {testResults.credentialDebug.botFound ? 'ja' : 'NEIN'}
|
|
||||||
{testResults.credentialDebug.botEmail && ` | ${testResults.credentialDebug.botEmail}`}
|
|
||||||
{testResults.credentialDebug.botMandateId && ` | bot-mandate=${testResults.credentialDebug.botMandateId}`}
|
|
||||||
{testResults.credentialDebug.allBotsCount !== undefined && ` | total=${testResults.credentialDebug.allBotsCount}`}
|
|
||||||
</div>
|
|
||||||
{testResults.credentialDebug.passwordDecrypted !== undefined && (
|
|
||||||
<div className={`${styles.testLogLine} ${testResults.credentialDebug.passwordDecrypted ? styles.testLogInfo : styles.testLogError}`}>
|
|
||||||
Passwort entschluesselt: {testResults.credentialDebug.passwordDecrypted ? 'ja' : 'NEIN'}
|
|
||||||
{testResults.credentialDebug.passwordError && ` | Fehler: ${testResults.credentialDebug.passwordError}`}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{testResults.recommendation && (
|
|
||||||
<div className={styles.testRecommendation}>
|
|
||||||
<strong>Empfehlung:</strong> {testResults.recommendation}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Screenshot Overlay */}
|
|
||||||
{screenshotPreview && (
|
|
||||||
<div
|
|
||||||
className={styles.testScreenshotOverlay}
|
|
||||||
onClick={() => setScreenshotPreview(null)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={screenshotPreview.src}
|
|
||||||
alt={screenshotPreview.caption}
|
|
||||||
className={styles.testScreenshotImage}
|
|
||||||
/>
|
|
||||||
<div className={styles.testScreenshotCaption}>
|
|
||||||
{screenshotPreview.caption} — Klicken zum Schliessen
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Bot Identity */}
|
{/* Bot Identity */}
|
||||||
<div className={styles.settingsSection}>
|
<div className={styles.settingsSection}>
|
||||||
<h4 className={styles.sectionTitle}>Bot-Identitaet</h4>
|
<h4 className={styles.sectionTitle}>Bot-Identitaet</h4>
|
||||||
|
|
@ -548,19 +166,9 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
onChange={(e) => _updateField('botName', e.target.value)}
|
onChange={(e) => _updateField('botName', e.target.value)}
|
||||||
placeholder="AI Assistant"
|
placeholder="AI Assistant"
|
||||||
/>
|
/>
|
||||||
<span className={styles.hint}>Wird als Teilnehmer-Name im Meeting angezeigt</span>
|
<span className={styles.hint}>
|
||||||
</div>
|
Default-Name fuer den Bot im Meeting. Falls keiner angegeben, wird der Name des System-Bots verwendet (z.B. "Nyla Larsson").
|
||||||
|
</span>
|
||||||
<div className={styles.formGroup}>
|
|
||||||
<label className={styles.label}>Hintergrundbild-URL</label>
|
|
||||||
<input
|
|
||||||
type="url"
|
|
||||||
className={styles.input}
|
|
||||||
value={formData.backgroundImageUrl || ''}
|
|
||||||
onChange={(e) => _updateField('backgroundImageUrl', e.target.value)}
|
|
||||||
placeholder="https://example.com/background.jpg"
|
|
||||||
/>
|
|
||||||
<span className={styles.hint}>Hintergrundbild fuer den Video-Feed des Bots</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -606,6 +214,22 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
</select>
|
</select>
|
||||||
<span className={styles.hint}>Wie soll der Bot im Meeting antworten?</span>
|
<span className={styles.hint}>Wie soll der Bot im Meeting antworten?</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.formGroup}>
|
||||||
|
<label className={styles.label}>Transfer-Modus</label>
|
||||||
|
<select
|
||||||
|
className={styles.select}
|
||||||
|
value={formData.transferMode || 'auto'}
|
||||||
|
onChange={(e) => _updateField('transferMode', e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="auto">Automatisch - Anonym: Audio, Authentifiziert: Captions</option>
|
||||||
|
<option value="caption">Captions - Teams-Transkript (Text aus Live-Captions)</option>
|
||||||
|
<option value="audio">Audio - Audio-Stream an Gateway (STT in eingestellter Sprache)</option>
|
||||||
|
</select>
|
||||||
|
<span className={styles.hint}>
|
||||||
|
Bei anonymem Join liefert Teams nur englische Captions. Audio-Modus ermoeglicht STT in jeder Sprache ueber den Gateway.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Voice Settings */}
|
{/* Voice Settings */}
|
||||||
|
|
@ -668,15 +292,6 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bot Account - managed by admin, read-only display */}
|
|
||||||
<div className={styles.settingsSection}>
|
|
||||||
<h4 className={styles.sectionTitle}>Bot-Account (Microsoft)</h4>
|
|
||||||
<span className={styles.hint}>
|
|
||||||
System-Bot-Accounts werden vom Administrator verwaltet und sind nicht direkt editierbar.
|
|
||||||
Waehle beim Session-Start den gewuenschten Join-Modus.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Advanced Settings */}
|
{/* Advanced Settings */}
|
||||||
<div className={styles.settingsSection}>
|
<div className={styles.settingsSection}>
|
||||||
<h4 className={styles.sectionTitle}>Erweiterte Einstellungen</h4>
|
<h4 className={styles.sectionTitle}>Erweiterte Einstellungen</h4>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue