Skip to content
Open
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
6 changes: 6 additions & 0 deletions jestHelpers/setup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React from 'react'
import { TransformStream, ReadableStream } from 'node:stream/web'

// jsdom doesn't expose Web Streams/Fetch APIs needed by assistant-stream
if (!global.TransformStream) global.TransformStream = TransformStream
if (!global.ReadableStream) global.ReadableStream = ReadableStream
if (!global.Response) global.Response = class Response {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The minimal Response stub may be insufficient.

The empty Response class lacks essential properties and methods (ok, status, json(), text(), body, etc.) that code may rely on. If assistant-stream or related code accesses these members, tests will fail silently or throw.

Consider using a more complete polyfill:

♻️ Suggested improvement
-if (!global.Response) global.Response = class Response {}
+if (!global.Response) {
+  // Minimal Response polyfill for assistant-stream
+  global.Response = class Response {
+    constructor(body, init = {}) {
+      this.body = body
+      this.status = init.status ?? 200
+      this.ok = this.status >= 200 && this.status < 300
+      this.headers = new Map(Object.entries(init.headers ?? {}))
+    }
+    async json() { return JSON.parse(this.body) }
+    async text() { return String(this.body) }
+  }
+}

Alternatively, use whatwg-fetch (already in dependencies) or undici's fetch polyfill which provides a complete implementation.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!global.Response) global.Response = class Response {}
if (!global.Response) {
// Minimal Response polyfill for assistant-stream
global.Response = class Response {
constructor(body, init = {}) {
this.body = body
this.status = init.status ?? 200
this.ok = this.status >= 200 && this.status < 300
this.headers = new Map(Object.entries(init.headers ?? {}))
}
async json() { return JSON.parse(this.body) }
async text() { return String(this.body) }
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jestHelpers/setup.js` at line 7, Replace the minimal stubbed Response class
(global.Response = class Response {}) with a full fetch/Response polyfill;
either import and invoke the existing whatwg-fetch or undici fetch polyfill in
jestHelpers/setup.js so global.Response (and associated properties/methods like
ok, status, json, text, body) are provided instead of the empty class, ensuring
any code that uses Response (e.g., assistant-stream) gets the expected behavior.


global.cozy = {}

Expand Down
11 changes: 8 additions & 3 deletions manifest.webapp
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,17 @@
"description": "Remote-doctype required to send anonymized measures to the DACC shared among mycozy.eu's Cozy."
},
"chatConversations": {
"description": "Required by the cozy Assistant",
"description": "Required by the AI Assistant to show conversations",
"type": "io.cozy.ai.chat.conversations",
"verbs": ["GET", "POST"]
"verbs": ["ALL"]
},
"assistants": {
"type": "io.cozy.ai.chat.assistants",
"verbs": ["ALL"],
"description": "Required to fetch, create and update AI assistants"
},
Comment on lines 217 to 226
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider using least-privilege permissions instead of ["ALL"].

Both chatConversations and assistants permissions now use ["ALL"] verbs, which includes DELETE. Following the principle of least privilege, specify only the verbs actually needed by the feature.

If the assistant only needs to read, create, and update:

🛡️ Suggested restriction
     "chatConversations": {
       "description": "Required by the AI Assistant to show conversations",
       "type": "io.cozy.ai.chat.conversations",
-      "verbs": ["ALL"]
+      "verbs": ["GET", "POST", "PUT", "PATCH"]
     },
     "assistants": {
       "type": "io.cozy.ai.chat.assistants",
-      "verbs": ["ALL"],
+      "verbs": ["GET", "POST", "PUT", "PATCH"],
       "description": "Required to fetch, create and update AI assistants"
     },

If DELETE is genuinely required (e.g., for clearing conversation history), the current configuration is acceptable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@manifest.webapp` around lines 217 - 226, Replace the broad verbs ["ALL"] on
the permissions objects chatConversations and assistants with least-privilege
verb lists that match actual usage (e.g., only read/create/update verbs such as
GET, POST, PUT/PATCH) so DELETE is not implicitly granted; update the "verbs"
arrays on the chatConversations and assistants entries to the minimal set
required by the feature, and only retain "DELETE" if the code explicitly
requires conversation deletion.

"chatEvents": {
"description": "Required by the cozy Assistant",
"description": "Required by the AI Assistant to receive realtime events",
"type": "io.cozy.ai.chat.events",
"verbs": ["GET"]
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"cozy-minilog": "3.9.1",
"cozy-pouch-link": "^60.19.0",
"cozy-realtime": "^5.8.0",
"cozy-search": "^0.14.1",
"cozy-search": "^0.25.0",
"cozy-sharing": "^30.0.1",
"cozy-stack-client": "^60.23.0",
"cozy-ui": "^137.0.0",
Expand Down
12 changes: 12 additions & 0 deletions src/lib/doctypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const DOCTYPE_PHOTOS_SETTINGS = 'io.cozy.photos.settings'
export const DOCTYPE_APPS = 'io.cozy.apps'
export const DOCTYPE_CONTACTS = 'io.cozy.contacts'
export const DOCTYPE_KONNECTORS = 'io.cozy.konnectors'
export const DOCTYPE_AI_CHAT_CONVERSATIONS = 'io.cozy.ai.chat.conversations'
export const DOCTYPE_AI_CHAT_ASSISTANTS = 'io.cozy.ai.chat.assistants'
Comment on lines +14 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how other doctypes without schema entries are handled
rg -n 'RealTimeQueries' --type=js -A 2 -B 2
# Check if there are other doctypes used without schema entries
rg -n 'DOCTYPE_' src/lib/doctypes.js

Repository: linagora/twake-drive

Length of output: 7153


🏁 Script executed:

cat -n src/lib/doctypes.js

Repository: linagora/twake-drive

Length of output: 1979


Add a schema entry for the assistants doctype.

DOCTYPE_AI_CHAT_ASSISTANTS is used in RealTimeQueries (in AssistantLayout.jsx) and referenced as a relationship target in the conversations schema, but lacks its own schema definition. While this works for realtime subscriptions, adding a schema entry ensures cozy-client properly normalizes and caches these documents, matching the pattern used for the conversations doctype.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/doctypes.js` around lines 14 - 15, Add a schema entry for the
assistants doctype so cozy-client can normalize/cache assistant docs: create a
schema object keyed by DOCTYPE_AI_CHAT_ASSISTANTS (the same constant exported
alongside DOCTYPE_AI_CHAT_CONVERSATIONS) mirroring the pattern used for the
conversations schema (include id, relationships, and any necessary attributes)
so RealTimeQueries/AssistantLayout.jsx subscriptions and the conversations
schema relationship target are properly recognized and normalized by
cozy-client.

export const DOCTYPE_CONTACTS_VERSION = 2

export const schema = {
Expand All @@ -33,5 +35,15 @@ export const schema = {
},
groups: { doctype: Group.doctype },
versions: { doctype: 'io.cozy.files.versions' },
conversations: {
doctype: DOCTYPE_AI_CHAT_CONVERSATIONS,
attributes: {},
relationships: {
assistant: {
type: 'has-one',
doctype: 'io.cozy.ai.chat.assistants'
}
}
},
Comment on lines +38 to +47
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use the constant instead of hardcoded doctype string.

Line 44 uses the literal string 'io.cozy.ai.chat.assistants' instead of the DOCTYPE_AI_CHAT_ASSISTANTS constant defined on line 15. This creates a maintenance risk if the doctype name changes.

♻️ Suggested fix
   conversations: {
     doctype: DOCTYPE_AI_CHAT_CONVERSATIONS,
     attributes: {},
     relationships: {
       assistant: {
         type: 'has-one',
-        doctype: 'io.cozy.ai.chat.assistants'
+        doctype: DOCTYPE_AI_CHAT_ASSISTANTS
       }
     }
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
conversations: {
doctype: DOCTYPE_AI_CHAT_CONVERSATIONS,
attributes: {},
relationships: {
assistant: {
type: 'has-one',
doctype: 'io.cozy.ai.chat.assistants'
}
}
},
conversations: {
doctype: DOCTYPE_AI_CHAT_CONVERSATIONS,
attributes: {},
relationships: {
assistant: {
type: 'has-one',
doctype: DOCTYPE_AI_CHAT_ASSISTANTS
}
}
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/doctypes.js` around lines 38 - 47, Replace the hardcoded doctype
string in the conversations.relationships.assistant block with the
DOCTYPE_AI_CHAT_ASSISTANTS constant: locate the conversations object (symbol
DOCTYPE_AI_CHAT_CONVERSATIONS) and update the assistant.relationships.type value
that currently reads 'io.cozy.ai.chat.assistants' to reference
DOCTYPE_AI_CHAT_ASSISTANTS so the module uses the defined constant rather than a
literal string.

...extraDoctypes
}
9 changes: 8 additions & 1 deletion src/modules/navigation/AppRoute.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import flag from 'cozy-flags'
import ExternalRedirect from './ExternalRedirect'
import Index from './Index'
import AIAssistantPaywallView from '../views/AI/AIAssistantPaywallView'
import AssistantLayout from '../views/Assistant/AssistantLayout'
import { DriveFolderView } from '../views/Drive/DriveFolderView'
import FilesViewerDrive from '../views/Drive/FilesViewerDrive'
import OnlyOfficeView from '../views/OnlyOffice'
Expand Down Expand Up @@ -54,6 +55,10 @@ import { SharedDriveFolderView } from '@/modules/views/SharedDrive/SharedDriveFo
import { TrashDestroyView } from '@/modules/views/Trash/TrashDestroyView'
import { TrashEmptyView } from '@/modules/views/Trash/TrashEmptyView'

const filteredBarRoutes = BarRoutes.filter(
r => r.props?.path !== 'assistant/:conversationId'
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to filter bar routes here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And why we did not have to do it in home? cc @lethemanh


const FilesRedirect = () => {
const { folderId } = useParams()
return <Navigate to={`/folder/${folderId}`} replace={true} />
Expand All @@ -76,6 +81,8 @@ const AppRoute = () => (
<Route path="note/:fileId" element={<PublicNoteRedirect />} />
<Route path="note/:driveId/:fileId" element={<PublicNoteRedirect />} />

<Route path="assistant/:conversationId" element={<AssistantLayout />} />

Comment on lines +84 to +85
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Getting worse: Large Method
AppRoute increases from 162 to 163 lines of code, threshold = 120

Suppress

<Route element={<Layout />}>
<Route path="upload" element={<UploaderComponent />} />
<Route path="/files/:folderId" element={<FilesRedirect />} />
Expand Down Expand Up @@ -244,7 +251,7 @@ const AppRoute = () => (
<Route path="move" element={<MoveFilesView />} />
</Route>

{BarRoutes.map(BarRoute => BarRoute)}
{filteredBarRoutes}
</Route>
</SentryRoutes>
)
Expand Down
41 changes: 41 additions & 0 deletions src/modules/views/Assistant/AssistantLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import cx from 'classnames'
import React from 'react'

import { BarComponent } from 'cozy-bar'
import { RealTimeQueries } from 'cozy-client'
import { AiText, AssistantView } from 'cozy-search'
import TwakeWorkplace from 'cozy-ui/transpiled/react/Icons/TwakeWorkplace'
import { Layout as LayoutUI } from 'cozy-ui/transpiled/react/Layout'

import styles from './assistant.styl'

import {
DOCTYPE_AI_CHAT_ASSISTANTS,
DOCTYPE_AI_CHAT_CONVERSATIONS
} from '@/lib/doctypes'

const AssistantLayout = () => {
return (
<LayoutUI monoColumn>
<RealTimeQueries doctype={DOCTYPE_AI_CHAT_CONVERSATIONS} />
<RealTimeQueries doctype={DOCTYPE_AI_CHAT_ASSISTANTS} />
<BarComponent
searchOptions={{ enabled: true }}
appSlug="home" // hack to hide the first Twake Workplace icon
appIcon={TwakeWorkplace}
appTextIcon={AiText}
disableInternalStore
componentsProps={{
Wrapper: {
className: cx('u-elevation-0', styles['assistant-topbar-border'])
}
}}
/>
<main className={styles['assistant-view']}>
<AssistantView />
</main>
</LayoutUI>
)
}
Comment on lines +17 to +39
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if AssistantView expects a conversationId prop or uses useParams internally
rg -n --type=js 'AssistantView' -A 10 -B 2
# Check cozy-search for AssistantView props
ast-grep --pattern 'export $_ AssistantView'

Repository: linagora/twake-drive

Length of output: 1702


🏁 Script executed:

# Find router/route configuration files
fd -type f -name "*route*" -o -name "*router*" | head -20

Repository: linagora/twake-drive

Length of output: 234


🏁 Script executed:

# Search for conversationId usage in the codebase
rg -n "conversationId" --type=js | head -20

Repository: linagora/twake-drive

Length of output: 271


🏁 Script executed:

# Search for useParams usage in Assistant-related files
rg -n "useParams" --type=js | grep -i assistant

Repository: linagora/twake-drive

Length of output: 46


🏁 Script executed:

# Check the file structure around AssistantLayout for route configuration
rg -n "assistant/:conversationId\|:/conversationId" --type=js

Repository: linagora/twake-drive

Length of output: 46


🏁 Script executed:

# Search for conversationId usage more carefully
rg "conversationId" -n

Repository: linagora/twake-drive

Length of output: 271


🏁 Script executed:

# Check the full AssistantLayout.jsx file
cat src/modules/views/Assistant/AssistantLayout.jsx

Repository: linagora/twake-drive

Length of output: 1072


🏁 Script executed:

# Look for any context providers or hooks that might handle conversationId
rg "useParams|useContext|useState.*conversation" -n --type jsx

Repository: linagora/twake-drive

Length of output: 91


🏁 Script executed:

# Check package.json for cozy-search version/documentation
rg "cozy-search" -n

Repository: linagora/twake-drive

Length of output: 374


🏁 Script executed:

# Look for other route patterns with parameters to see how they're handled
rg "useParams" -n

Repository: linagora/twake-drive

Length of output: 6074


🏁 Script executed:

# Check if there's any documentation or comments about AssistantView
rg -B3 -A3 "AssistantView" -n

Repository: linagora/twake-drive

Length of output: 1142


Extract conversationId from route parameters or remove the unused route parameter.

The route is defined as assistant/:conversationId, but AssistantLayout does not extract this parameter. All comparable route components in the codebase (FilesViewer, DriveFolderView, SharedDriveFolderView, etc.) consistently use useParams() to extract their route parameters. Either extract the parameter and pass it to AssistantView if needed, or remove it from the route definition.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/views/Assistant/AssistantLayout.jsx` around lines 16 - 36,
AssistantLayout is not reading the route parameter conversationId defined on the
route; update AssistantLayout to import and call useParams() to extract
conversationId and pass it into <AssistantView conversationId={conversationId}
/> (or, if the route param is unnecessary, remove conversationId from the route
instead); specifically modify the AssistantLayout function to const {
conversationId } = useParams() and forward that value to the AssistantView
component (or coordinate route change if you choose to remove the param).


export default AssistantLayout
16 changes: 16 additions & 0 deletions src/modules/views/Assistant/assistant.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@require 'settings/breakpoints.styl'
@require '../../../styles/coz-bar-size.styl'

.assistant-view
padding 0
height 'calc(100vh - %s - 1px)' % $coz-bar-size
display flex
flex-direction column
flex 1

+small-screen()
.assistant-view
margin-top $coz-bar-size

.assistant-topbar-border
border-bottom 1px solid var(--dividerColor)
Loading
Loading