ui-nyla/src/pages/views/teamsbot/TeamsbotAssistantView.tsx
2026-05-08 00:15:26 +02:00

190 lines
6.7 KiB
TypeScript

/**
* TeamsBot Assistant View
*
* Wizard: Select/create module → Meeting link → Bot selection → "Start bot"
*/
import React, { useState, useEffect, useCallback } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
import * as teamsbotApi from '../../../api/teamsbotApi';
import { useLanguage } from '../../../providers/language/LanguageContext';
import styles from './Teamsbot.module.css';
type WizardStep = 'module' | 'meeting' | 'bot' | 'confirm';
const STEPS: WizardStep[] = ['module', 'meeting', 'bot', 'confirm'];
export const TeamsbotAssistantView: React.FC = () => {
const { t } = useLanguage();
const { instance, mandateId } = useCurrentInstance();
const instanceId = instance?.id || '';
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const preselectedModuleId = searchParams.get('moduleId');
const [step, setStep] = useState<WizardStep>(preselectedModuleId ? 'meeting' : 'module');
const [modules, setModules] = useState<any[]>([]);
const [selectedModuleId, setSelectedModuleId] = useState<string | null>(preselectedModuleId);
const [newModuleTitle, setNewModuleTitle] = useState('');
const [createNewModule, setCreateNewModule] = useState(false);
const [meetingLink, setMeetingLink] = useState('');
const [botName, setBotName] = useState('AI Assistant');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const stepIdx = STEPS.indexOf(step);
const _loadModules = useCallback(async () => {
if (!instanceId) return;
try {
const result = await teamsbotApi.listModules(instanceId);
setModules(result || []);
} catch (err) {
console.error('Failed to load modules:', err);
}
}, [instanceId]);
useEffect(() => { _loadModules(); }, [_loadModules]);
const _handleNext = () => {
const nextIdx = stepIdx + 1;
if (nextIdx < STEPS.length) setStep(STEPS[nextIdx]);
};
const _handleBack = () => {
const prevIdx = stepIdx - 1;
if (prevIdx >= 0) setStep(STEPS[prevIdx]);
};
const _handleStart = async () => {
if (!meetingLink.trim()) {
setError(t('Meeting-Link erforderlich'));
return;
}
setLoading(true);
setError(null);
try {
let moduleId = selectedModuleId;
if (createNewModule && newModuleTitle.trim()) {
const mod = await teamsbotApi.createModule(instanceId, { title: newModuleTitle.trim() });
moduleId = mod.id;
}
const result = await teamsbotApi.startSession(instanceId, {
meetingLink: meetingLink.trim(),
botName,
moduleId: moduleId || undefined,
} as any);
navigate(`/mandates/${mandateId}/teamsbot/${instanceId}/sessions?sessionId=${result.session.id}`);
} catch (err: any) {
setError(err?.message || t('Fehler beim Starten'));
} finally {
setLoading(false);
}
};
return (
<div className={styles.assistantContainer}>
<div className={styles.wizardHeader}>
<h2>{t('Neues Meeting starten')}</h2>
<div className={styles.stepIndicator}>
{STEPS.map((s, i) => (
<div key={s} className={`${styles.stepDot} ${i <= stepIdx ? styles.stepActive : ''}`} />
))}
</div>
</div>
{error && <div className={styles.errorBanner}>{error}</div>}
<div className={styles.wizardContent}>
{step === 'module' && (
<div className={styles.wizardStep}>
<h3>{t('Meeting-Modul wählen')}</h3>
<div className={styles.moduleChoice}>
<label>
<input type="radio" checked={!createNewModule} onChange={() => setCreateNewModule(false)} />
{t('Bestehendes Modul')}
</label>
{!createNewModule && (
<select
value={selectedModuleId || ''}
onChange={e => setSelectedModuleId(e.target.value || null)}
className={styles.wizardSelect}
>
<option value="">{t('Kein Modul (Adhoc)')}</option>
{modules.map(m => (
<option key={m.id} value={m.id}>{m.title}</option>
))}
</select>
)}
<label>
<input type="radio" checked={createNewModule} onChange={() => setCreateNewModule(true)} />
{t('Neues Modul erstellen')}
</label>
{createNewModule && (
<input
type="text"
className={styles.wizardInput}
placeholder={t('z.B. Weekly Standup, Q3 Review...')}
value={newModuleTitle}
onChange={e => setNewModuleTitle(e.target.value)}
/>
)}
</div>
</div>
)}
{step === 'meeting' && (
<div className={styles.wizardStep}>
<h3>{t('Meeting-Link')}</h3>
<input
type="text"
className={styles.wizardInput}
placeholder="https://teams.microsoft.com/l/meetup-join/..."
value={meetingLink}
onChange={e => setMeetingLink(e.target.value)}
autoFocus
/>
</div>
)}
{step === 'bot' && (
<div className={styles.wizardStep}>
<h3>{t('Bot-Name')}</h3>
<input
type="text"
className={styles.wizardInput}
value={botName}
onChange={e => setBotName(e.target.value)}
/>
</div>
)}
{step === 'confirm' && (
<div className={styles.wizardStep}>
<h3>{t('Zusammenfassung')}</h3>
<div className={styles.confirmSummary}>
<div><strong>{t('Modul')}:</strong> {createNewModule ? newModuleTitle : (modules.find(m => m.id === selectedModuleId)?.title || t('Adhoc'))}</div>
<div><strong>{t('Meeting')}:</strong> {meetingLink}</div>
<div><strong>{t('Bot')}:</strong> {botName}</div>
</div>
</div>
)}
</div>
<div className={styles.wizardActions}>
{stepIdx > 0 && (
<button className={styles.btnSecondary} onClick={_handleBack}>{t('Zurück')}</button>
)}
<div style={{ flex: 1 }} />
{step !== 'confirm' ? (
<button className={styles.btnPrimary} onClick={_handleNext}>{t('Weiter')}</button>
) : (
<button className={styles.btnPrimary} onClick={_handleStart} disabled={loading}>
{loading ? t('Starte...') : t('Bot starten')}
</button>
)}
</div>
</div>
);
};