Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assistant: Add sources #2225

Merged
merged 5 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/assistant/AssistantProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ const AssistantProvider = ({ children }) => {
}))
}, 250)
}
} else {
}

if (res.object === 'delta') {
setAssistantState(v => ({
...v,
message: v.message + res.content,
Expand Down
20 changes: 12 additions & 8 deletions src/assistant/Conversations/ChatAssistantItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => <Icon icon={AssistantIcon} size={32} />, [])

return (
<ChatItem
{...props}
className={className}
icon={icon}
name={t('assistant.name')}
label={label}
/>
<>
<ChatItem
{...props}
className={className}
icon={icon}
name={t('assistant.name')}
label={label}
/>
{sources?.length > 0 && <Sources messageId={id} sources={sources} />}
</>
)
}

Expand Down
4 changes: 4 additions & 0 deletions src/assistant/Conversations/ChatConversation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ const ChatConversation = ({ conversation, myself }) => {
<ChatAssistantItem
key={conversation._id + '--' + idx}
className="u-mt-1-half"
id={message.id}
label={message.content}
sources={message.sources}
/>
)
}
Expand All @@ -55,7 +57,9 @@ const ChatConversation = ({ conversation, myself }) => {
<ChatAssistantItem
key={conversation._id + '--' + idx}
className="u-mt-1-half"
id={message.id}
label={message.content}
sources={message.sources}
/>
)
})}
Expand Down
86 changes: 86 additions & 0 deletions src/assistant/Conversations/Sources/Sources.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Box ref={ref} className="u-mt-1-half" pl="44px">
<Chip
className="u-mb-1"
icon={<Icon icon={MultiFilesIcon} className="u-ml-half" />}
label={t('assistant.sources', files.length)}
deleteIcon={
<Icon
className="u-h-1"
icon={RightIcon}
rotate={showSources ? 90 : 0}
/>
}
clickable
onClick={handleShowSources}
onDelete={handleShowSources}
cballevre marked this conversation as resolved.
Show resolved Hide resolved
/>
<Grow
in={showSources}
style={{ transformOrigin: '0 0 0' }}
mountOnEnter={true}
unmountOnExit={true}
>
<div>
{files.map(file => (
<SourcesItem key={`${messageId}-${file._id}`} file={file} />
))}
</div>
</Grow>
</Box>
)
}

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 <Sources messageId={messageId} files={files} />
}

export default SourcesWithFilesQuery
48 changes: 48 additions & 0 deletions src/assistant/Conversations/Sources/SourcesItem.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<ListItem
JF-Cozy marked this conversation as resolved.
Show resolved Hide resolved
className={styles['sourcesItem']}
component="a"
href={docUrl}
target="_blank"
button
>
<ListItemIcon>
<Icon
icon={getDriveMimeTypeIcon(false, file.name, file.mime)}
size={32}
/>
</ListItemIcon>
<ListItemText
primary={file.name}
secondary={file.path.replace(file.name, '')}
/>
</ListItem>
)
}

export default SourcesItem
5 changes: 5 additions & 0 deletions src/assistant/Conversations/Sources/styles.styl
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/assistant/Search/getIconForSearchResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions src/assistant/Views/AssistantDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
18 changes: 18 additions & 0 deletions src/assistant/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions src/components/AppWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const Inner = ({ children, lang, context }) => (
{children}
<RealTimeQueries doctype="io.cozy.ai.chat.conversations" />
<RealTimeQueries doctype="io.cozy.apps" />
<RealTimeQueries doctype="io.cozy.files" />
<RealTimeQueries doctype="io.cozy.jobs" />
<RealTimeQueries doctype="io.cozy.triggers" />
<RealTimeQueries doctype="io.cozy.konnectors" />
Expand Down
1 change: 1 addition & 0 deletions src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"hello_name":"${name}さん、こんにちは。",
"hello":"こんにちは、何かご用ですか?",
"name":"アシスタント Cozy",
"sources":"%{smart_count} ソース |||| %{smart_count} ソース",
"suggestions": {
"find_file": "ファイルの検索",
"reimbursements": "返済額の確認",
Expand Down
1 change: 1 addition & 0 deletions src/locales/nl_NL.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading