diff --git a/CHANGELOG.md b/CHANGELOG.md index c21980f..47a321e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,11 @@ and this project adheres to ## [Unreleased] -- ✨(front) global layout UI +- 🎨(front) global layout modification ### Changed +- ✨(front) global layout UI - ♻️(chat) rewrite backend using Pydantic AI SDK #4 - 🗃️(chat) enforce messages stored JSON format #6 - 🐛(chat) UI messages must have a unique identifier #6 diff --git a/src/backend/conversations/settings.py b/src/backend/conversations/settings.py index aaee8e4..42755fd 100755 --- a/src/backend/conversations/settings.py +++ b/src/backend/conversations/settings.py @@ -134,9 +134,9 @@ class Base(Configuration): ( ("en-us", "English"), ("fr-fr", "Français"), - ("de-de", "Deutsch"), - ("nl-nl", "Nederlands"), - ("es-es", "Español"), + # ("de-de", "Deutsch"), + # ("nl-nl", "Nederlands"), + # ("es-es", "Español"), ) ) diff --git a/src/backend/core/migrations/0001_initial.py b/src/backend/core/migrations/0001_initial.py index b389ab4..fbcb768 100644 --- a/src/backend/core/migrations/0001_initial.py +++ b/src/backend/core/migrations/0001_initial.py @@ -117,9 +117,9 @@ class Migration(migrations.Migration): choices=[ ("en-us", "English"), ("fr-fr", "Français"), - ("de-de", "Deutsch"), - ("nl-nl", "Nederlands"), - ("es-es", "Español"), + # ("de-de", "Deutsch"), + # ("nl-nl", "Nederlands"), + # ("es-es", "Español"), ], default=None, help_text="The language in which the user wants to see the interface.", diff --git a/src/backend/core/tests/test_api_config.py b/src/backend/core/tests/test_api_config.py index 87319a9..f5642be 100644 --- a/src/backend/core/tests/test_api_config.py +++ b/src/backend/core/tests/test_api_config.py @@ -51,9 +51,9 @@ def test_api_config(is_authenticated): "LANGUAGES": [ ["en-us", "English"], ["fr-fr", "Français"], - ["de-de", "Deutsch"], - ["nl-nl", "Nederlands"], - ["es-es", "Español"], + # ["de-de", "Deutsch"], + # ["nl-nl", "Nederlands"], + # ["es-es", "Español"], ], "LANGUAGE_CODE": "en-us", "MEDIA_BASE_URL": "http://testserver/", diff --git a/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts b/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts index 5456a90..96d3450 100644 --- a/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts +++ b/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts @@ -33,6 +33,7 @@ export const tokens = { 'greyscale-700': '#3A3A3A', 'greyscale-800': '#2A2A2A', 'greyscale-900': '#242424', + 'greyscale-050': '#EEF1F4', 'greyscale-000': '#fff', 'primary-100': '#ECECFE', 'primary-200': '#E3E3FD', @@ -77,7 +78,6 @@ export const tokens = { 'primary-050': '#F5F5FE', 'primary-150': '#F4F4FD', 'greyscale-text': '#303C4B', - 'greyscale-050': '#F6F6F6', 'greyscale-250': '#ddd', 'greyscale-350': '#ddd', 'greyscale-750': '#353535', diff --git a/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx b/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx index c52d877..b372b72 100644 --- a/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx +++ b/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx @@ -17,9 +17,16 @@ const fetchAPIAdapter = (input: RequestInfo | URL, init?: RequestInit) => { // Add force_web_search parameter if it's globally enabled if ((window as { globalForceWebSearch?: boolean }).globalForceWebSearch) { - const urlObj = new URL(url); - urlObj.searchParams.set('force_web_search', 'true'); - url = urlObj.toString(); + // For relative URLs, just append the parameter + if (url.startsWith('http')) { + const urlObj = new URL(url); + urlObj.searchParams.set('force_web_search', 'true'); + url = urlObj.toString(); + } else { + // For relative URLs, append the parameter manually + const separator = url.includes('?') ? '&' : '?'; + url = `${url}${separator}force_web_search=true`; + } } return fetchAPI(url, init); diff --git a/src/frontend/apps/conversations/src/features/chat/components/AttachmentList.tsx b/src/frontend/apps/conversations/src/features/chat/components/AttachmentList.tsx new file mode 100644 index 0000000..24b5278 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat/components/AttachmentList.tsx @@ -0,0 +1,112 @@ +import { Box, BoxButton, Icon, Text } from '@/components'; + +// Define Attachment type locally (mirroring backend structure) +export interface Attachment { + name?: string; + contentType?: string; + url: string; +} + +interface AttachmentListProps { + attachments: Attachment[]; + onRemove?: (index: number) => void; + isReadOnly?: boolean; +} + +export const AttachmentList = ({ + attachments, + onRemove, + isReadOnly = false, +}: AttachmentListProps) => { + if (!attachments || attachments.length === 0) { + return null; + } + + return ( + + {attachments.map((attachment, idx) => { + const { name } = attachment; + const removeAttachment = () => { + if (onRemove) { + onRemove(idx); + } + }; + + return ( + + + {/* Extension du fichier */} + + + {name?.split('.').pop()?.toUpperCase() || 'FILE'} + + + + {name} + + {!isReadOnly && onRemove && ( + + + + )} + + + ); + })} + + ); +}; diff --git a/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx b/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx index bd0e183..540e91a 100644 --- a/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx +++ b/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx @@ -19,6 +19,7 @@ import { Box, Icon, Loader, Text } from '@/components'; import { useChat } from '@/features/chat/api/useChat'; import { getConversation } from '@/features/chat/api/useConversation'; import { useCreateChatConversation } from '@/features/chat/api/useCreateConversation'; +import { AttachmentList } from '@/features/chat/components/AttachmentList'; import { InputChat } from '@/features/chat/components/InputChat'; import { SourceItemList } from '@/features/chat/components/SourceItemList'; import { ToolInvocationItem } from '@/features/chat/components/ToolInvocationItem'; @@ -42,7 +43,7 @@ export const Chat = ({ }) => { const { t } = useTranslation(); const copyToClipboard = useClipboard(); - const { isDesktop } = useResponsiveStore(); + const { isMobile } = useResponsiveStore(); const streamProtocol = 'data'; // or 'text' const [forceWebSearch, setForceWebSearch] = useState(false); @@ -374,14 +375,7 @@ export const Chat = ({ flex-basis: auto; height: 100%; flex-grow: 1; - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } + animation: fade-in 0.3s ease-out forwards; `} > 1 ? 'calc(100vh - 62px)' : '0'}; - max-height: ${messages.length > 1 ? 'calc(100vh - 62px)' : '0'}; + height: ${messages.length > 0 ? 'calc(100vh - 62px)' : '0'}; + max-height: ${messages.length > 0 ? 'calc(100vh - 62px)' : '0'}; `} > {messages.length > 0 && ( @@ -407,7 +401,7 @@ export const Chat = ({ key={message.id} data-message-id={message.id} $css={` - display: flex; + display: flex; width: 100%; margin: auto; padding-left: 12px; @@ -419,23 +413,45 @@ export const Chat = ({ + {/* Message content */} {message.content && ( - , - }} + - {message.content} - + , + }} + > + {message.content} + + )} + + {/* Attachments section */} + {message.experimental_attachments && + message.experimental_attachments.length > 0 && ( + + + + )} + + {/* Reasoning and tool invocations */} {message.parts ?.filter( @@ -461,25 +477,6 @@ export const Chat = ({ /> ) : null, )} - {/* Show attachments if present */} - {message.experimental_attachments?.map( - (attachment: Attachment, index: number) => - attachment.contentType?.includes('text/') || - attachment.contentType?.includes('image/') ? ( -
- {attachment.name} -
- ) : null, - )}
{message.role !== 'user' && ( - {isDesktop && ( + {!isMobile && ( {t('Copy')} @@ -564,7 +561,7 @@ export const Chat = ({ $variation="600" $size="16px" /> - {isDesktop && ( + {!isMobile && ( {t('Show sources')} @@ -607,6 +604,7 @@ export const Chat = ({ position: relative; bottom: 20px; margin: auto; + z-index: 1000; `} $gap="6px" $height="auto" diff --git a/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx b/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx index 71705d1..5578a61 100644 --- a/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx +++ b/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx @@ -2,9 +2,10 @@ import { Button } from '@openfun/cunningham-react'; import React, { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box, BoxButton, Icon, Text } from '@/components'; +import { Box, Icon, Text } from '@/components'; import { useResponsiveStore } from '@/stores'; +import { AttachmentList } from './AttachmentList'; import { ScrollDown } from './ScrollDown'; import { SendButton } from './SendButton'; @@ -40,7 +41,7 @@ export const InputChat = ({ const { t } = useTranslation(); const fileInputRef = useRef(null); const [isDragActive, setIsDragActive] = useState(false); - const { isDesktop } = useResponsiveStore(); + const { isDesktop, isMobile } = useResponsiveStore(); const [currentSuggestionIndex, setCurrentSuggestionIndex] = useState(0); const suggestions = [ @@ -69,55 +70,28 @@ export const InputChat = ({ width: 100%; padding: ${isDesktop ? '0' : '0 10px'}; max-width: 750px; - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - @keyframes fadeInDown { - from { - opacity: 0; - transform: translateY(-20px); - } - to { - opacity: 1; - transform: translateY(0); - } - } - @keyframes slideOutToBottom { - from { - opacity: 1; - transform: translateY(0); - } - to { - opacity: 0; - transform: translateY(20px); - } - } `} > {/* Bouton de scroll vers le bas */} - {messagesLength > 1 && containerRef && ( - 1 && + status !== 'streaming' && + containerRef && + onScrollToBottom && ( + - {})} - containerRef={containerRef} - /> - - )} + `} + > + + + )} {/* Message de bienvenue */} {messagesLength === 0 && ( @@ -180,9 +154,9 @@ export const InputChat = ({ messagesLength === 0 ? ` opacity: 0; - animation: fadeIn 0.4s cubic-bezier(1,0,0,1) 0s both; + animation: fade-in 0.4s cubic-bezier(1,0,0,1) 0s both; ` - : 'animation: fadeIn 0.4s cubic-bezier(1,0,0,1) 0.4s both;' + : 'animation: fade-in 0.4s cubic-bezier(1,0,0,1) 0.4s both;' } > 0 && ( - {Array.from(files).map((file, idx) => { - const { type: _type, name } = file; - const removeFile = () => { + ({ + name: file.name, + contentType: file.type, + url: URL.createObjectURL(file), + }))} + onRemove={(index) => { const dt = new DataTransfer(); Array.from(files).forEach((f, i) => { - if (i !== idx) { + if (i !== index) { dt.items.add(f); } }); setFiles(dt.files.length > 0 ? dt.files : null); - }; - - return ( - - {/*{type.startsWith('image/') ? ( - {name} - ) : ( - - )}*/} - - - {name} - - - - - - - ); - })} + }} + isReadOnly={false} + /> )} } > - {isDesktop && {t('Attach file')}} + {!isMobile && {t('Attach file')}} {onToggleWebSearch && ( )} diff --git a/src/frontend/apps/conversations/src/features/chat/components/ScrollDown.tsx b/src/frontend/apps/conversations/src/features/chat/components/ScrollDown.tsx index 3042dc5..253bf92 100644 --- a/src/frontend/apps/conversations/src/features/chat/components/ScrollDown.tsx +++ b/src/frontend/apps/conversations/src/features/chat/components/ScrollDown.tsx @@ -42,16 +42,7 @@ export const ScrollDown: React.FC = ({ z-index: 1000; opacity: ${isVisible ? '1' : '0'}; transition: opacity 0.3s ease; - animation: fadeIn 0.3s ease; - - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } + pointer-events: ${isVisible ? 'auto' : 'none'}; `} >