feat(chat): composer toolbar + attach menu and voice clip - #2147
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 49 minutes and 24 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAdds attachment dropdown (image/video/file), hidden video input, and MediaRecorder-based voice recording to the chat composer; pushes created file drafts, adjusts composer footer layout and controls, and adds i18n keys across five locales. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant ChatBar as Chat Bar
participant MediaAPI as Browser MediaRecorder API
participant FileBuilder as Audio File Builder
participant Drafts as Draft Attachment Manager
User->>ChatBar: Click "record" (mic)
ChatBar->>MediaAPI: request microphone access
MediaAPI-->>ChatBar: permission granted / stream
ChatBar->>MediaAPI: create MediaRecorder (choose supported MIME)
MediaAPI-->>ChatBar: recording started
Note over MediaAPI: collect audio chunks
User->>ChatBar: Click "stop"
ChatBar->>MediaAPI: stop recorder
MediaAPI-->>ChatBar: chunks available
ChatBar->>FileBuilder: convert chunks -> File (.m4a/.webm)
FileBuilder-->>ChatBar: audio File
ChatBar->>Drafts: pushDrafts([File], 'file')
Drafts-->>ChatBar: draft added
ChatBar->>User: update UI (clear recording state)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
0e74a31 to
06e7a41
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx (1)
237-250:⚠️ Potential issue | 🟠 MajorAppend the voice clip against the latest draft list.
mr.onstopcloses overpushDrafts, andpushDraftsclones thedraftAttachmentsprop from the render that started recording. If the user adds/removes attachments while recording, stopping will rebuild from that stale snapshot and can overwrite those newer draft changes.🛠️ Suggested fix
export function HumanChatPanelChatBar({ value, onChange, onSend, @@ const mediaRecorderRef = useRef<MediaRecorder | null>(null); const mediaStreamRef = useRef<MediaStream | null>(null); + const draftAttachmentsRef = useRef(draftAttachments); const [isVoiceRecording, setIsVoiceRecording] = useState(false); const [voiceError, setVoiceError] = useState<string | null>(null); @@ + useEffect(() => { + draftAttachmentsRef.current = draftAttachments; + }, [draftAttachments]); + const pushDrafts = useCallback( (files: FileList | File[], kind: 'file' | 'image') => { if (!onDraftAttachmentsChange) return; const arr = Array.from(files); - const next: ChatDraftAttachment[] = [...draftAttachments]; + const next: ChatDraftAttachment[] = [...draftAttachmentsRef.current]; for (const file of arr) { if (kind === 'image' && !file.type.startsWith('image/')) { continue; }Also applies to: 512-540, 626-643
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx` around lines 237 - 250, mr.onstop currently closes over the render-time draftAttachments and pushDrafts, so when recording stops it rebuilds attachments from a stale snapshot and can overwrite user changes; update the logic in the media recorder stop handler (mr.onstop) and in pushDrafts to read/merge against a fresh source (e.g., keep a draftAttachmentsRef that you update in a useEffect when prop draftAttachments changes, or use the state setter functional form like setDraftAttachments(prev => [...prev, newVoiceClip]) ) and append the created voice Blob (from recordedChunksRef) to that latest list instead of cloning the prop captured at start of recording to avoid losing concurrent edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx`:
- Around line 1103-1111: Update the shipped composer footer copy to remove the
stale mention hint by editing the HumanChatPanel.newlineHint localization
entries: find the HumanChatPanel.newlineHint key in all locale/resource files
and replace the string that references "@" or mentions instructions with text
that reflects the current behavior (e.g., remove any "use @ to mention" clause
and keep only the newline/submit hint). Ensure every locale translation is
updated consistently (or marked for translation) so the UI copy matches the
disabled AtSign button in human-chat-panel-chat-bar.tsx.
- Around line 248-250: recordedChunksRef being a single shared ref causes chunk
mixing between rapid stop/start cycles; change the recorder logic so each
MediaRecorder instance gets its own chunks array (e.g., create a new local const
recordedChunks = [] inside the startRecording function and capture it in the
mediaRecorder.ondataavailable/onstop handlers or attach it as a property on the
MediaRecorder instance) instead of pushing into the module-level
recordedChunksRef; ensure mediaRecorder.stop() handlers use that per-recorder
array to build the Blob and that you clear/remove that per-recorder storage on
finish to avoid memory leaks (update references in the start/stop handlers and
anywhere recordedChunksRef is used, e.g., in the component methods that create
and stop the recorder).
---
Outside diff comments:
In `@packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx`:
- Around line 237-250: mr.onstop currently closes over the render-time
draftAttachments and pushDrafts, so when recording stops it rebuilds attachments
from a stale snapshot and can overwrite user changes; update the logic in the
media recorder stop handler (mr.onstop) and in pushDrafts to read/merge against
a fresh source (e.g., keep a draftAttachmentsRef that you update in a useEffect
when prop draftAttachments changes, or use the state setter functional form like
setDraftAttachments(prev => [...prev, newVoiceClip]) ) and append the created
voice Blob (from recordedChunksRef) to that latest list instead of cloning the
prop captured at start of recording to avoid losing concurrent edits.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2bb3f45a-aed4-4c14-b67e-a9dd3bad9e6c
📒 Files selected for processing (6)
packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsxpackages/i18n/src/messages/de.jsonpackages/i18n/src/messages/en.jsonpackages/i18n/src/messages/es.jsonpackages/i18n/src/messages/fr.jsonpackages/i18n/src/messages/pt.json
Replace paperclip/image/bold row with + attach submenu (image, video, file), emoji, disabled @, and mic recording that adds audio via the same draft pipeline as file uploads. Keeps hidden file inputs and attachment UI. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
06e7a41 to
73ab825
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx (1)
65-77: 🛠️ Refactor suggestion | 🟠 MajorTighten the draft-attachment contract for the new media controls.
The new
+and mic actions are always interactive, but they silently no-op whenonDraftAttachmentsChangeis missing. Since this prop is still optional, the component API now allows rendering shipped controls that can never attach anything.Suggested fix
type HumanChatPanelChatBarProps = { value: string; onChange: (value: string) => void; onSend: () => void; placeholder?: string; channelName?: string; replyPreview?: ReplyPreview; editPreview?: EditPreview; draftAttachments?: ChatDraftAttachment[]; - onDraftAttachmentsChange?: (next: ChatDraftAttachment[]) => void; + onDraftAttachmentsChange: (next: ChatDraftAttachment[]) => void; };Also applies to: 522-549, 599-600, 1055-1142
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx` around lines 65 - 77, The prop contract allows rendering interactive media controls that silently no-op if onDraftAttachmentsChange is missing; update HumanChatPanelChatBarProps usage so the '+' and mic actions never render or are disabled when onDraftAttachmentsChange is undefined (or make onDraftAttachmentsChange required): locate the HumanChatPanelChatBar component and its handlers for adding attachments and recording (the click/press handlers tied to the '+' and mic UI), and change them to either hide/disable those controls when onDraftAttachmentsChange is not provided or change the prop type to require onDraftAttachmentsChange whenever draftAttachments is present, ensuring all code paths that update draftAttachments call onDraftAttachmentsChange only when it exists.
♻️ Duplicate comments (2)
packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx (2)
248-248:⚠️ Potential issue | 🟠 MajorKeep recording chunks scoped to one
MediaRecorderinstance.
recordedChunksRefis shared across sessions. Aftermr.stop(),dataavailable/onstopfinish asynchronously, so a quick stop→start can clear or reuse the same array before the first recorder flushes. That can drop the first clip or mix bytes from two recordings into one file.Suggested fix
- const recordedChunksRef = useRef<BlobPart[]>([]); const [isVoiceRecording, setIsVoiceRecording] = useState(false); const [voiceError, setVoiceError] = useState<string | null>(null); ... - recordedChunksRef.current = []; + const recordedChunks: BlobPart[] = []; const preferredTypes = [ 'audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', ]; ... mediaRecorderRef.current = mr; mr.ondataavailable = (ev) => { if (ev.data && ev.data.size > 0) { - recordedChunksRef.current.push(ev.data); + recordedChunks.push(ev.data); } }; mr.onstop = () => { for (const track of stream.getTracks()) { track.stop(); } mediaStreamRef.current = null; mediaRecorderRef.current = null; - const chunks = recordedChunksRef.current; - recordedChunksRef.current = []; - const blob = new Blob(chunks, { type: mr.mimeType || 'audio/webm' }); + const blob = new Blob(recordedChunks, { + type: mr.mimeType || 'audio/webm', + });What do the MediaRecorder spec/MDN docs say about `dataavailable` and `stop` event ordering after `MediaRecorder.stop()`, and is it safe to reuse one shared chunk array across rapid stop→start recording sessions?Also applies to: 616-644
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx` at line 248, recordedChunksRef is shared across recorder sessions which can cause chunk loss or mixing when stop() triggers asynchronous dataavailable/onstop events; instead create a chunks array scoped to each MediaRecorder instance (e.g., allocate a new local array inside the start/initialize function and capture it in the MediaRecorder event handlers, or associate a chunks array with the recorder via a WeakMap or a property on the recorder) so that handlers for a previous mr always push into its own array and you only use/clear the shared ref after that recorder has fully finished; update places referencing recordedChunksRef (the MediaRecorder creation/start logic and the dataavailable/onstop handlers) to use the per-recorder array.
1113-1121:⚠️ Potential issue | 🟡 MinorUpdate the footer hint now that mentions are disabled.
The
@button is now a read-only placeholder, but Line 1163 still rendersHumanChatPanel.newlineHint, and the locale strings still say@ to mention(for example, Line 1620 inpackages/i18n/src/messages/en.jsonandpackages/i18n/src/messages/pt.json). Please remove that instruction across locales so the shipped copy matches the actual behavior.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx` around lines 1113 - 1121, The footer hint still instructs users to use "@" to mention even though the AtSign button is now a disabled placeholder; update the UI and locales so the copy matches behavior by removing or changing the mention instruction. In human-chat-panel-chat-bar.tsx, remove or update the usage of HumanChatPanel.newlineHint (or the code path that renders the mention hint) so it no longer references mentioning via "@", and then update the corresponding locale entries in the i18n message files (e.g., the keys in packages/i18n/src/messages/en.json and packages/i18n/src/messages/pt.json that currently say "@ to mention") to a neutral newline/send hint that does not instruct using "@". Ensure you modify the exact symbols HumanChatPanel.newlineHint and the locale keys so UI text and translations no longer promise mention functionality.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx`:
- Around line 65-77: The prop contract allows rendering interactive media
controls that silently no-op if onDraftAttachmentsChange is missing; update
HumanChatPanelChatBarProps usage so the '+' and mic actions never render or are
disabled when onDraftAttachmentsChange is undefined (or make
onDraftAttachmentsChange required): locate the HumanChatPanelChatBar component
and its handlers for adding attachments and recording (the click/press handlers
tied to the '+' and mic UI), and change them to either hide/disable those
controls when onDraftAttachmentsChange is not provided or change the prop type
to require onDraftAttachmentsChange whenever draftAttachments is present,
ensuring all code paths that update draftAttachments call
onDraftAttachmentsChange only when it exists.
---
Duplicate comments:
In `@packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsx`:
- Line 248: recordedChunksRef is shared across recorder sessions which can cause
chunk loss or mixing when stop() triggers asynchronous dataavailable/onstop
events; instead create a chunks array scoped to each MediaRecorder instance
(e.g., allocate a new local array inside the start/initialize function and
capture it in the MediaRecorder event handlers, or associate a chunks array with
the recorder via a WeakMap or a property on the recorder) so that handlers for a
previous mr always push into its own array and you only use/clear the shared ref
after that recorder has fully finished; update places referencing
recordedChunksRef (the MediaRecorder creation/start logic and the
dataavailable/onstop handlers) to use the per-recorder array.
- Around line 1113-1121: The footer hint still instructs users to use "@" to
mention even though the AtSign button is now a disabled placeholder; update the
UI and locales so the copy matches behavior by removing or changing the mention
instruction. In human-chat-panel-chat-bar.tsx, remove or update the usage of
HumanChatPanel.newlineHint (or the code path that renders the mention hint) so
it no longer references mentioning via "@", and then update the corresponding
locale entries in the i18n message files (e.g., the keys in
packages/i18n/src/messages/en.json and packages/i18n/src/messages/pt.json that
currently say "@ to mention") to a neutral newline/send hint that does not
instruct using "@". Ensure you modify the exact symbols
HumanChatPanel.newlineHint and the locale keys so UI text and translations no
longer promise mention functionality.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ea39cbbb-25b3-4f78-ae0e-56a3f272355e
📒 Files selected for processing (6)
packages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsxpackages/i18n/src/messages/de.jsonpackages/i18n/src/messages/en.jsonpackages/i18n/src/messages/es.jsonpackages/i18n/src/messages/fr.jsonpackages/i18n/src/messages/pt.json
Mentions are not available in the composer yet; align footer copy with the disabled @ control across locales. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Use a local chunks array captured by ondataavailable/onstop instead of a shared ref so rapid stop/start cannot mix blobs (CodeRabbit). Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Summary
Updates the Human chat composer bottom icon row to match the requested layout:
+opens a dropdown with Photo or image, Video, and File — each still uses the same hidden<input type="file">flows andpushDrafts, so multi-select and draft preview / send behavior are unchanged.HumanChatPanelEmojiPickerbehavior.@— disabled button (read-only placeholder until mentions exist).pushDrafts(..., 'file')path as other documents (no Matrix protocol change).The Bold shortcut button was removed from the footer (formatting remains on the text selection floating toolbar).
i18n
New
HumanChatPanelkeys in en, de, es, fr, pt for attach menu labels, mention placeholder, voice actions, and errors.Testing
pnpm run format:fixpnpm --filter @hypha-platform/epics check-typesNote on PR 2139
That PR focuses on message hover / overflow / edit flows; it does not add a composer microphone. Voice capture here follows a minimal browser MediaRecorder → File → existing draft pattern aligned with “voice as attachment” without changing send/upload code paths.
Summary by CodeRabbit
New Features
Localization