diff --git a/src/assistant/AssistantProvider.jsx b/src/assistant/AssistantProvider.jsx index b15808bf46..49fedc4358 100644 --- a/src/assistant/AssistantProvider.jsx +++ b/src/assistant/AssistantProvider.jsx @@ -64,7 +64,9 @@ const AssistantProvider = ({ children }) => { })) }, 250) } - } else { + } + + if (res.object === 'delta') { setAssistantState(v => ({ ...v, message: v.message + res.content, diff --git a/src/assistant/Conversations/ChatAssistantItem.jsx b/src/assistant/Conversations/ChatAssistantItem.jsx index c200828e7f..7dfc4e8c99 100644 --- a/src/assistant/Conversations/ChatAssistantItem.jsx +++ b/src/assistant/Conversations/ChatAssistantItem.jsx @@ -5,21 +5,25 @@ import Icon from 'cozy-ui/transpiled/react/Icon' import AssistantIcon from 'assets/images/icon-assistant.png' +import Sources from './Sources/Sources' import ChatItem from './ChatItem' -const ChatAssistantItem = ({ className, label, ...props }) => { +const ChatAssistantItem = ({ className, id, label, sources, ...props }) => { const { t } = useI18n() // need memo to avoid rendering it everytime const icon = useMemo(() => , []) return ( - + <> + + {sources?.length > 0 && } + ) } diff --git a/src/assistant/Conversations/ChatConversation.jsx b/src/assistant/Conversations/ChatConversation.jsx index 53f4bcb4d6..6dd585a839 100644 --- a/src/assistant/Conversations/ChatConversation.jsx +++ b/src/assistant/Conversations/ChatConversation.jsx @@ -42,7 +42,9 @@ const ChatConversation = ({ conversation, myself }) => { ) } @@ -55,7 +57,9 @@ const ChatConversation = ({ conversation, myself }) => { ) })} diff --git a/src/assistant/Conversations/Sources/Sources.jsx b/src/assistant/Conversations/Sources/Sources.jsx new file mode 100644 index 0000000000..b7434504ce --- /dev/null +++ b/src/assistant/Conversations/Sources/Sources.jsx @@ -0,0 +1,86 @@ +import React, { useState, useRef, useEffect } from 'react' + +import { useQuery, isQueryLoading } from 'cozy-client' + +import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' +import Icon from 'cozy-ui/transpiled/react/Icon' +import Chip from 'cozy-ui/transpiled/react/Chips' +import MultiFilesIcon from 'cozy-ui/transpiled/react/Icons/MultiFiles' +import RightIcon from 'cozy-ui/transpiled/react/Icons/Right' +import Box from 'cozy-ui/transpiled/react/Box' +import Grow from 'cozy-ui/transpiled/react/Grow' + +import { buildFilesByIds } from 'assistant/queries' +import SourcesItem from './SourcesItem' + +const Sources = ({ messageId, files }) => { + const [showSources, setShowSources] = useState(false) + const { t } = useI18n() + const ref = useRef() + + const handleShowSources = () => { + setShowSources(v => !v) + } + + useEffect(() => { + if (showSources) { + const sourcesBottom = ref.current.getBoundingClientRect().bottom + const innerContainer = + document.getElementsByClassName('cozyDialogContent')[0] + const innerContainerBottom = innerContainer.getBoundingClientRect().bottom + if (sourcesBottom > innerContainerBottom) { + ref.current.scrollIntoView(false) + } + } + }, [showSources]) + + return ( + + } + label={t('assistant.sources', files.length)} + deleteIcon={ + + } + clickable + onClick={handleShowSources} + onDelete={handleShowSources} + /> + +
+ {files.map(file => ( + + ))} +
+
+
+ ) +} + +const SourcesWithFilesQuery = ({ messageId, sources }) => { + const fileIds = sources.map(source => source.id) + + const filesByIds = buildFilesByIds(fileIds) + const { data: files, ...queryResult } = useQuery( + filesByIds.definition, + filesByIds.options + ) + + const isLoading = isQueryLoading(queryResult) + + if (isLoading || files.length === 0) return null + + return +} + +export default SourcesWithFilesQuery diff --git a/src/assistant/Conversations/Sources/SourcesItem.jsx b/src/assistant/Conversations/Sources/SourcesItem.jsx new file mode 100644 index 0000000000..cc0e328a9a --- /dev/null +++ b/src/assistant/Conversations/Sources/SourcesItem.jsx @@ -0,0 +1,48 @@ +import React from 'react' + +import { useClient, generateWebLink } from 'cozy-client' +import { isNote } from 'cozy-client/dist/models/file' +import Icon from 'cozy-ui/transpiled/react/Icon' +import ListItem from 'cozy-ui/transpiled/react/ListItem' +import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon' +import ListItemText from 'cozy-ui/transpiled/react/ListItemText' + +import { getDriveMimeTypeIcon } from 'assistant/Search/getIconForSearchResult' + +import styles from './styles.styl' + +const SourcesItem = ({ file }) => { + const client = useClient() + + const docUrl = generateWebLink({ + slug: isNote(file) ? 'notes' : 'drive', + cozyUrl: client?.getStackClient().uri, + subDomainType: client?.getInstanceOptions().subdomain, + hash: isNote(file) + ? `/n/${file._id}` + : `/folder/${file.dir_id}/file/${file._id}` + }) + + return ( + + + + + + + ) +} + +export default SourcesItem diff --git a/src/assistant/Conversations/Sources/styles.styl b/src/assistant/Conversations/Sources/styles.styl new file mode 100644 index 0000000000..e6210102cf --- /dev/null +++ b/src/assistant/Conversations/Sources/styles.styl @@ -0,0 +1,5 @@ +.sourcesItem + // !important here to override Mui styles + margin-bottom 0.25rem !important + border 1px solid var(--borderMainColor) !important + border-radius 8px !important \ No newline at end of file diff --git a/src/assistant/Search/getIconForSearchResult.js b/src/assistant/Search/getIconForSearchResult.js index c6a1c55e0c..c61101832e 100644 --- a/src/assistant/Search/getIconForSearchResult.js +++ b/src/assistant/Search/getIconForSearchResult.js @@ -70,7 +70,7 @@ export const getIconForSearchResult = searchResult => { * @param {boolean} [options.isEncrypted] - Indicates whether the file is encrypted. Default is false. * @returns {import('react').ReactNode} - The icon corresponding to the file's mime type. */ -const getDriveMimeTypeIcon = ( +export const getDriveMimeTypeIcon = ( isDirectory, name, mime, diff --git a/src/assistant/Views/AssistantDialog.jsx b/src/assistant/Views/AssistantDialog.jsx index d045794226..14a6f3fef5 100644 --- a/src/assistant/Views/AssistantDialog.jsx +++ b/src/assistant/Views/AssistantDialog.jsx @@ -4,18 +4,21 @@ import { useNavigate } from 'react-router-dom' import { FixedDialog } from 'cozy-ui/transpiled/react/CozyDialogs' import { useBreakpoints } from 'cozy-ui/transpiled/react/providers/Breakpoints' +import { useSearch } from '../Search/SearchProvider' import Conversation from '../Conversations/Conversation' import ConversationBar from '../Conversations/ConversationBar' import { useAssistant } from '../AssistantProvider' const AssistantDialog = () => { const { assistantState, clearAssistant } = useAssistant() + const { clearSearch } = useSearch() const { isMobile } = useBreakpoints() const navigate = useNavigate() const onClose = () => { navigate('..') clearAssistant() + clearSearch() } return ( diff --git a/src/assistant/queries.js b/src/assistant/queries.js index 570f4ed15a..cd88cfb664 100644 --- a/src/assistant/queries.js +++ b/src/assistant/queries.js @@ -3,9 +3,27 @@ import { Q, fetchPolicies } from 'cozy-client' const CONTACTS_DOCTYPE = 'io.cozy.contacts' export const CHAT_CONVERSATIONS_DOCTYPE = 'io.cozy.ai.chat.conversations' export const CHAT_EVENTS_DOCTYPE = 'io.cozy.ai.chat.events' +export const FILES_DOCTYPE = 'io.cozy.files' const defaultFetchPolicy = fetchPolicies.olderThan(86_400_000) // 24 hours +// we don't use getByIds here to get `path` attribute in the result +// this have to be fixed, it's a work in progess +// meanwhile we use this sub-optimal request +export const buildFilesByIds = ids => { + return { + definition: Q(FILES_DOCTYPE).where({ + _id: { + $in: ids + } + }), + options: { + as: `${FILES_DOCTYPE}/${ids.join('')}`, + fetchPolicy: defaultFetchPolicy + } + } +} + export const buildChatConversationQueryById = id => { return { definition: Q(CHAT_CONVERSATIONS_DOCTYPE).getById(id), diff --git a/src/components/AppWrapper.jsx b/src/components/AppWrapper.jsx index 94d4d35d79..710e93cb35 100644 --- a/src/components/AppWrapper.jsx +++ b/src/components/AppWrapper.jsx @@ -68,6 +68,7 @@ const Inner = ({ children, lang, context }) => ( {children} + diff --git a/src/locales/de.json b/src/locales/de.json index 5056292a14..181330a953 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -11,6 +11,7 @@ "hello_name":"Hallo %{name}, was kann ich für Sie tun?", "hello":"Hallo, was kann ich für Sie tun?", "name":"Cozy Assistent", + "sources":"%{smart_count} Quelle |||| %{smart_count} Quellen", "suggestions": { "find_file": "Eine Datei suchen", "reimbursements": "Meine Rückerstattungen überprüfen", diff --git a/src/locales/en.json b/src/locales/en.json index e944678255..0e8b83d779 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -11,6 +11,7 @@ "hello_name":"Hello %{name}, what can I do for you?", "hello":"Hello, what can I do for you?", "name":"Cozy Assistant", + "sources":"%{smart_count} source |||| %{smart_count} sources", "suggestions": { "find_file": "Search a file", "reimbursements": "Check my repayments", diff --git a/src/locales/es.json b/src/locales/es.json index e413b57a41..a06c665c9a 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -11,6 +11,7 @@ "hello_name":"Hola %{name}, ¿en qué puedo ayudarle?", "hello":"Hola, ¿en qué puedo ayudarle?", "name":"Asistente Cozy", + "sources":"%{smart_count} fuente |||| %{smart_count} fuentes", "suggestions": { "find_file": "Buscar un fichero", "reimbursements": "Comprobar mis reembolsos", diff --git a/src/locales/fr.json b/src/locales/fr.json index da3e2da4f5..a0dce4ed43 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -11,6 +11,7 @@ "hello_name":"Bonjour %{name}, que puis-je faire pour vous ?", "hello":"Bonjour, que puis-je faire pour vous ?", "name":"Assistant Cozy", + "sources":"%{smart_count} source |||| %{smart_count} sources", "suggestions": { "find_file": "Rechercher un fichier", "reimbursements": "Vérifier mes remboursements", diff --git a/src/locales/it.json b/src/locales/it.json index da6ce6d9c3..e6aeced2a5 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -11,6 +11,7 @@ "hello_name":"Salve %{name}, cosa posso fare per lei?", "hello":"Salve, cosa posso fare per lei?", "name":"Assistente Cozy", + "sources":"%{smart_count} fonte |||| %{smart_count} fonti", "suggestions": { "find_file": "Ricerca di un file", "reimbursements": "Controllare i miei rimborsi", diff --git a/src/locales/ja.json b/src/locales/ja.json index 7491b33509..902abf79a9 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -11,6 +11,7 @@ "hello_name":"${name}さん、こんにちは。", "hello":"こんにちは、何かご用ですか?", "name":"アシスタント Cozy", + "sources":"%{smart_count} ソース |||| %{smart_count} ソース", "suggestions": { "find_file": "ファイルの検索", "reimbursements": "返済額の確認", diff --git a/src/locales/nl_NL.json b/src/locales/nl_NL.json index 4b2d8ba335..4678ffbe71 100644 --- a/src/locales/nl_NL.json +++ b/src/locales/nl_NL.json @@ -11,6 +11,7 @@ "hello_name":"Hallo %{name}, wat kan ik voor je doen?", "hello":"Hallo, wat kan ik voor u doen?", "name":"Cozy Assistent", + "sources":"%{smart_count} bron |||| %{smart_count} bronnen", "suggestions": { "find_file": "Een bestand zoeken", "reimbursements": "Mijn vergoedingen controleren",