data-primals-engine 1.0.10 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/README.md +8 -0
- package/client/index.html +31 -0
- package/client/index.js +25 -0
- package/client/package-lock.json +11664 -0
- package/client/package.json +70 -0
- package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
- package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
- package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
- package/client/public/doc/API.postman_collection.json +214 -0
- package/client/public/github.svg +1 -0
- package/client/public/profilCompany.jpg +0 -0
- package/client/public/profilDeveloper.jpg +0 -0
- package/client/public/profilEngineer.jpg +0 -0
- package/client/public/profilPersonal.jpg +0 -0
- package/client/public/profils .psd +0 -0
- package/client/public/react.svg +9 -0
- package/client/src/APIInfo.jsx +328 -0
- package/client/src/AddWidgetTypeModal.jsx +44 -0
- package/client/src/AddWidgetTypeModal.scss +87 -0
- package/client/src/App.css +42 -0
- package/client/src/App.jsx +586 -0
- package/client/src/App.scss +1466 -0
- package/client/src/AssistantChat.jsx +280 -0
- package/client/src/AssistantChat.scss +229 -0
- package/client/src/Button.jsx +12 -0
- package/client/src/Button.scss +197 -0
- package/client/src/CalculationBuilder.jsx +338 -0
- package/client/src/CalculationStepRow.jsx +199 -0
- package/client/src/CalendarConfigModal.jsx +49 -0
- package/client/src/ChartConfigModal.jsx +402 -0
- package/client/src/ConditionBuilder.jsx +622 -0
- package/client/src/ConditionBuilder.scss +672 -0
- package/client/src/ConditionBuilder2.jsx +757 -0
- package/client/src/ContentView.jsx +274 -0
- package/client/src/CronBuilder.jsx +126 -0
- package/client/src/CronBuilder.scss +128 -0
- package/client/src/CronPartBuilder.jsx +150 -0
- package/client/src/Dashboard.jsx +210 -0
- package/client/src/Dashboard.scss +445 -0
- package/client/src/DashboardChart.jsx +362 -0
- package/client/src/DashboardFlexViewItem.jsx +129 -0
- package/client/src/DashboardView.jsx +554 -0
- package/client/src/DataChart.jsx +349 -0
- package/client/src/DataEditor.jsx +556 -0
- package/client/src/DataLayout.jsx +709 -0
- package/client/src/DataTable.jsx +996 -0
- package/client/src/DataTable.scss +73 -0
- package/client/src/Dialog.jsx +90 -0
- package/client/src/Dialog.scss +117 -0
- package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
- package/client/src/DocumentationPageLayout.jsx +93 -0
- package/client/src/DocumentationPageLayout.scss +262 -0
- package/client/src/Draggable.jsx +222 -0
- package/client/src/Draggable.scss +49 -0
- package/client/src/ExportDialog.jsx +172 -0
- package/client/src/Field.jsx +1558 -0
- package/client/src/FlexBuilder.jsx +269 -0
- package/client/src/FlexBuilder.scss +226 -0
- package/client/src/FlexBuilderControls.jsx +170 -0
- package/client/src/FlexBuilderModal.jsx +75 -0
- package/client/src/FlexBuilderModal.scss +109 -0
- package/client/src/FlexBuilderPreview.jsx +48 -0
- package/client/src/FlexBuilderUtils.js +84 -0
- package/client/src/FlexDataRenderer.jsx +170 -0
- package/client/src/FlexDataRenderer.scss +80 -0
- package/client/src/FlexNode.jsx +290 -0
- package/client/src/FlexTreeUtils.js +129 -0
- package/client/src/HiddenableCell.jsx +46 -0
- package/client/src/HiddenableCell.scss +36 -0
- package/client/src/KPIDialog.jsx +39 -0
- package/client/src/KPIWidget.jsx +139 -0
- package/client/src/KanbanCard.jsx +92 -0
- package/client/src/KanbanColumn.jsx +29 -0
- package/client/src/KanbanConfigModal.jsx +115 -0
- package/client/src/KanbanView.jsx +113 -0
- package/client/src/KanbanView.scss +107 -0
- package/client/src/MessageRotator.jsx +77 -0
- package/client/src/MessageRotator.scss +14 -0
- package/client/src/ModelCreator.jsx +640 -0
- package/client/src/ModelCreator.scss +154 -0
- package/client/src/ModelCreatorField.jsx +810 -0
- package/client/src/ModelImporter.jsx +120 -0
- package/client/src/ModelList.jsx +205 -0
- package/client/src/Notification.jsx +137 -0
- package/client/src/Notification.scss +126 -0
- package/client/src/NotificationProvider.jsx +73 -0
- package/client/src/PackGallery.jsx +211 -0
- package/client/src/PackGallery.scss +156 -0
- package/client/src/Pagination.jsx +141 -0
- package/client/src/RTE.jsx +415 -0
- package/client/src/RTETrans.jsx +103 -0
- package/client/src/RelationField.jsx +247 -0
- package/client/src/RelationValue.jsx +230 -0
- package/client/src/RestoreConfirmationModal.jsx +183 -0
- package/client/src/RestoreDialog.jsx +134 -0
- package/client/src/RestoreDialog.scss +67 -0
- package/client/src/RichTextEditorModal.jsx +34 -0
- package/client/src/RichTextEditorModal.scss +54 -0
- package/client/src/TourSpotlight.jsx +234 -0
- package/client/src/TourSpotlight.scss +100 -0
- package/client/src/TutorialsMenu.jsx +83 -0
- package/client/src/TutorialsMenu.scss +217 -0
- package/client/src/TutorialsRewardModal.jsx +65 -0
- package/client/src/Utils.jsx +78 -0
- package/client/src/ViewSwitcher.jsx +54 -0
- package/client/src/ViewSwitcher.scss +74 -0
- package/client/src/Webpage.jsx +69 -0
- package/client/src/_variables.scss +21 -0
- package/client/src/assets/react.svg +1 -0
- package/client/src/constants.js +68 -0
- package/client/src/contexts/AuthContext.jsx +28 -0
- package/client/src/contexts/ModelContext.jsx +328 -0
- package/client/src/contexts/UIContext.jsx +52 -0
- package/client/src/core/data.js +35 -0
- package/client/src/entry-client.jsx +15 -0
- package/client/src/entry-server.jsx +13 -0
- package/client/src/filter.js +222 -0
- package/client/src/hooks/data.js +101 -0
- package/client/src/hooks/useDebounce.js +20 -0
- package/client/src/hooks/useDragAndDrop.js +59 -0
- package/client/src/hooks/useLocalStorage.js +100 -0
- package/client/src/hooks/useMount.js +42 -0
- package/client/src/hooks/useTutorials.jsx +227 -0
- package/client/src/hooks/useWindowSize.js +24 -0
- package/client/src/i18n.js +47 -0
- package/client/src/index.css +90 -0
- package/client/src/index.js +10 -0
- package/client/src/main.jsx +11 -0
- package/client/src/translations.js +15940 -0
- package/client/src/tutorials.js +112 -0
- package/client/vite.config.js +30 -0
- package/eslint.config.js +50 -0
- package/package.json +48 -27
- package/server.js +9 -0
- package/src/constants.js +12 -23
- package/src/core.js +9 -8
- package/src/data.js +81 -80
- package/src/defaultModels.js +114 -115
- package/src/email.js +8 -9
- package/src/engine.js +5 -5
- package/src/events.js +1 -2
- package/src/i18n.js +5390 -211
- package/src/middlewares/middleware-mongodb.js +94 -93
- package/src/migrate.js +24 -24
- package/src/modules/assistant.js +55 -55
- package/src/modules/bucket.js +10 -9
- package/src/modules/data.js +387 -357
- package/src/modules/file.js +3 -6
- package/src/modules/mongodb.js +6 -7
- package/src/modules/user.js +24 -47
- package/src/modules/workflow.js +68 -67
- package/src/openai.jobs.js +2 -2
- package/src/packs.js +30 -31
- package/src/providers.js +101 -3
- package/src/setenv.js +1 -1
- package/src/user.js +0 -17
- package/src/workers/crypto-worker.js +21 -20
- package/src/workers/import-export-worker.js +18 -18
- package/test/data.backup.integration.test.js +7 -12
- package/test/data.integration.test.js +27 -25
- package/test/globalSetup.js +2 -2
- package/test/import_export.integration.test.js +6 -6
- package/test/model.integration.test.js +21 -22
- package/test/workflow.integration.test.js +2 -5
- package/test/workflow.robustness.test.js +6 -13
- package/vitest.config.js +2 -2
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// client/src/components/AssistantChat.jsx
|
|
2
|
+
|
|
3
|
+
import React, { useState, useMemo, useEffect, useRef } from 'react';
|
|
4
|
+
import { FaRobot, FaPaperPlane, FaTimes, FaExpand, FaCompress } from 'react-icons/fa';
|
|
5
|
+
import './AssistantChat.scss';
|
|
6
|
+
import { useModelContext } from "./contexts/ModelContext.jsx";
|
|
7
|
+
import { Trans, useTranslation } from "react-i18next";
|
|
8
|
+
import Markdown from 'react-markdown';
|
|
9
|
+
import {useQueryClient} from "react-query";
|
|
10
|
+
|
|
11
|
+
const AssistantChat = ({ config }) => {
|
|
12
|
+
const { selectedModel } = useModelContext();
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
15
|
+
const [isMaximized, setIsMaximized] = useState(false);
|
|
16
|
+
const [messages, setMessages] = useState([
|
|
17
|
+
{ from: 'bot', text: t('assistant.welcome') }
|
|
18
|
+
]);
|
|
19
|
+
const [input, setInput] = useState('');
|
|
20
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
21
|
+
|
|
22
|
+
// NOUVEL ÉTAT : Stocke une action en attente de confirmation de l'utilisateur
|
|
23
|
+
const [pendingConfirmation, setPendingConfirmation] = useState(null);
|
|
24
|
+
|
|
25
|
+
// NOUVEAU : Référence pour le défilement automatique
|
|
26
|
+
const messagesEndRef = useRef(null);
|
|
27
|
+
|
|
28
|
+
// Fonction pour défiler vers le bas
|
|
29
|
+
const scrollToBottom = () => {
|
|
30
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const queryClient = useQueryClient();
|
|
34
|
+
|
|
35
|
+
// Défiler vers le bas à chaque nouveau message ou changement de statut de chargement
|
|
36
|
+
useEffect(scrollToBottom, [messages, isLoading]);
|
|
37
|
+
|
|
38
|
+
// Fonction centralisée pour appeler l'API de l'assistant
|
|
39
|
+
const handleApiCall = async (payload) => {
|
|
40
|
+
setIsLoading(true);
|
|
41
|
+
// On efface toute confirmation en attente dès qu'une nouvelle action est lancée
|
|
42
|
+
setPendingConfirmation(null);
|
|
43
|
+
const isConfirmation = !!payload.confirmedAction;
|
|
44
|
+
if (!isConfirmation) {
|
|
45
|
+
setPendingConfirmation(null);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch('/api/assistant/chat', {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: { 'Content-Type': 'application/json' },
|
|
51
|
+
body: JSON.stringify(payload)
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error(`Erreur HTTP: ${response.status}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = await response.json();
|
|
59
|
+
|
|
60
|
+
if (result.success) {
|
|
61
|
+
if (isConfirmation) {
|
|
62
|
+
const modelToInvalidate = payload.confirmedAction.params.model;
|
|
63
|
+
if (modelToInvalidate) {
|
|
64
|
+
console.log(`[Assistant] Action on model '${modelToInvalidate}' succeeded. Invalidating cache.`);
|
|
65
|
+
// Invalide toutes les requêtes qui commencent par ce tableau.
|
|
66
|
+
// C'est la mode la plus simple et la plus sûre pour s'assurer
|
|
67
|
+
// que toutes les vues de ces données (paginées ou non) sont rafraîchies.
|
|
68
|
+
queryClient.invalidateQueries(['api/data', modelToInvalidate]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// On crée un objet de message pour le bot qui peut contenir plus que du texte
|
|
73
|
+
const botMessage = {
|
|
74
|
+
from: 'bot',
|
|
75
|
+
text: null,
|
|
76
|
+
actionDetails: null // Pour stocker les détails de l'action à afficher
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Gérer le texte à afficher
|
|
80
|
+
if (result.displayMessage) {
|
|
81
|
+
botMessage.text = result.displayMessage;
|
|
82
|
+
} else if (result.codeMessage) {
|
|
83
|
+
const code = typeof result.codeMessage === 'object'
|
|
84
|
+
? JSON.stringify(result.codeMessage, null, 2)
|
|
85
|
+
: result.codeMessage;
|
|
86
|
+
botMessage.text = `\`\`\`json\n${code}\n\`\`\``;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// NOUVEAU : Si une confirmation est demandée, on enrichit le message
|
|
90
|
+
if (result.confirmationRequest) {
|
|
91
|
+
// On stocke l'action complète dans l'état pour l'envoyer si l'utilisateur confirme
|
|
92
|
+
setPendingConfirmation(result.confirmationRequest);
|
|
93
|
+
|
|
94
|
+
// On ajoute les détails (modèle, filtre, données) au message pour l'affichage
|
|
95
|
+
botMessage.actionDetails = {
|
|
96
|
+
model: result.model,
|
|
97
|
+
filter: result.filter,
|
|
98
|
+
data: result.data // Sera undefined si non présent, ce qui est correct
|
|
99
|
+
};
|
|
100
|
+
} else if (isConfirmation) {
|
|
101
|
+
// Si c'était un appel de confirmation et qu'il n'y a pas de nouvelle demande, on vide l'état
|
|
102
|
+
setPendingConfirmation(null);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// On ajoute le message à la liste uniquement s'il a du contenu textuel
|
|
106
|
+
if (botMessage.text) {
|
|
107
|
+
setMessages(prev => [...prev, botMessage]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
} else {
|
|
111
|
+
const errorMessage = { from: 'bot', text: t('assistant.error', `Désolé, une erreur est survenue : {{message}}`, { message: result.message }) };
|
|
112
|
+
setMessages(prev => [...prev, errorMessage]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const errorMessage = { from: 'bot', text: t('assistant.contactError', `Désolé, impossible de contacter l'assistant. ({{message}})`, { message: error.message }) };
|
|
117
|
+
setMessages(prev => [...prev, errorMessage]);
|
|
118
|
+
} finally {
|
|
119
|
+
setIsLoading(false);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Gère la soumission du formulaire de chat
|
|
124
|
+
const handleSubmit = async (e) => {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
// On ne soumet rien si le champ est vide, si une requête est en cours ou si une confirmation est attendue
|
|
127
|
+
if (!input.trim() || isLoading || pendingConfirmation) return;
|
|
128
|
+
|
|
129
|
+
const userMessage = { from: 'user', text: input };
|
|
130
|
+
const currentInput = input;
|
|
131
|
+
|
|
132
|
+
setMessages(prev => [...prev, userMessage]);
|
|
133
|
+
setInput('');
|
|
134
|
+
|
|
135
|
+
gtag('event', "Assistant Prior - msg");
|
|
136
|
+
|
|
137
|
+
await handleApiCall({
|
|
138
|
+
message: currentInput,
|
|
139
|
+
history: messages,
|
|
140
|
+
provider: selectedProvider,
|
|
141
|
+
context: { modelName: selectedModel?.name }
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// NOUVELLE FONCTION : Gère la réponse de l'utilisateur à une demande de confirmation
|
|
146
|
+
const handleConfirmAction = async (isConfirmed) => {
|
|
147
|
+
if (!pendingConfirmation) return;
|
|
148
|
+
|
|
149
|
+
if (isConfirmed) {
|
|
150
|
+
// L'utilisateur a cliqué sur "Oui"
|
|
151
|
+
const confirmationMessage = { from: 'user', text: t('yes', 'Oui') };
|
|
152
|
+
setMessages(prev => [...prev, confirmationMessage]);
|
|
153
|
+
|
|
154
|
+
// On rappelle l'API en envoyant l'action à confirmer
|
|
155
|
+
await handleApiCall({
|
|
156
|
+
message: "Action confirmée par l'utilisateur.",
|
|
157
|
+
history: messages,
|
|
158
|
+
provider: selectedProvider,
|
|
159
|
+
context: { modelName: selectedModel?.name },
|
|
160
|
+
confirmedAction: pendingConfirmation
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
// L'utilisateur a cliqué sur "Non"
|
|
164
|
+
const cancelMessage = { from: 'user', text: t('no', 'Non') };
|
|
165
|
+
const botResponseMessage = { from: 'bot', text: t('assistant.actionCancelled', "Action annulée.") };
|
|
166
|
+
setMessages(prev => [...prev, cancelMessage, botResponseMessage]);
|
|
167
|
+
setPendingConfirmation(null); // On annule la demande de confirmation
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Logique pour le sélecteur de fournisseur (OpenAI/Google)
|
|
172
|
+
const availableProviders = useMemo(() => {
|
|
173
|
+
const providers = [];
|
|
174
|
+
if (config?.openai) providers.push({ value: 'openai', label: 'OpenAI' });
|
|
175
|
+
if (config?.google) providers.push({ value: 'google', label: 'Google' });
|
|
176
|
+
return providers;
|
|
177
|
+
}, [config]);
|
|
178
|
+
|
|
179
|
+
const [selectedProvider, setSelectedProvider] = useState(null);
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (availableProviders.length > 0 && !selectedProvider) {
|
|
183
|
+
setSelectedProvider(availableProviders[0].value);
|
|
184
|
+
}
|
|
185
|
+
}, [availableProviders, selectedProvider]);
|
|
186
|
+
|
|
187
|
+
// Si le chat est fermé, on affiche juste le bouton flottant
|
|
188
|
+
if (!isOpen) {
|
|
189
|
+
return (
|
|
190
|
+
<button className="assistant-fab" onClick={() => setIsOpen(true)} title={t('assistant.open', "Ouvrir l'assistant Prior")}>
|
|
191
|
+
<FaRobot />
|
|
192
|
+
</button>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Si le chat est ouvert, on affiche la fenêtre complète
|
|
197
|
+
return (
|
|
198
|
+
<div className={`assistant-chat-window ${isMaximized ? 'maximized' : ''}`}>
|
|
199
|
+
<div className="chat-header">
|
|
200
|
+
<h3><FaRobot style={{ marginRight: '8px' }} /> <Trans i18nKey="assistant.named" values={{ named: 'Prior' }} /></h3>
|
|
201
|
+
<div className="header-actions">
|
|
202
|
+
{availableProviders.length > 1 && (
|
|
203
|
+
<select
|
|
204
|
+
className="provider-selector"
|
|
205
|
+
value={selectedProvider}
|
|
206
|
+
onChange={(e) => setSelectedProvider(e.target.value)}
|
|
207
|
+
>
|
|
208
|
+
{availableProviders.map(provider => (
|
|
209
|
+
<option key={provider.value} value={provider.value}>
|
|
210
|
+
{provider.label}
|
|
211
|
+
</option>
|
|
212
|
+
))}
|
|
213
|
+
</select>
|
|
214
|
+
)}
|
|
215
|
+
<button onClick={() => setIsMaximized(!isMaximized)} title={isMaximized ? t('collapse', "Réduire") : t('expand', "Agrandir")}>
|
|
216
|
+
{isMaximized ? <FaCompress /> : <FaExpand />}
|
|
217
|
+
</button>
|
|
218
|
+
<button onClick={() => setIsOpen(false)} title={t('close', "Fermer")}>
|
|
219
|
+
<FaTimes />
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div className="chat-messages">
|
|
225
|
+
{messages.map((msg, index) => (
|
|
226
|
+
<div key={index} className={`message ${msg.from}`}>
|
|
227
|
+
<Markdown>{msg.text}</Markdown>
|
|
228
|
+
{msg.actionDetails && (
|
|
229
|
+
<div className="action-details">
|
|
230
|
+
{msg.actionDetails.model && (
|
|
231
|
+
<p><strong>{t('model', 'Modèle')}:</strong> <code>{msg.actionDetails.model}</code></p>
|
|
232
|
+
)}
|
|
233
|
+
{msg.actionDetails.filter && (
|
|
234
|
+
<>
|
|
235
|
+
<p><strong>{t('filter', 'Filtre')}:</strong></p>
|
|
236
|
+
{/* Utiliser Markdown pour afficher un bloc de code JSON formaté */}
|
|
237
|
+
<Markdown>{`\`\`\`json\n${JSON.stringify(msg.actionDetails.filter, null, 2)}\n\`\`\``}</Markdown>
|
|
238
|
+
</>
|
|
239
|
+
)}
|
|
240
|
+
{msg.actionDetails.data && (
|
|
241
|
+
<>
|
|
242
|
+
<p><strong>{t('data', 'Données')}:</strong></p>
|
|
243
|
+
<Markdown>{`\`\`\`json\n${JSON.stringify(msg.actionDetails.data, null, 2)}\n\`\`\``}</Markdown>
|
|
244
|
+
</>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
))}
|
|
250
|
+
{isLoading && <div className="message bot"><p>...</p></div>}
|
|
251
|
+
|
|
252
|
+
{/* NOUVEAU : Affichage des boutons de confirmation */}
|
|
253
|
+
{pendingConfirmation && !isLoading && (
|
|
254
|
+
<div className="message bot confirmation-prompt">
|
|
255
|
+
<button onClick={() => handleConfirmAction(true)}>{t('yes', 'Oui')}</button>
|
|
256
|
+
<button onClick={() => handleConfirmAction(false)} className="cancel">{t('no', 'Non')}</button>
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
<div ref={messagesEndRef} />
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<form className="chat-input-form" onSubmit={handleSubmit}>
|
|
263
|
+
<input
|
|
264
|
+
type="text"
|
|
265
|
+
value={input}
|
|
266
|
+
onChange={(e) => setInput(e.target.value)}
|
|
267
|
+
placeholder={t('assistant.type', "Écrivez votre message...")}
|
|
268
|
+
// On désactive l'input si une requête est en cours ou si une confirmation est attendue
|
|
269
|
+
disabled={isLoading || !!pendingConfirmation}
|
|
270
|
+
autoFocus
|
|
271
|
+
/>
|
|
272
|
+
<button type="submit" disabled={isLoading || !input.trim() || !!pendingConfirmation} title={t('send', "Envoyer")}>
|
|
273
|
+
<FaPaperPlane />
|
|
274
|
+
</button>
|
|
275
|
+
</form>
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export default AssistantChat;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// client/src/components/AssistantChat.scss
|
|
2
|
+
|
|
3
|
+
// Variables pour une maintenance facile
|
|
4
|
+
$primary-color: #007bff;
|
|
5
|
+
$bot-message-bg: #f1f0f0;
|
|
6
|
+
$user-message-bg: #e6f2ff;
|
|
7
|
+
$chat-window-bg: #ffffff;
|
|
8
|
+
$header-height: 50px;
|
|
9
|
+
$input-height: 50px;
|
|
10
|
+
|
|
11
|
+
// Le bouton flottant (quand le chat est fermé)
|
|
12
|
+
.assistant-fab {
|
|
13
|
+
position: fixed;
|
|
14
|
+
bottom: 20px;
|
|
15
|
+
right: 20px;
|
|
16
|
+
width: 60px;
|
|
17
|
+
height: 60px;
|
|
18
|
+
background-color: $primary-color;
|
|
19
|
+
color: white;
|
|
20
|
+
border-radius: 50%;
|
|
21
|
+
border: none;
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
font-size: 24px;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
28
|
+
z-index: 1000;
|
|
29
|
+
transition: transform 0.2s ease-in-out;
|
|
30
|
+
|
|
31
|
+
&:hover {
|
|
32
|
+
transform: scale(1.1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// La fenêtre de chat (quand elle est ouverte)
|
|
37
|
+
.assistant-chat-window {
|
|
38
|
+
position: fixed;
|
|
39
|
+
bottom: 20px;
|
|
40
|
+
right: 20px;
|
|
41
|
+
width: 100%;
|
|
42
|
+
left: 2px;
|
|
43
|
+
max-width: 720px;
|
|
44
|
+
min-height: 400px;
|
|
45
|
+
height: 60vh;
|
|
46
|
+
max-height: calc(100vh - 40px);
|
|
47
|
+
background: $chat-window-bg;
|
|
48
|
+
border-radius: 12px;
|
|
49
|
+
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.25);
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
z-index: 1001;
|
|
54
|
+
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
55
|
+
|
|
56
|
+
// Style pour la vue maximisée
|
|
57
|
+
&.maximized {
|
|
58
|
+
top: 0;
|
|
59
|
+
left: 0;
|
|
60
|
+
right: 0;
|
|
61
|
+
bottom: 0;
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: 100%;
|
|
64
|
+
border-radius: 0;
|
|
65
|
+
max-height: 100vh;
|
|
66
|
+
z-index: 10000000;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// En-tête du chat
|
|
71
|
+
.chat-header {
|
|
72
|
+
height: $header-height;
|
|
73
|
+
background-color: #f7f7f7;
|
|
74
|
+
border-bottom: 1px solid #e0e0e0;
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: space-between;
|
|
78
|
+
padding: 0 15px;
|
|
79
|
+
flex-shrink: 0;
|
|
80
|
+
|
|
81
|
+
h3 {
|
|
82
|
+
font-size: 16px;
|
|
83
|
+
font-weight: 600;
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.header-actions {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 5px;
|
|
92
|
+
|
|
93
|
+
// NOUVEAU : Style pour le sélecteur
|
|
94
|
+
.provider-selector {
|
|
95
|
+
background-color: #e9ecef;
|
|
96
|
+
border: 1px solid #ced4da;
|
|
97
|
+
border-radius: 4px;
|
|
98
|
+
padding: 2px 6px;
|
|
99
|
+
font-size: 12px;
|
|
100
|
+
margin-right: 5px;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
button {
|
|
105
|
+
background: none;
|
|
106
|
+
border: none;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
font-size: 16px;
|
|
109
|
+
color: #666;
|
|
110
|
+
padding: 5px;
|
|
111
|
+
border-radius: 50%;
|
|
112
|
+
&:hover {
|
|
113
|
+
background-color: #e0e0e0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Zone des messages
|
|
120
|
+
.chat-messages {
|
|
121
|
+
flex-grow: 1;
|
|
122
|
+
padding: 15px;
|
|
123
|
+
overflow-y: auto;
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
gap: 12px;
|
|
127
|
+
|
|
128
|
+
.message {
|
|
129
|
+
max-width: 80%;
|
|
130
|
+
p {
|
|
131
|
+
padding: 4px 8px;
|
|
132
|
+
border-radius: 18px;
|
|
133
|
+
margin: 0;
|
|
134
|
+
line-height: 1.5;
|
|
135
|
+
word-wrap: break-word;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.action-details {
|
|
139
|
+
border-left: 3px solid #4a90e2; // Une bordure pour délimiter
|
|
140
|
+
padding: 10px;
|
|
141
|
+
margin-top: 12px;
|
|
142
|
+
background-color: rgba(0, 0, 0, 0.03); // Un fond léger
|
|
143
|
+
border-radius: 0 8px 8px 0;
|
|
144
|
+
font-size: 0.9em;
|
|
145
|
+
|
|
146
|
+
p {
|
|
147
|
+
margin: 0 0 5px 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Style pour le nom du modèle
|
|
151
|
+
code {
|
|
152
|
+
background-color: rgba(0, 0, 0, 0.08);
|
|
153
|
+
padding: 2px 5px;
|
|
154
|
+
border-radius: 4px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Style pour les blocs de code JSON (filtre et données)
|
|
158
|
+
pre {
|
|
159
|
+
white-space: pre-wrap;
|
|
160
|
+
word-wrap: break-word;
|
|
161
|
+
background-color: #f5f5f5 !important; // Assurez-vous que le fond est visible
|
|
162
|
+
padding: 10px;
|
|
163
|
+
border-radius: 4px;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
&.bot {
|
|
167
|
+
align-self: flex-start;
|
|
168
|
+
p {
|
|
169
|
+
background-color: $bot-message-bg;
|
|
170
|
+
border-bottom-left-radius: 4px;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
&.user {
|
|
175
|
+
align-self: flex-end;
|
|
176
|
+
p {
|
|
177
|
+
background-color: $user-message-bg;
|
|
178
|
+
color: #333;
|
|
179
|
+
border-bottom-right-radius: 4px;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Formulaire de saisie
|
|
186
|
+
.chat-input-form {
|
|
187
|
+
height: $input-height;
|
|
188
|
+
display: flex;
|
|
189
|
+
border-top: 1px solid #e0e0e0;
|
|
190
|
+
padding: 5px;
|
|
191
|
+
flex-shrink: 0;
|
|
192
|
+
|
|
193
|
+
input {
|
|
194
|
+
flex-grow: 1;
|
|
195
|
+
border: none;
|
|
196
|
+
padding: 0 10px;
|
|
197
|
+
font-size: 14px;
|
|
198
|
+
&:focus {
|
|
199
|
+
outline: none;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
button {
|
|
204
|
+
background-color: $primary-color;
|
|
205
|
+
color: white;
|
|
206
|
+
border: none;
|
|
207
|
+
border-radius: 8px;
|
|
208
|
+
width: 40px;
|
|
209
|
+
height: 40px;
|
|
210
|
+
cursor: pointer;
|
|
211
|
+
font-size: 16px;
|
|
212
|
+
transition: background-color 0.2s;
|
|
213
|
+
|
|
214
|
+
&:hover {
|
|
215
|
+
background-color: darken($primary-color, 10%);
|
|
216
|
+
}
|
|
217
|
+
&:disabled {
|
|
218
|
+
background-color: #a0cfff;
|
|
219
|
+
cursor: not-allowed;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
.message{
|
|
226
|
+
pre {
|
|
227
|
+
white-space: break-spaces;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "./Button.scss";
|
|
2
|
+
|
|
3
|
+
function Button({ children, disabled, className, ...rest }) {
|
|
4
|
+
const cn = `${className || ""} btn${disabled ? " btn-disabled" : ""}`;
|
|
5
|
+
return (
|
|
6
|
+
<button disabled={disabled} {...rest} className={cn}>
|
|
7
|
+
{children}
|
|
8
|
+
</button>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default Button;
|