feat(teamsbot): dynamic voice API, session auto-load, clean API types
- Dynamic language list from Google TTS API (string codes instead of objects) - Voice test sends botName for AI-generated sample text per language - Session view auto-loads most recent session when no sessionId given - Shows "Keine Sitzungen vorhanden" when no sessions exist - Updated testVoice API to pass botName instead of static text Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
76dd20c1ce
commit
c58bc77154
3 changed files with 40 additions and 15 deletions
|
|
@ -186,16 +186,16 @@ export async function updateConfig(instanceId: string, updates: ConfigUpdateRequ
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test TTS voice with a sample text. Returns base64-encoded audio.
|
* Test TTS voice with AI-generated sample text. Returns base64-encoded audio.
|
||||||
*/
|
*/
|
||||||
export async function testVoice(
|
export async function testVoice(
|
||||||
instanceId: string,
|
instanceId: string,
|
||||||
text: string,
|
botName: string,
|
||||||
language: string,
|
language: string,
|
||||||
voiceId?: string,
|
voiceId?: string,
|
||||||
): Promise<{ success: boolean; audio?: string; format?: string; error?: string }> {
|
): Promise<{ success: boolean; audio?: string; format?: string; text?: string; error?: string }> {
|
||||||
const response = await api.post(`/api/teamsbot/${instanceId}/voice/test`, {
|
const response = await api.post(`/api/teamsbot/${instanceId}/voice/test`, {
|
||||||
text,
|
botName,
|
||||||
language,
|
language,
|
||||||
voiceId,
|
voiceId,
|
||||||
});
|
});
|
||||||
|
|
@ -204,8 +204,9 @@ export async function testVoice(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch available TTS languages from Google Cloud.
|
* Fetch available TTS languages from Google Cloud.
|
||||||
|
* Returns array of language codes (e.g. ["de-DE", "en-US", ...])
|
||||||
*/
|
*/
|
||||||
export async function fetchLanguages(): Promise<VoiceLanguage[]> {
|
export async function fetchLanguages(): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
const response = await api.get('/voice-google/languages');
|
const response = await api.get('/voice-google/languages');
|
||||||
return response.data?.languages || [];
|
return response.data?.languages || [];
|
||||||
|
|
|
||||||
|
|
@ -11,25 +11,44 @@ import styles from './Teamsbot.module.css';
|
||||||
export const TeamsbotSessionView: React.FC = () => {
|
export const TeamsbotSessionView: React.FC = () => {
|
||||||
const { instance } = useCurrentInstance();
|
const { instance } = useCurrentInstance();
|
||||||
const instanceId = instance?.id || '';
|
const instanceId = instance?.id || '';
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const sessionId = searchParams.get('sessionId') || '';
|
const sessionId = searchParams.get('sessionId') || '';
|
||||||
|
|
||||||
const [session, setSession] = useState<TeamsbotSession | null>(null);
|
const [session, setSession] = useState<TeamsbotSession | null>(null);
|
||||||
const [transcripts, setTranscripts] = useState<TeamsbotTranscript[]>([]);
|
const [transcripts, setTranscripts] = useState<TeamsbotTranscript[]>([]);
|
||||||
const [botResponses, setBotResponses] = useState<TeamsbotBotResponse[]>([]);
|
const [botResponses, setBotResponses] = useState<TeamsbotBotResponse[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [noSessions, setNoSessions] = useState(false);
|
||||||
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 transcriptEndRef = useRef<HTMLDivElement>(null);
|
const transcriptEndRef = useRef<HTMLDivElement>(null);
|
||||||
const eventSourceRef = useRef<EventSource | null>(null);
|
const eventSourceRef = useRef<EventSource | null>(null);
|
||||||
|
|
||||||
// Load session data
|
// Load session data - if no sessionId given, load the most recent session
|
||||||
const _loadSession = useCallback(async () => {
|
const _loadSession = useCallback(async () => {
|
||||||
if (!instanceId || !sessionId) return;
|
if (!instanceId) return;
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await teamsbotApi.getSession(instanceId, sessionId);
|
setNoSessions(false);
|
||||||
|
|
||||||
|
let targetSessionId = sessionId;
|
||||||
|
|
||||||
|
// No sessionId in URL -> find the most recent session
|
||||||
|
if (!targetSessionId) {
|
||||||
|
const listResult = await teamsbotApi.listSessions(instanceId, true);
|
||||||
|
const sessions = listResult.sessions || [];
|
||||||
|
if (sessions.length === 0) {
|
||||||
|
setNoSessions(true);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Pick the most recent (first in list, sorted by creation date desc)
|
||||||
|
targetSessionId = sessions[0].id;
|
||||||
|
setSearchParams({ sessionId: targetSessionId }, { replace: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await teamsbotApi.getSession(instanceId, targetSessionId);
|
||||||
setSession(result.session);
|
setSession(result.session);
|
||||||
setTranscripts(result.transcripts || []);
|
setTranscripts(result.transcripts || []);
|
||||||
setBotResponses(result.botResponses || []);
|
setBotResponses(result.botResponses || []);
|
||||||
|
|
@ -39,7 +58,7 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [instanceId, sessionId]);
|
}, [instanceId, sessionId, setSearchParams]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
_loadSession();
|
_loadSession();
|
||||||
|
|
@ -134,6 +153,12 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) return <div className={styles.loading}>Lade Sitzung...</div>;
|
if (loading) return <div className={styles.loading}>Lade Sitzung...</div>;
|
||||||
|
if (noSessions) return (
|
||||||
|
<div className={styles.emptyState || styles.loading}>
|
||||||
|
<p>Keine Sitzungen vorhanden.</p>
|
||||||
|
<p>Starte eine neue Sitzung im <strong>Dashboard</strong>.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
if (!session) return <div className={styles.errorBanner}>Sitzung nicht gefunden</div>;
|
if (!session) return <div className={styles.errorBanner}>Sitzung nicht gefunden</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
const [formData, setFormData] = useState<ConfigUpdateRequest>({});
|
const [formData, setFormData] = useState<ConfigUpdateRequest>({});
|
||||||
|
|
||||||
// Dynamic voice data from Google TTS API
|
// Dynamic voice data from Google TTS API
|
||||||
const [languages, setLanguages] = useState<VoiceLanguage[]>([]);
|
const [languages, setLanguages] = useState<string[]>([]);
|
||||||
const [voices, setVoices] = useState<VoiceOption[]>([]);
|
const [voices, setVoices] = useState<VoiceOption[]>([]);
|
||||||
const [loadingVoices, setLoadingVoices] = useState(false);
|
const [loadingVoices, setLoadingVoices] = useState(false);
|
||||||
|
|
||||||
|
|
@ -111,8 +111,7 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
const language = formData.language || 'de-DE';
|
const language = formData.language || 'de-DE';
|
||||||
const botName = formData.botName || 'AI Assistant';
|
const botName = formData.botName || 'AI Assistant';
|
||||||
const testText = `Hallo, ich bin ${botName}. So klinge ich in diesem Meeting.`;
|
const result = await teamsbotApi.testVoice(instanceId, botName, language, formData.voiceId);
|
||||||
const result = await teamsbotApi.testVoice(instanceId, testText, language, formData.voiceId);
|
|
||||||
|
|
||||||
if (result.success && result.audio) {
|
if (result.success && result.audio) {
|
||||||
// Play audio from base64
|
// Play audio from base64
|
||||||
|
|
@ -221,8 +220,8 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
onChange={(e) => _handleLanguageChange(e.target.value)}
|
onChange={(e) => _handleLanguageChange(e.target.value)}
|
||||||
>
|
>
|
||||||
{languages.length > 0 ? (
|
{languages.length > 0 ? (
|
||||||
languages.map((lang, idx) => (
|
languages.map((langCode, idx) => (
|
||||||
<option key={`${lang.code}-${idx}`} value={lang.code}>{lang.name} ({lang.code})</option>
|
<option key={`${langCode}-${idx}`} value={langCode}>{langCode}</option>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue