Skip to content
Extraits de code Groupes Projets
Valider 3e12c857 rédigé par zakariaeyahya's avatar zakariaeyahya
Parcourir les fichiers

add integration for configuration assitant

parent 4e943221
Branches
Étiquettes
1 requête de fusion!94Resolve IA-439 "Feauture/"
"use client";
// Directive to indicate a client component
import { useParams } from 'next/navigation';
import { useEffect } from 'react';
import AIAssistantSettings from 'src/shared/sections/ai/assistants-management/ai-assistant-settings';
export default function AssistantSettingsPage() {
const params = useParams();
const assistantId = params.id as string;
// ✅ AJOUT 1: Logging pour debug
useEffect(() => {
console.log('🔍 Assistant Settings Page loaded');
console.log('📝 Assistant ID from URL:', assistantId);
console.log('📝 Params:', params);
}, [assistantId, params]);
// ✅ AJOUT 2: Validation simple de l'ID
if (!assistantId) {
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h2>❌ ID d'assistant manquant</h2>
<p>Veuillez vérifier l'URL et réessayer.</p>
</div>
);
}
// ✅ AJOUT 3: Validation que l'ID est un nombre valide
if (isNaN(Number(assistantId))) {
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h2>❌ ID d'assistant invalide</h2>
<p>L'ID doit être un nombre. ID reçu: {assistantId}</p>
</div>
);
}
// ✅ AJOUT 4: Log de confirmation avant rendu
console.log('✅ Rendering AIAssistantSettings with ID:', assistantId);
return <AIAssistantSettings assistantId={assistantId} />;
}
\ No newline at end of file
import { useEffect } from 'react';
import { useAssistantResponseConfigStore } from 'src/shared/api/stores/assistantResponseConfigStore';
export function useAssistantConfig(assistantId: string | number) {
const {
responseConfig,
loading,
error,
getResponseConfig,
addMotivationalPhrase,
removeMotivationalPhrase,
toggleVoiceTranscription,
toggleImageSupport,
resetToDefaults,
clearError
} = useAssistantResponseConfigStore();
const assistantIdStr = assistantId.toString();
useEffect(() => {
if (assistantIdStr && assistantIdStr !== '0' && assistantIdStr !== '') {
console.log("🔍 Loading config for assistant:", assistantIdStr);
getResponseConfig(assistantIdStr);
} else {
console.error("❌ Invalid assistantId:", assistantIdStr);
}
}, [assistantIdStr, getResponseConfig]);
// Fonctions wrapper pour simplifier l'usage
const handleAddPhrase = async (phrase: string) => {
if (phrase.trim()) {
await addMotivationalPhrase(assistantIdStr, phrase.trim());
}
};
const handleRemovePhrase = async (phrase: string) => {
await removeMotivationalPhrase(assistantIdStr, phrase);
};
const handleToggleVoice = async (enabled: boolean) => {
await toggleVoiceTranscription(assistantIdStr, enabled);
};
const handleToggleImage = async (enabled: boolean) => {
await toggleImageSupport(assistantIdStr, enabled);
};
const handleReset = async () => {
await resetToDefaults(assistantIdStr);
};
return {
// État
config: responseConfig,
loading,
error,
// Actions simplifiées
addPhrase: handleAddPhrase,
removePhrase: handleRemovePhrase,
toggleVoice: handleToggleVoice,
toggleImage: handleToggleImage,
reset: handleReset,
clearError,
// Raccourcis vers les données
motivationalPhrases: responseConfig?.motivationalPhrases || [],
audioFormats: responseConfig?.audioFormats || [],
voiceTranscription: responseConfig?.voiceTranscription || false,
imageSupport: responseConfig?.imageSupport || false,
welcomeMessage: responseConfig?.welcomeMessage || ''
};
}
\ No newline at end of file
import { GATEWAY_API_URL } from 'src/config-global';
const configPrefix = `${GATEWAY_API_URL}/api/assistants`;
export const configEndpoints = {
responseConfig: {
get: (assistantId: string) => `${configPrefix}/${assistantId}/response-config`,
update: (assistantId: string) => `${configPrefix}/${assistantId}/response-config`,
motivationalPhrases: (assistantId: string) => `${configPrefix}/${assistantId}/response-config/motivational-phrases`,
audioFormats: (assistantId: string) => `${configPrefix}/${assistantId}/response-config/audio-formats`,
toggleVoice: (assistantId: string) => `${configPrefix}/${assistantId}/response-config/voice-transcription/toggle`,
toggleImage: (assistantId: string) => `${configPrefix}/${assistantId}/response-config/image-support/toggle`,
reset: (assistantId: string) => `${configPrefix}/${assistantId}/response-config/reset`,
exists: (assistantId: string) => `${configPrefix}/${assistantId}/response-config/exists`,
initialize: (assistantId: string) => `${configPrefix}/${assistantId}/response-config/initialize`,
delete: (assistantId: string) => `${configPrefix}/${assistantId}/response-config`
},
detailLevel: {
get: (assistantId: string) => `${configPrefix}/${assistantId}/detail-level`,
update: (assistantId: string) => `${configPrefix}/${assistantId}/detail-level`,
descriptions: `${configPrefix}/detail-level/descriptions`,
examples: `${configPrefix}/detail-level/examples`,
reset: (assistantId: string) => `${configPrefix}/${assistantId}/detail-level/reset`
},
languageConfig: {
get: (assistantId: string) => `${configPrefix}/${assistantId}/language-config`,
update: (assistantId: string) => `${configPrefix}/${assistantId}/language-config`,
supportedLanguages: `${configPrefix}/language-config/supported-languages`,
toggleLanguage: (assistantId: string, languageCode: string) =>
`${configPrefix}/${assistantId}/language-config/languages/${languageCode}/toggle`,
primaryLanguage: (assistantId: string) =>
`${configPrefix}/${assistantId}/language-config/primary-language`,
secondaryLanguage: (assistantId: string) =>
`${configPrefix}/${assistantId}/language-config/secondary-language`,
communicationStyle: (assistantId: string) =>
`${configPrefix}/${assistantId}/language-config/communication-style`,
reset: (assistantId: string) => `${configPrefix}/${assistantId}/language-config/reset`
},
filteringConfig: {
get: (assistantId: string) => `${configPrefix}/${assistantId}/filtering-config`,
updateRules: (assistantId: string) => `${configPrefix}/${assistantId}/filtering-config/rules`,
createRule: (assistantId: string) => `${configPrefix}/${assistantId}/filtering-config/rules`,
updateRule: (assistantId: string, ruleId: string) =>
`${configPrefix}/${assistantId}/filtering-config/rules/${ruleId}`,
deleteRule: (assistantId: string, ruleId: string) =>
`${configPrefix}/${assistantId}/filtering-config/rules/${ruleId}`,
systemRules: `${configPrefix}/filtering-config/system-rules`,
blockedTermsAssistant: (assistantId: string) =>
`${configPrefix}/${assistantId}/filtering-config/blocked-terms/assistant`,
blockedTermsStudents: (assistantId: string) =>
`${configPrefix}/${assistantId}/filtering-config/blocked-terms/students`,
reset: (assistantId: string) => `${configPrefix}/${assistantId}/filtering-config/reset`,
updateKnowledgeBase: (assistantId: string) =>
`${configPrefix}/${assistantId}/filtering-config/knowledge-base/update`
},
testing: {
submitTest: (assistantId: string) => `${configPrefix}/${assistantId}/testing/test-query`,
getHistory: (assistantId: string) => `${configPrefix}/${assistantId}/testing/history`,
evaluate: (assistantId: string, testId: string) =>
`${configPrefix}/${assistantId}/testing/history/${testId}/evaluate`,
deleteTest: (assistantId: string, testId: string) =>
`${configPrefix}/${assistantId}/testing/history/${testId}`,
clearHistory: (assistantId: string) => `${configPrefix}/${assistantId}/testing/history`,
statistics: (assistantId: string) => `${configPrefix}/${assistantId}/testing/statistics`,
report: (assistantId: string) => `${configPrefix}/${assistantId}/testing/report`
},
history: {
get: (assistantId: string) => `${configPrefix}/${assistantId}/config-history`,
getBySection: (assistantId: string, section: string) =>
`${configPrefix}/${assistantId}/config-history/section/${section}`,
getByUser: (assistantId: string, userId: string) =>
`${configPrefix}/${assistantId}/config-history/user/${userId}`,
getByDateRange: (assistantId: string) => `${configPrefix}/${assistantId}/config-history/date-range`,
clear: (assistantId: string) => `${configPrefix}/${assistantId}/config-history`
}
};
\ No newline at end of file
import { create } from 'zustand';
import axiosInstance from 'src/utils/axios';
import { configEndpoints } from 'src/shared/api/endpoints/assistant-config';
import type {
AssistantResponseConfigDTO,
UpdateResponseConfigRequest,
ApiResponse
} from 'src/types/assistant-config';
export interface AssistantResponseConfigStore {
// State
responseConfig: AssistantResponseConfigDTO | null;
loading: boolean;
error: string | null;
// Actions
getResponseConfig: (assistantId: string) => Promise<void>;
updateResponseConfig: (assistantId: string, request: UpdateResponseConfigRequest) => Promise<void>;
updateMotivationalPhrases: (assistantId: string, phrases: string[]) => Promise<void>;
addMotivationalPhrase: (assistantId: string, phrase: string) => Promise<void>;
removeMotivationalPhrase: (assistantId: string, phrase: string) => Promise<void>;
updateAudioFormats: (assistantId: string, formats: string[]) => Promise<void>;
toggleVoiceTranscription: (assistantId: string, enabled: boolean) => Promise<void>;
toggleImageSupport: (assistantId: string, enabled: boolean) => Promise<void>;
resetToDefaults: (assistantId: string) => Promise<void>;
clearError: () => void;
clearConfig: () => void;
}
export const useAssistantResponseConfigStore = create<AssistantResponseConfigStore>((set, get) => ({
// ============================================================================
// STATE INITIAL
// ============================================================================
responseConfig: null,
loading: false,
error: null,
// ============================================================================
// RÉCUPÉRATION DE LA CONFIGURATION
// ============================================================================
getResponseConfig: async (assistantId: string) => {
set({ loading: true, error: null });
try {
const response = await axiosInstance.get<ApiResponse<AssistantResponseConfigDTO>>(
configEndpoints.responseConfig.get(assistantId)
);
if (response.data.success) {
set({
responseConfig: response.data.data,
loading: false
});
} else {
set({
error: response.data.message || 'Erreur lors de la récupération',
loading: false
});
}
} catch (error: any) {
set({
error: error.message || 'Erreur de connexion',
loading: false
});
}
},
// ============================================================================
// MISE À JOUR COMPLÈTE
// ============================================================================
updateResponseConfig: async (assistantId: string, request: UpdateResponseConfigRequest) => {
set({ loading: true, error: null });
try {
const response = await axiosInstance.put<ApiResponse<AssistantResponseConfigDTO>>(
configEndpoints.responseConfig.update(assistantId),
request
);
if (response.data.success) {
set({
responseConfig: response.data.data,
loading: false
});
} else {
set({
error: response.data.message || 'Erreur lors de la mise à jour',
loading: false
});
}
} catch (error: any) {
set({
error: error.message || 'Erreur de connexion',
loading: false
});
}
},
// ============================================================================
// GESTION DES PHRASES DE MOTIVATION
// ============================================================================
updateMotivationalPhrases: async (assistantId: string, phrases: string[]) => {
set({ loading: true, error: null });
try {
const response = await axiosInstance.put<ApiResponse<string>>(
configEndpoints.responseConfig.motivationalPhrases(assistantId),
phrases
);
if (response.data.success) {
// Rafraîchir la configuration
await get().getResponseConfig(assistantId);
} else {
set({
error: response.data.message || 'Erreur lors de la mise à jour des phrases',
loading: false
});
}
} catch (error: any) {
set({
error: error.message || 'Erreur de connexion',
loading: false
});
}
},
addMotivationalPhrase: async (assistantId: string, phrase: string) => {
set({ loading: true, error: null });
try {
const response = await axiosInstance.post<ApiResponse<string>>(
`${configEndpoints.responseConfig.motivationalPhrases(assistantId)}?phrase=${encodeURIComponent(phrase)}`
);
if (response.data.success) {
// Rafraîchir la configuration
await get().getResponseConfig(assistantId);
} else {
set({
error: response.data.message || 'Erreur lors de l\'ajout de la phrase',
loading: false
});
}
} catch (error: any) {
set({
error: error.message || 'Erreur de connexion',
loading: false
});
}
},
removeMotivationalPhrase: async (assistantId: string, phrase: string) => {
set({ loading: true, error: null });
try {
const response = await axiosInstance.delete<ApiResponse<string>>(
`${configEndpoints.responseConfig.motivationalPhrases(assistantId)}?phrase=${encodeURIComponent(phrase)}`
);
if (response.data.success) {
// Rafraîchir la configuration
await get().getResponseConfig(assistantId);
} else {
set({
error: response.data.message || 'Erreur lors de la suppression de la phrase',
loading: false
});
}
} catch (error: any) {
set({
error: error.message || 'Erreur de connexion',
loading: false
});
}
},
// ============================================================================
// GESTION DES FORMATS AUDIO
// ============================================================================
updateAudioFormats: async (assistantId: string, formats: string[]) => {
set({ loading: true, error: null });
try {
const response = await axiosInstance.put<ApiResponse<string>>(
configEndpoints.responseConfig.audioFormats(assistantId),
formats
);
if (response.data.success) {
// Rafraîchir la configuration
await get().getResponseConfig(assistantId);
} else {
set({
error: response.data.message || 'Erreur lors de la mise à jour des formats audio',
loading: false
});
}
} catch (error: any) {
set({
error: error.message || 'Erreur de connexion',
loading: false
});
}
},
// ============================================================================
// BASCULEMENT DES FONCTIONNALITÉS
// ============================================================================
toggleVoiceTranscription: async (assistantId: string, enabled: boolean) => {
set({ loading: true, error: null });
try {
const response = await axiosInstance.post<ApiResponse<string>>(
`${configEndpoints.responseConfig.toggleVoice(assistantId)}?enabled=${enabled}`
);
if (response.data.success) {
// Rafraîchir la configuration
await get().getResponseConfig(assistantId);
} else {
set({
error: response.data.message || 'Erreur lors du basculement de la transcription',
loading: false
});
}
} catch (error: any) {
set({
error: error.message || 'Erreur de connexion',
loading: false
});
}
},
toggleImageSupport: async (assistantId: string, enabled: boolean) => {
set({ loading: true, error: null });
try {
const response = await axiosInstance.post<ApiResponse<string>>(
`${configEndpoints.responseConfig.toggleImage(assistantId)}?enabled=${enabled}`
);
if (response.data.success) {
// Rafraîchir la configuration
await get().getResponseConfig(assistantId);
} else {
set({
error: response.data.message || 'Erreur lors du basculement du support d\'images',
loading: false
});
}
} catch (error: any) {
set({
error: error.message || 'Erreur de connexion',
loading: false
});
}
},
// ============================================================================
// RÉINITIALISATION
// ============================================================================
resetToDefaults: async (assistantId: string) => {
set({ loading: true, error: null });
try {
const response = await axiosInstance.post<ApiResponse<string>>(
configEndpoints.responseConfig.reset(assistantId)
);
if (response.data.success) {
// Rafraîchir la configuration
await get().getResponseConfig(assistantId);
} else {
set({
error: response.data.message || 'Erreur lors de la réinitialisation',
loading: false
});
}
} catch (error: any) {
set({
error: error.message || 'Erreur de connexion',
loading: false
});
}
},
// ============================================================================
// UTILITAIRES
// ============================================================================
clearError: () => {
set({ error: null });
},
clearConfig: () => {
set({ responseConfig: null, error: null });
}
}));
\ No newline at end of file
......@@ -53,7 +53,7 @@ export default function AIAssistantSettings({ assistantId }: AIAssistantSettings
{
id: "customization",
label: "Gestion des Réponses IA",
component: <AIAssistantCustomization />
component: <AIAssistantCustomization assistantId={assistantId} />
},
{
id: "detail-level",
......
......@@ -11,7 +11,6 @@ import {
faPlusCircle,
faMicrophone
} from "@fortawesome/free-solid-svg-icons";
import Box from "@mui/material/Box";
import List from "@mui/material/List";
import Alert from "@mui/material/Alert";
......@@ -25,15 +24,22 @@ import TextField from "@mui/material/TextField";
import { useTheme } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
import CircularProgress from "@mui/material/CircularProgress";
import { useAssistantResponseConfigStore } from "src/shared/api/stores/assistantResponseConfigStore";
import type {
UpdateResponseConfigRequest,
AssistantResponseConfigDTO
} from "src/types/assistant-config";
import { AIAssistantHistoryService } from "../AIAssistantHistoryService";
import { AIAssistantCustomizationService } from "./AIAssistantCustomizationService";
// Define types for better readability and type safety
export type ResponseType = "text" | "audio" | "image";
export type ResponseType = "TEXT" | "AUDIO" | "IMAGE";
export type InputType = "text" | "audio" | "image";
export type AudioFormat = "mp3" | "wav" | "aac";
export type ImageFormat = "jpg" | "png" | "svg" | "pdf";
export type AudioFormat = "MP3" | "WAV" | "AAC";
export type ImageFormat = "JPG" | "PNG" | "PDF";
export type DeletionType = "motivation" | "aide";
export interface DeletionHistoryItem {
......@@ -48,7 +54,7 @@ export interface IAIAssistantCustomizationSettings {
welcomeMessage: string;
motivationalPhrases: string[];
helpPhrases: string[];
audioFormat: AudioFormat[]; // Ici on utilise un array d'AudioFormat
audioFormat: AudioFormat[];
voiceTranscription: boolean;
imageSupport: boolean;
imageFormat: ImageFormat[];
......@@ -66,7 +72,8 @@ type ActionType =
| { type: 'RESTORE_PHRASE', index: number }
| { type: 'DELETE_HISTORY_ITEM', index: number }
| { type: 'CLEAR_HISTORY' }
| { type: 'RESET_SETTINGS' };
| { type: 'RESET_SETTINGS' }
| { type: 'LOAD_FROM_API', config: AssistantResponseConfigDTO };
// Reducer function for state management
const settingsReducer = (state: IAIAssistantCustomizationSettings, action: ActionType): IAIAssistantCustomizationSettings => {
......@@ -161,18 +168,32 @@ const settingsReducer = (state: IAIAssistantCustomizationSettings, action: Actio
case 'RESET_SETTINGS':
return {
responseType: ["text"],
responseType: ["TEXT"],
inputType: "text",
welcomeMessage: "Bonjour ! Comment puis-je vous aider ?",
motivationalPhrases: ["Excellent travail !", "Continue comme ça !", "Tu progresses bien !"],
helpPhrases: ["Je vais t'expliquer la règle...", "Voici comment conjuguer ce verbe..."],
audioFormat: ["mp3"], // Modifié pour être un array
audioFormat: ["MP3"],
voiceTranscription: false,
imageSupport: false,
imageFormat: [],
deletionHistory: [],
};
case 'LOAD_FROM_API':
return {
responseType: action.config.responseTypes,
inputType: "text",
welcomeMessage: action.config.welcomeMessage,
motivationalPhrases: action.config.motivationalPhrases,
helpPhrases: action.config.helpPhrases || [],
audioFormat: action.config.audioFormats as AudioFormat[],
voiceTranscription: action.config.voiceTranscription,
imageSupport: action.config.imageSupport,
imageFormat: action.config.imageFormats as ImageFormat[],
deletionHistory: state.deletionHistory,
};
default:
return state;
}
......@@ -185,7 +206,7 @@ const useAnimationControl = (delay = 0) => {
useEffect(() => {
const timer = setTimeout(() => setIsVisible(true), delay);
return () => clearTimeout(timer);
}, [delay]); // Added 'delay' as a dependency
}, [delay]);
return isVisible;
};
......@@ -217,28 +238,73 @@ const useFormValidation = (settings: IAIAssistantCustomizationSettings) =>
};
}, [settings]);
export default function AIAssistantCustomization() {
// Use reducer for complex state management
const [settings, dispatch] = useReducer<React.Reducer<IAIAssistantCustomizationSettings, ActionType>>(
settingsReducer,
AIAssistantCustomizationService.getCustomizationSettings() as IAIAssistantCustomizationSettings
);
interface AIAssistantCustomizationProps {
assistantId: string;
}
export default function AIAssistantCustomization({ assistantId }: AIAssistantCustomizationProps) {
const [settings, dispatch] = useReducer(settingsReducer, {
responseType: ["TEXT"],
inputType: "text",
welcomeMessage: "Bonjour ! Comment puis-je vous aider ?",
motivationalPhrases: ["Excellent travail !", "Continue comme ça !", "Tu progresses bien !"],
helpPhrases: ["Je vais t'expliquer la règle...", "Voici comment conjuguer ce verbe..."],
audioFormat: ["MP3"],
voiceTranscription: false,
imageSupport: false,
imageFormat: [],
deletionHistory: [],
});
const [newPhrase, setNewPhrase] = useState("");
const [showSaveMessage, setShowSaveMessage] = useState(false);
const [saveMessageSeverity, setSaveMessageSeverity] = useState<"success" | "error">("success");
const [saveMessageText, setSaveMessageText] = useState("");
// Animation controls
const {
responseConfig,
loading,
error,
getResponseConfig,
updateResponseConfig,
addMotivationalPhrase,
removeMotivationalPhrase,
resetToDefaults,
clearError
} = useAssistantResponseConfigStore();
const headerAnimationVisible = useAnimationControl(100);
const formAnimationVisible = useAnimationControl(300);
const buttonAnimationVisible = useAnimationControl(500);
// Form validation
const { hasErrors, errors } = useFormValidation(settings);
const theme = useTheme();
// Handle form field changes
useEffect(() => {
if (assistantId) {
console.log("Loading config for assistant ID:", assistantId);
getResponseConfig(assistantId);
} else {
console.error("❌ assistantId is missing!");
}
}, [assistantId, getResponseConfig]);
useEffect(() => {
if (responseConfig) {
dispatch({ type: 'LOAD_FROM_API', config: responseConfig });
}
}, [responseConfig]);
useEffect(() => {
if (error) {
setSaveMessageSeverity("error");
setSaveMessageText(error);
setShowSaveMessage(true);
clearError();
}
}, [error, clearError]);
const handleChange = useCallback((field: keyof IAIAssistantCustomizationSettings, value: any) => {
dispatch({ type: 'SET_FIELD', field, value });
}, []);
......@@ -255,16 +321,30 @@ export default function AIAssistantCustomization() {
dispatch({ type: 'TOGGLE_AUDIO_FORMAT', format });
}, []);
const handleAddPhrase = useCallback(() => {
const handleAddPhrase = useCallback(async () => {
if (newPhrase.trim() !== "") {
dispatch({ type: 'ADD_PHRASE', phrase: newPhrase });
setNewPhrase("");
try {
await addMotivationalPhrase(assistantId, newPhrase.trim());
setNewPhrase("");
} catch (error) {
setSaveMessageSeverity("error");
setSaveMessageText("Erreur lors de l'ajout de la phrase");
setShowSaveMessage(true);
}
}
}, [newPhrase]);
}, [newPhrase, assistantId, addMotivationalPhrase]);
const handleDeletePhrase = useCallback((index: number) => {
dispatch({ type: 'DELETE_PHRASE', index });
}, []);
const handleDeletePhrase = useCallback(async (index: number) => {
const phrase = settings.motivationalPhrases[index];
try {
await removeMotivationalPhrase(assistantId, phrase);
dispatch({ type: 'DELETE_PHRASE', index });
} catch (error) {
setSaveMessageSeverity("error");
setSaveMessageText("Erreur lors de la suppression de la phrase");
setShowSaveMessage(true);
}
}, [settings.motivationalPhrases, assistantId, removeMotivationalPhrase]);
const handleRestorePhrase = useCallback((index: number) => {
dispatch({ type: 'RESTORE_PHRASE', index });
......@@ -278,26 +358,24 @@ export default function AIAssistantCustomization() {
dispatch({ type: 'CLEAR_HISTORY' });
}, []);
const handleSaveCustomization = useCallback(() => {
const handleSaveCustomization = useCallback(async () => {
if (hasErrors) {
setSaveMessageSeverity("error");
setSaveMessageText("Veuillez corriger les erreurs avant d'enregistrer");
setShowSaveMessage(true);
return;
}
try {
AIAssistantHistoryService.addEntry({
id: `hist-${Date.now()}`,
date: new Date().toLocaleString(),
user: "Admin",
section: "Gestion des Réponses IA",
action: "modify",
comment: "Mise à jour des réponses autorisées et des phrases de motivation",
});
AIAssistantCustomizationService.saveCustomizationSettings(settings);
const request: UpdateResponseConfigRequest = {
responseTypes: settings.responseType,
welcomeMessage: settings.welcomeMessage,
motivationalPhrases: settings.motivationalPhrases,
audioFormats: settings.audioFormat,
voiceTranscription: settings.voiceTranscription,
imageSupport: settings.imageSupport,
imageFormats: settings.imageFormat,
};
await updateResponseConfig(assistantId, request);
setSaveMessageSeverity("success");
setSaveMessageText("Paramètres enregistrés avec succès !");
setShowSaveMessage(true);
......@@ -306,37 +384,20 @@ export default function AIAssistantCustomization() {
setSaveMessageText("Erreur lors de l'enregistrement des paramètres");
setShowSaveMessage(true);
}
}, [settings, hasErrors]);
const handleReset = useCallback(() => {
dispatch({ type: 'RESET_SETTINGS' });
AIAssistantCustomizationService.saveCustomizationSettings({
responseType: ["text"],
inputType: "text",
welcomeMessage: "Bonjour ! Comment puis-je vous aider ?",
motivationalPhrases: ["Excellent travail !", "Continue comme ça !", "Tu progresses bien !"],
helpPhrases: ["Je vais t'expliquer la règle...", "Voici comment conjuguer ce verbe..."],
audioFormat: ["mp3"], // Modifié pour être un array au lieu d'une string
voiceTranscription: false,
imageSupport: false,
imageFormat: [],
deletionHistory: [],
});
AIAssistantHistoryService.addEntry({
id: `hist-${Date.now()}`,
date: new Date().toLocaleString(),
user: "Admin",
section: "Gestion des Réponses IA",
action: "modify",
comment: "Réinitialisation des paramètres de personnalisation",
});
setSaveMessageSeverity("success");
setSaveMessageText("Paramètres réinitialisés avec succès !");
setShowSaveMessage(true);
}, []);
}, [settings, hasErrors, assistantId, updateResponseConfig]);
const handleReset = useCallback(async () => {
try {
await resetToDefaults(assistantId);
setSaveMessageSeverity("success");
setSaveMessageText("Paramètres réinitialisés avec succès !");
setShowSaveMessage(true);
} catch (error) {
setSaveMessageSeverity("error");
setSaveMessageText("Erreur lors de la réinitialisation");
setShowSaveMessage(true);
}
}, [assistantId, resetToDefaults]);
const handleCloseSnackbar = () => {
setShowSaveMessage(false);
......@@ -366,13 +427,22 @@ export default function AIAssistantCustomization() {
</motion.div>
);
// Response type options with icons
const responseTypeOptions = [
{ value: "text" as ResponseType, label: "Texte", icon: faFont },
{ value: "audio" as ResponseType, label: "Audio", icon: faMicrophone },
{ value: "image" as ResponseType, label: "Image", icon: faImage },
{ value: "TEXT" as ResponseType, label: "Texte", icon: faFont },
{ value: "AUDIO" as ResponseType, label: "Audio", icon: faMicrophone },
];
if (loading && !responseConfig) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '400px' }}>
<CircularProgress />
<Typography variant="h6" sx={{ ml: 2 }}>
Chargement de la configuration...
</Typography>
</Box>
);
}
return (
<LazyMotion features={domAnimation}>
<Box sx={{ width: "100%", maxWidth: 800, mx: "auto", p: 3 }}>
......@@ -407,6 +477,7 @@ export default function AIAssistantCustomization() {
variant={settings.responseType.includes(option.value) ? "contained" : "outlined"}
color="primary"
onClick={() => handleToggleResponseType(option.value)}
disabled={loading}
startIcon={<FontAwesomeIcon icon={option.icon} />}
sx={{ borderRadius: 4, px: 2 }}
>
......@@ -430,6 +501,7 @@ export default function AIAssistantCustomization() {
error={!!errors.welcomeMessage}
helperText={errors.welcomeMessage}
sx={{ mt: 2 }}
disabled={loading}
/>
<Typography variant="subtitle1" sx={{ mt: 3, mb: 1, fontWeight: 'medium' }}>
......@@ -443,13 +515,14 @@ export default function AIAssistantCustomization() {
value={newPhrase}
onChange={(e) => setNewPhrase(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAddPhrase()}
disabled={loading}
/>
<Button
variant="contained"
color="primary"
onClick={handleAddPhrase}
startIcon={<FontAwesomeIcon icon={faPlusCircle} />}
disabled={!newPhrase.trim()}
disabled={!newPhrase.trim() || loading}
sx={{ borderRadius: 2 }}
>
Ajouter
......@@ -481,6 +554,7 @@ export default function AIAssistantCustomization() {
color="primary"
size="small"
sx={{ '&:hover': { color: theme.palette.error.main } }}
disabled={loading}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
......@@ -500,15 +574,16 @@ export default function AIAssistantCustomization() {
<Typography variant="subtitle1" sx={{ mb: 1 }}>Formats Audio</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
{["mp3", "wav", "aac"].map((format) => (
{["MP3", "WAV", "AAC"].map((format) => (
<Button
key={format}
variant={settings.audioFormat.includes(format as AudioFormat) ? "contained" : "outlined"}
color="primary"
onClick={() => handleToggleAudioFormat(format as AudioFormat)}
disabled={loading}
sx={{ borderRadius: 4, minWidth: '80px' }}
>
{format.toUpperCase()}
{format}
</Button>
))}
</Box>
......@@ -523,6 +598,7 @@ export default function AIAssistantCustomization() {
checked={!!settings.voiceTranscription}
onChange={(e) => handleChange("voiceTranscription", e.target.checked)}
color="primary"
disabled={loading}
/>
</Box>
......@@ -534,22 +610,23 @@ export default function AIAssistantCustomization() {
checked={!!settings.imageSupport}
onChange={(e) => handleChange("imageSupport", e.target.checked)}
color="primary"
disabled={loading}
/>
</Box>
<Typography variant="subtitle1" sx={{ mb: 1 }}>Formats d&apos;Image</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
{["jpg", "png", "svg", "pdf"].map((format) => (
{["JPG", "PNG", "PDF"].map((format) => (
<Button
key={format}
variant={settings.imageFormat.includes(format as ImageFormat) ? "contained" : "outlined"}
color="primary"
onClick={() => handleToggleImageFormat(format as ImageFormat)}
disabled={!settings.imageSupport}
disabled={!settings.imageSupport || loading}
sx={{ borderRadius: 4, minWidth: '80px' }}
>
{format.toUpperCase()}
{format}
</Button>
))}
</Box>
......@@ -570,6 +647,7 @@ export default function AIAssistantCustomization() {
onClick={handleClearAllHistory}
startIcon={<FontAwesomeIcon icon={faTrash} />}
sx={{ borderRadius: 2 }}
disabled={loading}
>
Vider l&apos;historique
</Button>
......@@ -578,7 +656,7 @@ export default function AIAssistantCustomization() {
{settings.deletionHistory.length === 0 ? (
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'text.secondary', py: 2 }}>
Aucun élément dans l&aposhistorique des suppressions
Aucun élément dans l&apos;historique des suppressions
</Typography>
) : (
<List sx={{ maxHeight: '300px', overflow: 'auto' }}>
......@@ -608,6 +686,7 @@ export default function AIAssistantCustomization() {
onClick={() => handleRestorePhrase(index)}
size="small"
sx={{ mr: 1, color: theme.palette.info.main }}
disabled={loading}
>
<FontAwesomeIcon icon={faUndo} />
</IconButton>
......@@ -618,6 +697,7 @@ export default function AIAssistantCustomization() {
onClick={() => handleDeleteHistoryItem(index)}
size="small"
sx={{ color: theme.palette.error.main }}
disabled={loading}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
......@@ -654,6 +734,7 @@ export default function AIAssistantCustomization() {
startIcon={<FontAwesomeIcon icon={faSave} />}
size="large"
sx={{ borderRadius: 2, px: 4 }}
disabled={loading}
>
Enregistrer les modifications
</Button>
......@@ -665,6 +746,7 @@ export default function AIAssistantCustomization() {
onClick={handleReset}
size="large"
sx={{ borderRadius: 2 }}
disabled={loading}
>
Réinitialiser
</Button>
......
......@@ -293,12 +293,22 @@ export default function AIAssistantDescription({ assistantId }: AIAssistantFilte
};
}, [assistantId, getAssistantById]);
// Format date for display
const formattedDate = useMemo(() => {
if (!assistant?.lastUpdated) return '';
const raw = assistant?.lastUpdated; // string | number | Date | undefined | null
if (!raw) return '-';
// ⬇️ Conversion robuste
const date =
typeof assistant.lastUpdated === 'string' ? new Date(assistant.lastUpdated) : assistant.lastUpdated;
(raw instanceof Date) // déjà un Date ?
? raw
: new Date(
typeof raw === 'number' // timestamp (ms ou s) ?
? (raw.toString().length === 10 ? raw * 1000 : raw) // 10 chiffres → secondes → ms
: raw // sinon on laisse la chaîne à new Date(...)
);
// ⬇️ Validation
if (Number.isNaN(date.valueOf())) return '-';
return new Intl.DateTimeFormat('fr-FR', {
year: 'numeric',
......
......@@ -126,7 +126,9 @@ export function AIAssistantTableRow({
<TableCell padding="checkbox" sx={appliedStyle}>
<Checkbox checked={selected} onChange={onSelectRow} />
</TableCell>
<ConditionalTableCell condition={visibleColumns.name ?? false} sx={appliedStyle}>
{row.name || '-'}
</ConditionalTableCell>
<ConditionalTableCell condition={visibleColumns.type ?? false} sx={appliedStyle}>
{row.type === 'Apprentissage' ? 'Apprentissage' : row.type || '-'}
</ConditionalTableCell>
......
export type ResponseType = "TEXT" | "AUDIO" | "IMAGE";
export interface AssistantResponseConfigDTO {
assistantId: number;
responseTypes: ResponseType[];
welcomeMessage: string;
motivationalPhrases: string[];
helpPhrases: string[];
audioFormats: string[];
voiceTranscription: boolean;
imageSupport: boolean;
imageFormats: string[];
}
export interface UpdateResponseConfigRequest {
responseTypes: ResponseType[];
welcomeMessage: string;
motivationalPhrases: string[];
audioFormats: string[];
voiceTranscription: boolean;
imageSupport: boolean;
imageFormats: string[];
}
export interface ApiResponse<T> {
success: boolean;
message: string;
data: T;
errorCode: string | null;
timestamp: number;
}
\ No newline at end of file
0% ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter