diff --git a/src/api/teamsbotApi.ts b/src/api/teamsbotApi.ts index 8f201c6..627ecc9 100644 --- a/src/api/teamsbotApi.ts +++ b/src/api/teamsbotApi.ts @@ -71,6 +71,7 @@ export interface TeamsbotConfig { triggerCooldownSeconds: number; contextWindowSegments: number; debugMode?: boolean; + avatarFileId?: string; } export interface TeamsbotSessionStats { @@ -103,6 +104,7 @@ export interface ConfigUpdateRequest { triggerCooldownSeconds?: number; contextWindowSegments?: number; debugMode?: boolean; + avatarFileId?: string; } // Voice option type re-exported from the central voice catalog API @@ -602,6 +604,7 @@ export interface MeetingModule { kpiTargets?: string; defaultMeetingLink?: string; defaultBotName?: string; + defaultAvatarFileId?: string; status: string; } @@ -612,7 +615,7 @@ export async function listModules(instanceId: string): Promise export async function createModule(instanceId: string, body: { title: string; seriesType?: string; defaultBotId?: string; goals?: string; kpiTargets?: string; - defaultMeetingLink?: string; defaultBotName?: string; + defaultMeetingLink?: string; defaultBotName?: string; defaultAvatarFileId?: string; }): Promise { const response = await api.post(`/api/teamsbot/${instanceId}/modules`, body); return response.data?.module; @@ -631,3 +634,31 @@ export async function updateModule(instanceId: string, moduleId: string, body: P export async function deleteModule(instanceId: string, moduleId: string): Promise { await api.delete(`/api/teamsbot/${instanceId}/modules/${moduleId}`); } + +export interface MediaFileInfo { + id: string; + fileName: string; + mimeType: string; +} + +export async function listMediaFiles(): Promise { + const response = await api.get('/api/files/list', { + params: { pagination: JSON.stringify({ pageSize: 500 }) }, + }); + const data = response.data; + let items: any[]; + if (Array.isArray(data)) { + items = data; + } else if (Array.isArray(data?.items)) { + items = data.items; + } else { + console.warn('[listMediaFiles] unexpected response shape:', Object.keys(data || {})); + items = []; + } + const filtered = items.filter((f: any) => { + const mime = (f.mimeType || '').toLowerCase(); + return mime.startsWith('image/') || mime.startsWith('video/'); + }); + console.log(`[listMediaFiles] ${items.length} total files, ${filtered.length} media files`); + return filtered.map((f: any) => ({ id: f.id, fileName: f.fileName, mimeType: f.mimeType })); +} diff --git a/src/pages/views/teamsbot/TeamsbotModulesView.tsx b/src/pages/views/teamsbot/TeamsbotModulesView.tsx index ad1526d..9e64240 100644 --- a/src/pages/views/teamsbot/TeamsbotModulesView.tsx +++ b/src/pages/views/teamsbot/TeamsbotModulesView.tsx @@ -7,8 +7,10 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useCurrentInstance } from '../../../hooks/useCurrentInstance'; import * as teamsbotApi from '../../../api/teamsbotApi'; -import type { MeetingModule, TeamsbotSession } from '../../../api/teamsbotApi'; +import type { MeetingModule, TeamsbotSession, MediaFileInfo } from '../../../api/teamsbotApi'; import { useLanguage } from '../../../providers/language/LanguageContext'; +import { useFileContext } from '../../../contexts/FileContext'; +import { FaSpinner } from 'react-icons/fa'; import styles from './Teamsbot.module.css'; const SERIES_TYPE_LABELS: Record = { @@ -46,8 +48,47 @@ export const TeamsbotModulesView: React.FC = () => { const [createDefaultLink, setCreateDefaultLink] = useState(''); const [createDefaultBotName, setCreateDefaultBotName] = useState(''); const [createGoals, setCreateGoals] = useState(''); + const [createDefaultAvatarFileId, setCreateDefaultAvatarFileId] = useState(''); const [createSaving, setCreateSaving] = useState(false); + const [mediaFiles, setMediaFiles] = useState([]); + const fileCtx = useFileContext(); + const avatarInputRef = useRef(null); + const [avatarUploading, setAvatarUploading] = useState(false); + const [avatarTarget, setAvatarTarget] = useState<'create' | 'edit'>('create'); + + const _refreshMediaFiles = useCallback(async () => { + const result = await teamsbotApi.listMediaFiles().catch(() => [] as MediaFileInfo[]); + setMediaFiles(result); + }, []); + + const _handleAvatarUpload = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file || !fileCtx?.handleFileUpload) return; + setAvatarUploading(true); + try { + const result = await fileCtx.handleFileUpload(file); + if (result?.success) { + const data: any = (result.fileData as any)?.file || result.fileData; + const id = data?.id || (result.fileData as any)?.id; + if (id) { + if (avatarTarget === 'create') { + setCreateDefaultAvatarFileId(id); + } else if (editingModule) { + setEditingModule({ ...editingModule, defaultAvatarFileId: id }); + } + const refreshed = await teamsbotApi.listMediaFiles().catch(() => [] as MediaFileInfo[]); + setMediaFiles(refreshed); + } + } + } catch { + // upload error handled by FileContext + } finally { + setAvatarUploading(false); + if (avatarInputRef.current) avatarInputRef.current.value = ''; + } + }; + const _loadModules = useCallback(async () => { if (!instanceId) return; setLoading(true); @@ -63,6 +104,10 @@ export const TeamsbotModulesView: React.FC = () => { useEffect(() => { _loadModules(); }, [_loadModules]); + useEffect(() => { + teamsbotApi.listMediaFiles().then(setMediaFiles).catch(() => {}); + }, []); + const _loadModuleSessions = useCallback(async (moduleId: string) => { if (!instanceId) return; try { @@ -125,6 +170,7 @@ export const TeamsbotModulesView: React.FC = () => { seriesType: createSeriesType, defaultMeetingLink: createDefaultLink.trim() || undefined, defaultBotName: createDefaultBotName.trim() || undefined, + defaultAvatarFileId: createDefaultAvatarFileId || undefined, goals: createGoals.trim() || undefined, }); setCreateOpen(false); @@ -132,6 +178,7 @@ export const TeamsbotModulesView: React.FC = () => { setCreateSeriesType('adhoc'); setCreateDefaultLink(''); setCreateDefaultBotName(''); + setCreateDefaultAvatarFileId(''); setCreateGoals(''); _loadModules(); } catch (err) { @@ -185,7 +232,7 @@ export const TeamsbotModulesView: React.FC = () => {
_toggleExpand(mod.id)}> {t(SERIES_TYPE_LABELS[mod.seriesType] || mod.seriesType)} {mod.title} - {t(STATUS_LABELS[mod.status] || mod.status)} + {t(STATUS_LABELS[mod.status] || mod.status || 'Aktiv')}
+