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}
-
-
-
-
-
-
- );
- })}
+ }}
+ isReadOnly={false}
+ />
)}
}
>
- {isDesktop && {t('Attach file')}}
+ {!isMobile && {t('Attach file')}}
{onToggleWebSearch && (
}
>
- {isDesktop && (
+ {!isMobile && (
{t('Research on the web')}
)}
+ {isMobile && forceWebSearch && (
+
+
+ {t('Web')}
+
+
+
+ )}
)}
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'};
`}
>