diff --git a/src/config/appSettings.ts b/src/config/appSettings.ts index 34576bb..a2248d3 100644 --- a/src/config/appSettings.ts +++ b/src/config/appSettings.ts @@ -9,13 +9,16 @@ export type AssistantsSettingsType = { }; export type ChatSettingsType = { + name: string; description: string; participantInstructions: string; participantEndText: string; + sendAllToChatbot: boolean; }; export type ExchangeSettings = { id: UUID; + name: string; assistant: AssistantSettings; description: string; chatbotInstructions: string; diff --git a/src/langs/en.json b/src/langs/en.json index eb20e9c..aee7795 100644 --- a/src/langs/en.json +++ b/src/langs/en.json @@ -32,13 +32,16 @@ }, "CHAT": { "TITLE": "Chat Settings", + "NAME": "Interaction Name", "DESCRIPTION": "Chat Description", "INSTRUCTIONS": "Participant Instructions", "END": "End Screen Text", - "USER": "Participant" + "SEND_ALL": "Let Assistant Remember All Messages", + "SEND_ALL_INFO": "If checked, all previous messages will be included in the prompt sent to the chatbot, instead of only messages from the current exchange." }, "EXCHANGES": { "TITLE": "Exchanges Settings", + "NAME": "Exchange Name", "DESCRIPTION": "Exchanges Description", "INSTRUCTIONS": "Chatbot instructions", "CUE": "Initial Cue", @@ -54,6 +57,7 @@ }, "CONVERSATIONS": { "TITLE": "View Conversations", + "EXPORT_ALL": "Export All", "TABLE": { "MEMBER": "Member", "UPDATED": "Last change", @@ -61,7 +65,8 @@ "NOT_STARTED": "Not started", "COMPLETE": "Completed", "INCOMPLETE": "Not completed", - "DELETE": "Delete interaction", + "DELETE": "Delete", + "EXPORT": "Export", "NONE": "No conversations so far." }, "RESET": "Delete and reset conversation" diff --git a/src/langs/fr.json b/src/langs/fr.json index 22c4ef4..41c6a07 100644 --- a/src/langs/fr.json +++ b/src/langs/fr.json @@ -32,13 +32,16 @@ }, "CHAT": { "TITLE": " Paramètres du Chat", + "NAME": "Nom de l'Interaction", "DESCRIPTION": "Description du Chat", "INSTRUCTIONS": "Instructions pour le Participant", "END": "Texte de Fin d'Écran", - "USER": "Participant" + "SEND_ALL": "Laissez l'Assistant se Souvenir de Tous les Messages", + "SEND_ALL_INFO": "Si cette option est cochée, tous les messages précédents seront inclus dans l'invite envoyée au chatbot, au lieu des seuls messages de l'échange en cours. " }, "EXCHANGES": { "TITLE": "Échanges Paramètres", + "NAME": "Nom de l'Échange", "DESCRIPTION": "Description des Échanges", "INSTRUCTIONS": "Instructions du Chatbot", "CUE": "Initial Cue", @@ -54,6 +57,7 @@ }, "CONVERSATIONS": { "TITLE": "Voir les Conversations", + "EXPORT_ALL": "Exporter Tout", "TABLE": { "MEMBER": "Membre", "UPDATED": "Dernière modification", @@ -61,7 +65,8 @@ "NOT_STARTED": "Non démarré", "COMPLETE": "Completed", "INCOMPLETE": "Non terminé", - "DELETE": "Supprimer l'interaction", + "DELETE": "Supprimer", + "EXPORT": "Exporter", "NONE": "Aucune conversation jusqu'à présent" }, "RESET": "Supprimer et réinitialiser la conversation" diff --git a/src/modules/context/SettingsContext.tsx b/src/modules/context/SettingsContext.tsx index 4e90674..79a12ef 100644 --- a/src/modules/context/SettingsContext.tsx +++ b/src/modules/context/SettingsContext.tsx @@ -24,14 +24,17 @@ export const defaultSettingsValues: AllSettingsType = { assistantList: [{ id: uuidv4(), name: '', description: '' }], }, chat: { + name: '', description: '', participantInstructions: '', participantEndText: '', + sendAllToChatbot: false, }, exchanges: { exchangeList: [ { id: uuidv4(), + name: '', assistant: { id: '', name: '', diff --git a/src/modules/interaction/ParticipantInteraction.tsx b/src/modules/interaction/ParticipantInteraction.tsx index 4f240c8..b507db9 100644 --- a/src/modules/interaction/ParticipantInteraction.tsx +++ b/src/modules/interaction/ParticipantInteraction.tsx @@ -255,6 +255,7 @@ const ParticipantInteraction = (): ReactElement => { return []; })} participant={currentMember} + sendAllMessages={interaction.sendAllToChatbot} /> ); }; diff --git a/src/modules/message/MessagesPane.tsx b/src/modules/message/MessagesPane.tsx index 66382fe..97ed079 100644 --- a/src/modules/message/MessagesPane.tsx +++ b/src/modules/message/MessagesPane.tsx @@ -39,6 +39,7 @@ type MessagesPaneProps = { participant: Agent; autoDismiss: boolean; goToNextExchange: () => void; + sendAllMessages?: boolean; readOnly?: boolean; }; @@ -51,6 +52,7 @@ const MessagesPane = ({ participant, autoDismiss, goToNextExchange, + sendAllMessages = false, readOnly = false, }: MessagesPaneProps): ReactElement => { // Hook to post chat messages asynchronously using mutation @@ -103,6 +105,7 @@ const MessagesPane = ({ id: uuidv4(), content: currentExchange.participantCue, sender: currentExchange.assistant, + sentAt: new Date(), }, ]); } @@ -145,7 +148,12 @@ const MessagesPane = ({ */ function handlePostChatbot(newMessage: Message): void { // Build the prompt for the chatbot using the existing messages and the new message - const prompt: ChatBotMessage[] = [...buildPrompt(msgs, newMessage)]; + const prompt: ChatBotMessage[] = [ + ...buildPrompt( + [...(sendAllMessages ? pastMessages : []), ...msgs], + newMessage, + ), + ]; // Send the prompt to the chatbot API and handle the response postChatBot(prompt) @@ -154,6 +162,7 @@ const MessagesPane = ({ id: uuidv4(), content: chatBotRes.completion, sender: currentExchange.assistant, + sentAt: new Date(), }; // Add the chatbot's response to the list of messages @@ -174,6 +183,7 @@ const MessagesPane = ({ id: uuidv4(), content, sender: participant, + sentAt: new Date(), }; // Update the messages state with the new message diff --git a/src/results/ConversationsView.tsx b/src/results/ConversationsView.tsx index 2ae67cf..56e2f09 100644 --- a/src/results/ConversationsView.tsx +++ b/src/results/ConversationsView.tsx @@ -2,11 +2,13 @@ import { FC, Fragment } from 'react'; import { UseTranslationResponse, useTranslation } from 'react-i18next'; import DeleteIcon from '@mui/icons-material/Delete'; +import FileDownloadIcon from '@mui/icons-material/FileDownload'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import { Alert, Box, + Button, Collapse, IconButton, Paper, @@ -48,24 +50,14 @@ const Conversations: FC = ({ const { mutate: deleteAppData } = mutations.useDeleteAppData(); // Fetching interaction data - const { data: appDatas } = hooks.useAppData(); + const appDatas = + hooks + .useAppData() + .data?.filter((appData) => appData.type === 'Interaction') || []; // Fetching all members from the app context or defaulting to the checked-out member const allMembers: Member[] = hooks.useAppContext().data?.members || []; - /* - // Memoized value to find the interaction corresponding to the selected member - const checkedOutInteraction: Interaction | undefined = useMemo( - (): Interaction | undefined => - appDatas?.find( - (appData): boolean => - appData?.data?.exchanges?.exchangeList && - appData.member.id === checkedOutMember.id, - )?.data, - [appDatas, checkedOutMember.id], - ); -*/ - const StatusLabel: (started: boolean, complete: boolean) => string = ( started: boolean, complete: boolean, @@ -79,9 +71,88 @@ const Conversations: FC = ({ return t('CONVERSATIONS.TABLE.NOT_STARTED'); }; + // Utility function to convert JSON data to CSV format + const convertJsonToCsv: (data: Interaction[]) => string = ( + data: Interaction[], + ): string => { + const headers: string[] = [ + 'Participant', + 'Sender', + 'Sent at', + 'Exchange', + 'Interaction', + 'Content', + 'Type', + ]; + const csvRows: string[] = [ + headers.join(','), // header row first + ...data.flatMap((interactionData: Interaction): string[] => + interactionData.exchanges.exchangeList.flatMap( + (exchange: Exchange): string[] => + exchange.messages.map((message: Message): string => + [ + interactionData.participant.id, + message.sender.id, + format(new Date(message.sentAt || ''), 'dd/MM/yyyy HH:mm'), + exchange.name, + interactionData.name, + message.content, + typeof message.content, + ].join(','), + ), + ), + ), + ]; + // map data rows + return csvRows.join('\n'); + }; + /* + ...data.map((row) => + headers.map((header) => JSON.stringify(row[header] || '')).join(','), +*/ + // Function to download CSV file + const downloadCsv: (csv: string, filename: string) => void = ( + csv: string, + filename: string, + ): void => { + const blob: Blob = new Blob([csv], { type: 'text/csv' }); + const url: string = window.URL.createObjectURL(blob); + const anchor: HTMLAnchorElement = document.createElement('a'); + anchor.setAttribute('hidden', ''); + anchor.setAttribute('href', url); + anchor.setAttribute('download', filename); + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + }; + + // Main function to handle JSON export as CSV + const exportJsonAsCsv: (jsonData: Interaction[], filename: string) => void = ( + jsonData: Interaction[], + filename: string, + ): void => { + if (jsonData && jsonData.length) { + const csv: string = convertJsonToCsv(jsonData); + downloadCsv(csv, filename); + } + }; + return ( - {t('CONVERSATIONS.TITLE')} + + {t('CONVERSATIONS.TITLE')} + + @@ -91,6 +162,7 @@ const Conversations: FC = ({ {t('CONVERSATIONS.TABLE.UPDATED')} {t('CONVERSATIONS.TABLE.STATUS')} {t('CONVERSATIONS.TABLE.DELETE')} + {t('CONVERSATIONS.TABLE.EXPORT')} @@ -155,6 +227,7 @@ const Conversations: FC = ({ id: interaction?.id || '', }) } + disabled={!interaction} sx={{ width: 'auto' }} > @@ -162,6 +235,19 @@ const Conversations: FC = ({ + + { + exportJsonAsCsv( + interaction ? [interaction.data] : [], + `chatbot_${interaction?.data.description}_${format(new Date(), 'yyyyMMdd_HH.mm')}.csv`, + ); + }} + disabled={!interaction?.data} + > + + + = ({ unmountOnExit > - {interaction?.data ? ( + {interaction?.data?.started ? ( = ({ ); - /* - return ( - - {t('CONVERSATIONS.TITLE')} - - {t('CONVERSATIONS.MEMBER')} - - - {checkedOutMember.id === '' ? null : ( - - {checkedOutInteraction ? ( - - {}} - interactionDescription="" - pastMessages={ - checkedOutInteraction.exchanges.exchangeList.flatMap( - (exchange: Exchange): Message[] => { - // Collect dismissed messages from exchanges - if (exchange.dismissed) { - return exchange.messages; - } - return []; - }, - ) || [] - } - participant={checkedOutInteraction.participant} - autoDismiss={false} - goToNextExchange={(): void => {}} - readOnly - /> - {checkedOutInteraction.completed ? ( - - {t('CONVERSATIONS.TABLE.COMPLETE')} - - ) : ( - - {t('CONVERSATIONS.TABLE.INCOMPLETE')} - - )} - - - - deleteAppData({ - id: - appDatas?.find( - (appData): boolean => - appData.member.id === checkedOutMember.id, - )?.id || '', - }) - } - sx={{ width: 'auto' }} - > - - - - - - - ) : ( - // Show a warning if no interaction is found - - {t('CONVERSATIONS.NONE')} - - )} - - )} - - ); - */ }; export default Conversations; diff --git a/src/settings/ChatSettings.tsx b/src/settings/ChatSettings.tsx index 37446ed..50f7c05 100644 --- a/src/settings/ChatSettings.tsx +++ b/src/settings/ChatSettings.tsx @@ -1,7 +1,8 @@ import { ChangeEvent, FC } from 'react'; import { UseTranslationResponse, useTranslation } from 'react-i18next'; -import { Typography } from '@mui/material'; +import InfoBadge from '@mui/icons-material/Info'; +import { Switch, Tooltip, Typography } from '@mui/material'; import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; @@ -21,14 +22,24 @@ const ChatSettings: FC = ({ chat, onChange }) => { // Destructuring chat settings const { + name: chatName, description: chatDescription, participantInstructions: chatInstructions, participantEndText: chatEndText, + sendAllToChatbot: chatSendAll, }: ChatSettingsType = chat; return ( {t('SETTINGS.CHAT.TITLE')} + , + ): void => onChange({ ...chat, name: e.target.value })} + /> = ({ chat, onChange }) => { e: ChangeEvent, ): void => onChange({ ...chat, participantEndText: e.target.value })} /> + + {t('SETTINGS.CHAT.SEND_ALL')} + {' '} + + + + + ): void => + onChange({ ...chat, sendAllToChatbot: e.target.checked }) + } + /> ); }; diff --git a/src/settings/ExchangesSettings.tsx b/src/settings/ExchangesSettings.tsx index 6077744..7b3b8fc 100644 --- a/src/settings/ExchangesSettings.tsx +++ b/src/settings/ExchangesSettings.tsx @@ -82,6 +82,7 @@ const ExchangeSettingsPanel: FC = ({ // Destructuring exchange settings const { + name: exchangeName, assistant: exchangeAssistant, description: exchangeDescription, chatbotInstructions: exchangeInstructions, @@ -125,15 +126,15 @@ const ExchangeSettingsPanel: FC = ({ , - ): void => onChange(index, 'description', e.target.value)} + ): void => onChange(index, 'name', e.target.value)} /> + handleMoveUp(index)} @@ -153,6 +154,15 @@ const ExchangeSettingsPanel: FC = ({ + , + ): void => onChange(index, 'description', e.target.value)} + /> = ({ exchanges, onChange }) => { { // Generate a new unique ID id: uuidv4(), + name: '', assistant: { id: '', name: '', diff --git a/src/types/Message.ts b/src/types/Message.ts index 026c7ad..dd12b9f 100644 --- a/src/types/Message.ts +++ b/src/types/Message.ts @@ -6,6 +6,5 @@ export interface Message { id: UUID; content: string; sender: Agent; - updatedAt?: string; - createdAt?: string; + sentAt?: Date; }