From f73936c26cb894c43f61a54f252e6db99860f3fd Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Wed, 23 Oct 2024 12:15:33 +0200 Subject: [PATCH 1/5] chore: Add files query and realtime --- src/assistant/queries.js | 18 ++++++++++++++++++ src/components/AppWrapper.jsx | 1 + 2 files changed, 19 insertions(+) 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} + From 7d43074e5fd4a4270023fe6951db5bc48f2fb6b6 Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Wed, 23 Oct 2024 12:17:19 +0200 Subject: [PATCH 2/5] feat(Assistant): Create sources component to show sources --- .../Conversations/Sources/Sources.jsx | 86 +++++++++++++++++++ .../Conversations/Sources/SourcesItem.jsx | 48 +++++++++++ .../Conversations/Sources/styles.styl | 5 ++ .../Search/getIconForSearchResult.js | 2 +- src/locales/de.json | 1 + src/locales/en.json | 1 + src/locales/es.json | 1 + src/locales/fr.json | 1 + src/locales/it.json | 1 + src/locales/ja.json | 1 + src/locales/nl_NL.json | 1 + 11 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/assistant/Conversations/Sources/Sources.jsx create mode 100644 src/assistant/Conversations/Sources/SourcesItem.jsx create mode 100644 src/assistant/Conversations/Sources/styles.styl diff --git a/src/assistant/Conversations/Sources/Sources.jsx b/src/assistant/Conversations/Sources/Sources.jsx new file mode 100644 index 0000000000..cf749c6c5d --- /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 = ({ 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, index) => ( + + ))} +
+
+
+ ) +} + +const SourcesWithFilesQuery = ({ 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/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", From 5d8859cd2bd52278b06104406733a75f357c6264 Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Wed, 23 Oct 2024 12:17:49 +0200 Subject: [PATCH 3/5] fix(Assistant): Empty search when closing Assistant dialog --- src/assistant/Views/AssistantDialog.jsx | 3 +++ 1 file changed, 3 insertions(+) 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 ( From 53a52912667886f8dec2c5f5a239649b4e4e1e72 Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Wed, 23 Oct 2024 12:18:21 +0200 Subject: [PATCH 4/5] feat(Assistant): Show sources when present --- src/assistant/AssistantProvider.jsx | 4 +++- .../Conversations/ChatAssistantItem.jsx | 20 +++++++++++-------- .../Conversations/ChatConversation.jsx | 2 ++ 3 files changed, 17 insertions(+), 9 deletions(-) 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..6f41c73866 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, 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..ccd88070e0 100644 --- a/src/assistant/Conversations/ChatConversation.jsx +++ b/src/assistant/Conversations/ChatConversation.jsx @@ -43,6 +43,7 @@ const ChatConversation = ({ conversation, myself }) => { key={conversation._id + '--' + idx} className="u-mt-1-half" label={message.content} + sources={message.sources} /> ) } @@ -56,6 +57,7 @@ const ChatConversation = ({ conversation, myself }) => { key={conversation._id + '--' + idx} className="u-mt-1-half" label={message.content} + sources={message.sources} /> ) })} From 027384e09ee3460f87ea4c32a63c57bc92819c14 Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Thu, 24 Oct 2024 11:04:02 +0200 Subject: [PATCH 5/5] feat: Replace index by messageId in Sources key --- src/assistant/Conversations/ChatAssistantItem.jsx | 4 ++-- src/assistant/Conversations/ChatConversation.jsx | 2 ++ src/assistant/Conversations/Sources/Sources.jsx | 10 +++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/assistant/Conversations/ChatAssistantItem.jsx b/src/assistant/Conversations/ChatAssistantItem.jsx index 6f41c73866..7dfc4e8c99 100644 --- a/src/assistant/Conversations/ChatAssistantItem.jsx +++ b/src/assistant/Conversations/ChatAssistantItem.jsx @@ -8,7 +8,7 @@ import AssistantIcon from 'assets/images/icon-assistant.png' import Sources from './Sources/Sources' import ChatItem from './ChatItem' -const ChatAssistantItem = ({ className, label, sources, ...props }) => { +const ChatAssistantItem = ({ className, id, label, sources, ...props }) => { const { t } = useI18n() // need memo to avoid rendering it everytime const icon = useMemo(() => , []) @@ -22,7 +22,7 @@ const ChatAssistantItem = ({ className, label, sources, ...props }) => { name={t('assistant.name')} label={label} /> - {sources?.length > 0 && } + {sources?.length > 0 && } ) } diff --git a/src/assistant/Conversations/ChatConversation.jsx b/src/assistant/Conversations/ChatConversation.jsx index ccd88070e0..6dd585a839 100644 --- a/src/assistant/Conversations/ChatConversation.jsx +++ b/src/assistant/Conversations/ChatConversation.jsx @@ -42,6 +42,7 @@ const ChatConversation = ({ conversation, myself }) => { @@ -56,6 +57,7 @@ const ChatConversation = ({ conversation, myself }) => { diff --git a/src/assistant/Conversations/Sources/Sources.jsx b/src/assistant/Conversations/Sources/Sources.jsx index cf749c6c5d..b7434504ce 100644 --- a/src/assistant/Conversations/Sources/Sources.jsx +++ b/src/assistant/Conversations/Sources/Sources.jsx @@ -13,7 +13,7 @@ import Grow from 'cozy-ui/transpiled/react/Grow' import { buildFilesByIds } from 'assistant/queries' import SourcesItem from './SourcesItem' -const Sources = ({ files }) => { +const Sources = ({ messageId, files }) => { const [showSources, setShowSources] = useState(false) const { t } = useI18n() const ref = useRef() @@ -58,8 +58,8 @@ const Sources = ({ files }) => { unmountOnExit={true} >
- {files.map((file, index) => ( - + {files.map(file => ( + ))}
@@ -67,7 +67,7 @@ const Sources = ({ files }) => { ) } -const SourcesWithFilesQuery = ({ sources }) => { +const SourcesWithFilesQuery = ({ messageId, sources }) => { const fileIds = sources.map(source => source.id) const filesByIds = buildFilesByIds(fileIds) @@ -80,7 +80,7 @@ const SourcesWithFilesQuery = ({ sources }) => { if (isLoading || files.length === 0) return null - return + return } export default SourcesWithFilesQuery