feat: implement AI Adapter layer, State Machines for Auth/Input, and Contextual AI Features#1168
feat: implement AI Adapter layer, State Machines for Auth/Input, and Contextual AI Features#1168vivekyadav-3 wants to merge 36 commits intoRocketChat:developfrom
Conversation
…ecording consistency
…ChatBody, and fix emoji insertion at cursor
… authentication, commands, and message tools
- Added encodeURIComponent() to properly encode user input before appending to URL query string - Prevents special characters (&, ?, #, %) from breaking query parameters - Fixes issue RocketChat#1149
- Changed typing status timeout from 15000ms to 10000ms - Makes typing indicator more responsive and updates faster - Improves real-time chat experience
- Added defensive check to ensure selectedItem exists before accessing properties - Prevents TypeError when user types commands not in the filtered list - Fixes issue RocketChat#1144
…icated - Added default empty string values in destructuring pattern - Fixes all 37 API methods that were sending literal 'undefined' as header values - Headers now send empty strings instead of 'undefined' when user is not logged in - Fixes issue RocketChat#1133
Summary of changes: - Replaced legacy DDP method calls (getUserRoles, rooms:get) with modern REST API endpoints for better server compatibility. - Fixed critical busy-wait loop in handleTypingEvent that caused application freezes. - URL-encoded search and filter parameters in API calls to prevent HTTP Parameter Pollution. - Added a 50,000-character safety guard in sendMessage to prevent crashes from excessively large messages. - Cleaned up unused variables and imports across several components.
… resolve listener leaks
- Prevented crash on unknown slash commands in CommandsList\n- Improved command list visibility logic in useShowCommands\n- Fixed session state leaks by resetting user store on logout in EmbeddedChat\n- Removed hardcoded default emoji preview in EmojiPicker
…Contextual AI Features (Smart Replies, Summary, Translation)
… fix Typing Indicator stability
|
|
There was a problem hiding this comment.
Pull request overview
This PR introduces an AI integration layer (adapter + UI entry points) and adds more deterministic UI/auth/input behaviors, alongside a set of cleanup and reliability changes across the React SDK and API/auth packages.
Changes:
- Add AI adapter contract + mock adapter, wire AI actions into UI (smart replies, translate, summary modal).
- Add auth “state” propagation and update React to reflect auth/connection states more explicitly.
- Improve stability/cleanup patterns (listeners/intervals/media recorder cleanup), plus performance-oriented memoization and filtering updates.
Reviewed changes
Copilot reviewed 47 out of 48 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| test_simple.js | Adds a simple console log script (appears unrelated to PR goals). |
| temp_lerna_index.js | Adds a large process-spawn helper (appears unrelated to PR goals). |
| packages/react/src/views/TypingUsers/TypingUsers.js | Fixes listener cleanup by using a stable handler function. |
| packages/react/src/views/ReportMessage/MessageReportWindow.js | Removes unused theme hook import. |
| packages/react/src/views/MessageList/MessageList.js | Memoizes filtered/reported messages; adjusts day-boundary logic and propTypes. |
| packages/react/src/views/MessageAggregators/common/MessageAggregator.js | Refactors aggregator rendering/memoization; changes export shape; adds propTypes. |
| packages/react/src/views/MessageAggregators/StarredMessages.js | Minor import cleanup (but still imports aggregator as named export). |
| packages/react/src/views/Message/MessageToolbox.js | Adds translate option and refactors permission checks; formatting/style changes. |
| packages/react/src/views/Message/MessageMetrics.js | Removes unused formatDistance import. |
| packages/react/src/views/Message/Message.js | Adds memoized role sets; adds optimistic star/pin; adds translate handler hookup. |
| packages/react/src/views/LoginForm/LoginForm.js | Adds PropTypes and improves password-toggle button semantics. |
| packages/react/src/views/FileMessage/FileMessage.js | Simplifies component signature; removes unused props; keeps internal fetch state setters. |
| packages/react/src/views/EmojiPicker/EmojiPicker.js | Removes defaultEmoji from preview config. |
| packages/react/src/views/EmbeddedChat.js | Wires in Mock AI adapter; adds auth memoization and auto-login toast behavior; adjusts auth listener cleanup. |
| packages/react/src/views/DynamicHeader/DynamicHeader.js | Adds actions slot to header UI. |
| packages/react/src/views/CommandList/CommandsList.js | Adds guards for null command/messageRef; makes list keys more resilient. |
| packages/react/src/views/ChatLayout/ChatLayout.js | Removes unused starredMessages store selector. |
| packages/react/src/views/ChatInput/VideoMessageRecoder.js | Cleans up interval on unmount; toggles recording state in store; theme hook cleanup. |
| packages/react/src/views/ChatInput/SmartReplies.js | New smart-replies UI component reading from AI store. |
| packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js | Improves emoji insertion at cursor + triggers upstream updates after formatting actions. |
| packages/react/src/views/ChatInput/ChatInput.js | Adds input FSM states; smart replies fetching/listener; improves typing timer cleanup and send error handling; adds ARIA attributes. |
| packages/react/src/views/ChatInput/AudioMessageRecorder.js | Fixes typo in store action; adds interval cleanup on unmount. |
| packages/react/src/views/ChatHeader/ChatHeader.js | Improves logout token cleanup; swaps avatar rendering; adds thread “Summarize” action. |
| packages/react/src/views/ChatBody/ChatBody.js | Consolidates auth-change listener management; improves scroll-to-bottom behavior; adds auth-state messaging; stabilizes UiKitModal key; adds summary modal. |
| packages/react/src/views/ChatBody/AiSummaryModal.js | New modal to display and copy AI-generated summaries. |
| packages/react/src/views/ChannelState/ChannelState.js | Adds PropTypes. |
| packages/react/src/views/AttachmentHandler/TextAttachment.js | Removes unused author prop. |
| packages/react/src/store/userStore.js | Adds authState and setter. |
| packages/react/src/store/messageStore.js | Renames toogleRecordingMessage to toggleRecordingMessage. |
| packages/react/src/store/index.js | Re-exports useAiStore. |
| packages/react/src/store/aiStore.js | New Zustand store for AI state (replies/loading/summary modal). |
| packages/react/src/lib/emoji.js | Changes emoji parsing to replace all :shortname: occurrences. |
| packages/react/src/hooks/useShowCommands.js | Avoids showing empty command list when no matches. |
| packages/react/src/hooks/useRCAuth.js | Subscribes to auth state changes; improves login error handling and messaging. |
| packages/react/src/hooks/useMediaRecorder.js | Avoids stopping non-recording recorder; adds unmount cleanup. |
| packages/react/src/hooks/useFetchChatData.js | Stores raw permissions to avoid unnecessary re-apply; minor role mapping refactor. |
| packages/react/lint_report.txt | Adds lint report artifact. |
| packages/auth/src/index.ts | Exports AuthState. |
| packages/auth/src/RocketChatAuth.ts | Adds auth state machine + listeners; updates login flows to set state. |
| packages/api/src/index.ts | Exports AI adapter types and mock adapter. |
| packages/api/src/MockAiAdapter.ts | Adds mock AI adapter implementation. |
| packages/api/src/IAiAdapter.ts | Adds AI adapter interface contract. |
| packages/api/src/EmbeddedChatApi.ts | Adds AI adapter plumbing + AI feature methods; improves typing lock; URL-encodes query params; changes several REST endpoints/return shapes; adds message length guard. |
| UPDATED_PR_DESCRIPTION.md | Adds updated narrative PR description. |
| RFC_CHAT_INPUT_REFACTOR.md | Adds ChatInput refactor proposal doc. |
| PULL_REQUEST_GUIDE.md | Adds guide text for PR context/testing. |
| PR_SUMMARY.md | Adds summary doc for addressed issues/testing. |
| GSOC_2026_PROPOSAL_EmbeddedChat.md | Adds proposal document. |
Comments suppressed due to low confidence (1)
packages/auth/src/RocketChatAuth.ts:60
onAuthChangeisasyncand currently returns nothing. Several React components in this PR treat it like it returns an unsubscribe function, which results in leaked listeners. Consider makingonAuthChangereturn a synchronous unsubscribe (e.g., a function that callsremoveAuthListener) and makeremoveAuthListenernon-async since it’s purely synchronous array mutation.
async onAuthChange(callback: (user: object | null) => void) {
this.authListeners.push(callback);
const user = await this.getCurrentUser();
callback(user);
}
async removeAuthListener(callback: (user: object | null) => void) {
this.authListeners = this.authListeners.filter((cb) => cb !== callback);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <Modal.Close onClick={() => setSummaryModalOpen(false)} /> | ||
| </Modal.Header> | ||
| <Modal.Content style={{ padding: '1.5rem', maxHeight: '60vh', overflowY: 'auto' }}> | ||
| <Markdown body={summaryContent} /> |
There was a problem hiding this comment.
Markdown in this codebase renders only when both body and md token props are provided; passing just a string body will render nothing. As a result, the summary content won’t be displayed. Either render summaryContent as plain text, or convert it to the token format expected by Markup/Markdown and pass it via the md prop.
| <Markdown body={summaryContent} /> | |
| <div>{summaryContent}</div> |
| const setIsLoginIn = useLoginStore((state) => state.setIsLoginIn); | ||
| const dispatchToastMessage = useToastBarDispatch(); | ||
| if (isClosable && !setClosableState) { |
There was a problem hiding this comment.
useToastBarDispatch() is called in EmbeddedChat before a ToastBarProvider exists in the render tree. Since useToastBarDispatch destructures from ToastContext, this will throw at runtime (context is undefined). Move the ToastBarProvider higher (wrapping the component that calls the hook) or relocate the auto-login error toast into a child component rendered under the provider.
| const unsubscribe = RCInstance.auth.onAuthChange((user) => { | ||
| if (user) { | ||
| // Clear old listeners before adding new ones to avoid duplicates | ||
| removeAllListeners(); | ||
| RCInstance.addMessageListener(addMessage); | ||
| RCInstance.addMessageDeleteListener(removeMessage); | ||
| RCInstance.addActionTriggeredListener(onActionTriggerResponse); | ||
| RCInstance.addUiInteractionListener(onActionTriggerResponse); | ||
|
|
||
| getMessagesAndRoles(); | ||
| setHasMoreMessages(true); | ||
| } else { | ||
| getMessagesAndRoles(anonymousMode); | ||
| } | ||
| }); | ||
| }, [RCInstance, anonymousMode, getMessagesAndRoles]); | ||
|
|
||
| useEffect(() => { | ||
| RCInstance.auth.onAuthChange((user) => { | ||
| if (user) { | ||
| fetchAndSetPermissions(); | ||
| } else { | ||
| removeAllListeners(); | ||
| getMessagesAndRoles(anonymousMode); | ||
| permissionsRef.current = null; | ||
| } | ||
| }); | ||
| }, []); | ||
|
|
||
| return () => { | ||
| if (typeof unsubscribe === 'function') unsubscribe(); | ||
| removeAllListeners(); | ||
| }; |
There was a problem hiding this comment.
RCInstance.auth.onAuthChange(...) does not return an unsubscribe function; unsubscribe here won’t be callable so the auth-change listener isn’t removed on unmount. This can lead to duplicated listeners and repeated getMessagesAndRoles() / permission fetches after re-mounts. Use removeAuthListener in cleanup or change onAuthChange to return an unsubscribe.
| import React, { useCallback } from 'react'; | ||
| import { useComponentOverrides } from '@embeddedchat/ui-elements'; | ||
| import { useStarredMessageStore, useUserStore } from '../../store'; | ||
| import { MessageAggregator } from './common/MessageAggregator'; |
There was a problem hiding this comment.
MessageAggregator is now exported as default from ./common/MessageAggregator, but this file still imports it as a named export ({ MessageAggregator }). This will fail to import at runtime; switch to a default import (or restore the named export).
| if (result.success) { | ||
| return result.room; | ||
| } | ||
| return null; |
There was a problem hiding this comment.
getRoomInfo() changed to return result.room (a room object) instead of the previous method-call response shape. At least one consumer expects roomInfo.result[...] (e.g., ChatHeader’s archived-room path), which will now throw. Either preserve the previous return shape or update all callers to the new return type.
| if (result.success) { | |
| return result.room; | |
| } | |
| return null; | |
| return result; |
| @@ -157,6 +194,14 @@ const ChatInput = ({ scrollToBottom, clearUnreadDividerRef }) => { | |||
| .catch(console.error); | |||
| } | |||
| }); | |||
|
|
|||
| return () => { | |||
| if (typeof unsubscribe === 'function') unsubscribe(); | |||
| if (timerRef.current) clearTimeout(timerRef.current); | |||
There was a problem hiding this comment.
Same issue as elsewhere: RCInstance.auth.onAuthChange(...) does not return an unsubscribe function, so the cleanup here won’t remove the auth listener. Use RCInstance.auth.removeAuthListener (or change onAuthChange to return an unsubscribe) to avoid listener leaks and duplicated fetches.
| const MessageAggregator = ({ | ||
| title, |
There was a problem hiding this comment.
MessageAggregator was changed from a named export to a default export, but multiple existing views still import it as { MessageAggregator } (e.g., StarredMessages, PinnedMessages, MentionedMessages, etc.). This will cause runtime/import errors. Either keep the named export for backward compatibility or update all import sites to use the default export.
| @@ -8,16 +8,16 @@ import { | |||
| useComponentOverrides, | |||
| appendClassNames, | |||
| useTheme, | |||
| } from '@embeddedchat/ui-elements'; | |||
| import RCContext from '../../context/RCInstance'; | |||
| import { EmojiPicker } from '../EmojiPicker'; | |||
| import { getMessageToolboxStyles } from './Message.styles'; | |||
| import SurfaceMenu from '../SurfaceMenu/SurfaceMenu'; | |||
| import { Markdown } from '../Markdown'; | |||
| import Attachment from '../AttachmentHandler/Attachment'; | |||
| } from "@embeddedchat/ui-elements"; | |||
| import RCContext from "../../context/RCInstance"; | |||
| import { EmojiPicker } from "../EmojiPicker"; | |||
| import { getMessageToolboxStyles } from "./Message.styles"; | |||
| import SurfaceMenu from "../SurfaceMenu/SurfaceMenu"; | |||
| import { Markdown } from "../Markdown"; | |||
| import Attachment from "../AttachmentHandler/Attachment"; | |||
There was a problem hiding this comment.
This file was switched to double quotes, but the repo’s Prettier config for packages/react enforces singleQuote: true and ESLint treats Prettier violations as errors. Reformat this file to match the project’s Prettier settings (or run the formatter) to avoid lint/build failures.
| translate: { | ||
| label: "Translate", | ||
| id: "translate", | ||
| onClick: () => handleTranslateMessage(message), | ||
| iconName: "language", | ||
| visible: RCInstance.getAiAdapter()?.enabled, | ||
| }, | ||
| }), |
There was a problem hiding this comment.
options is memoized but reads RCInstance.getAiAdapter()?.enabled without including RCInstance (or a stable aiEnabled value) in the dependency list. If the adapter is set/changed at runtime, the Translate option visibility can become stale. Include a stable dependency or derive aiEnabled outside the memo.
| @@ -0,0 +1,61 @@ | |||
| import React from 'react'; | |||
| import { Box, Button, useTheme, lighten, darken } from '@embeddedchat/ui-elements'; | |||
There was a problem hiding this comment.
Button is imported but never used, which triggers lint warnings in this package. Remove the unused import (or switch the reply chips to actual Buttons if that was the intent).
| import { Box, Button, useTheme, lighten, darken } from '@embeddedchat/ui-elements'; | |
| import { Box, useTheme, lighten, darken } from '@embeddedchat/ui-elements'; |
Pull Request: AI Adapter Layer & Architecture Improvements
Overview
This PR introduces a major architectural update focused on stability and AI integration, aligned with the GSoC 2026 goals.
It adds a pluggable AI adapter system and restructures core authentication and message input handling using Finite State Machines (FSM) to improve reliability.
What’s Included
Pluggable AI Interface
A new IAiAdapter interface allows integration with any AI service without modifying core logic.
Centralized AI State Management
Added a dynamic AI store to manage replies, summaries, and loading states.
AI Features
Smart Replies: Suggested responses displayed above the input field.
Message Translation: Translation option available in the message action menu.
Conversation Summary Modal: A dedicated AiSummaryModal for generating and viewing summaries of long threads.
Authentication State Machine
Auth transitions are now centrally managed in RocketChatAuth.ts. This prevents inconsistent states and resolves infinite loading issues.
Message Input State Machine
Introduced clear states (DRAFTING, SENDING, ERROR) to prevent race conditions and double message submissions.
Media Resource Cleanup
Updated useMediaRecorder.js to ensure camera and microphone streams are always properly released.
Improved ARIA labels and states for the main chat input.
Ensures better compatibility with screen readers and improves WCAG compliance.
How to Test
AI Features
Enable MockAiAdapter in EmbeddedChat.js.
Open a thread and click “Summarize Thread” in the header.
Use “Translate” from the message toolbox.
Observe Smart Replies appearing when new messages arrive.
State Machines
Trigger a login failure to confirm transition to the ERROR state and automatic reset to UNAUTHENTICATED.
Send a delayed message and observe the SENDING state on the action button.
Accessibility
Use Chrome Vox or VoiceOver to navigate the chat input and verify ARIA labels are correctly announced.