Skip to content

Comments

feat: support ai meeting readonly mode on web#247

Open
LucasXu0 wants to merge 8 commits intomainfrom
feat/ai_meeting
Open

feat: support ai meeting readonly mode on web#247
LucasXu0 wants to merge 8 commits intomainfrom
feat/ai_meeting

Conversation

@LucasXu0
Copy link
Contributor

@LucasXu0 LucasXu0 commented Feb 4, 2026

Description

Screenshot 2026-02-04 at 20 31 16 Screenshot 2026-02-04 at 20 31 23 Screenshot 2026-02-04 at 20 31 11

Checklist

General

  • I've included relevant documentation or comments for the changes introduced.
  • I've tested the changes in multiple environments (e.g., different browsers, operating systems).

Testing

  • I've added or updated tests to validate the changes introduced for AppFlowy Web.

Feature-Specific

  • For feature additions, I've added a preview (video, screenshot, or demo) in the "Feature Preview" section.
  • I've verified that this feature integrates seamlessly with existing functionality.

Summary by Sourcery

Add full web rendering support for AI meeting blocks, including tabbed views, speaker sections, and inline references, while enforcing read-only behavior for AI-generated meeting content.

New Features:

  • Render AI meeting blocks on web with editable titles, localized labels, and tabbed navigation for summary, notes, and transcript sections.
  • Introduce AI meeting speaker blocks with avatar, name resolution, and timestamp display within transcripts.
  • Support inline AI meeting references that show source excerpts in a rich tooltip and scroll to the referenced transcript or notes content.

Enhancements:

  • Treat AI meeting sub-blocks as container blocks in the editor model and hide hover/selection controls inside the AI meeting container for a cleaner UX.
  • Improve selection toolbar and editor read-only handling so that formatting controls are suppressed when the selection is inside read-only AI meeting content.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 4, 2026

Reviewer's Guide

Implements full web support for AI meeting blocks, including tabbed summary/notes/transcript sections, speaker sub-blocks, inline references, and enforces read-only behavior for AI-generated meeting content, along with associated editor integration and UI tweaks.

Sequence diagram for clicking an inline AI meeting reference

sequenceDiagram
  actor User
  participant InlineReferenceComponent
  participant SlateEditor
  participant YjsEditor
  participant DOM

  User->>InlineReferenceComponent: click referenceBadge
  InlineReferenceComponent->>InlineReferenceComponent: determine meetingNode and sourceType
  InlineReferenceComponent->>YjsEditor: setBlockData(meetingBlockId, selected_tab_index)
  Note over InlineReferenceComponent,YjsEditor: Switch to notes or transcript tab if needed

  InlineReferenceComponent->>YjsEditor: findSlateEntryByBlockId(targetBlockId)
  YjsEditor-->>InlineReferenceComponent: slateEntry
  InlineReferenceComponent->>SlateEditor: ReactEditor.toDOMNode(targetNode)
  SlateEditor-->>InlineReferenceComponent: domNode

  InlineReferenceComponent->>DOM: smoothScrollIntoViewIfNeeded(domNode)
  DOM-->>InlineReferenceComponent: scroll complete
  InlineReferenceComponent->>DOM: add highlight-block class
  Note over DOM: Highlight referenced content for a few seconds
Loading

Sequence diagram for read-only enforcement on AI meeting content

sequenceDiagram
  participant SlateEditor
  participant WithElementPlugin
  participant SelectionToolbarHook
  participant AIMeetingBlockComponent
  participant Notify
  participant User

  SlateEditor->>WithElementPlugin: shouldTreatPathReadOnly(path)
  WithElementPlugin->>WithElementPlugin: Editor.above(... aiMeetingReadOnlyTypes)
  WithElementPlugin-->>SlateEditor: true when inside summary/notes/transcription

  SlateEditor->>AIMeetingBlockComponent: render(node)
  AIMeetingBlockComponent->>SlateEditor: editor.isElementReadOnly(element)
  SlateEditor-->>AIMeetingBlockComponent: readOnly flag
  AIMeetingBlockComponent-->>SlateEditor: render tabs/title with inputs disabled when readOnly

  SlateEditor->>SelectionToolbarHook: selectionchange event
  SelectionToolbarHook->>SelectionToolbarHook: isSelectionInReadOnly using Editor.above
  SelectionToolbarHook-->>SelectionToolbarHook: visible false when in read-only AI meeting

  User->>AIMeetingBlockComponent: click inside readOnly section
  AIMeetingBlockComponent->>Notify: notify.info(readOnlyHint)
  Notify-->>User: show read-only hint toast
Loading

Class diagram for AI meeting block types and inline reference data

classDiagram
  direction LR

  class BlockData {
  }

  class AIMeetingBlockData {
    +string title
    +string date
    +string audio_file_path
    +string recording_state
    +string summary_template
    +string summary_detail
    +string summary_language
    +string transcript_id
    +string transcription_type
    +string created_at
    +string last_modified
    +number selected_tab_index
    +number pending_billing_duration
    +boolean show_notes_directly
    +boolean auto_start_recording
    +string speaker_info_map
  }

  class AIMeetingSpeakerBlockData {
    +string speaker_id
    +number timestamp
    +number end_timestamp
  }

  class BlockNode {
    +string id
    +string blockId
    +BlockType type
    +BlockData data
  }

  class AIMeetingNode {
    +AIMeetingBlockData data
  }

  class AIMeetingSummaryNode {
    +BlockType type~AIMeetingSummaryBlock~
  }

  class AIMeetingNotesNode {
    +BlockType type~AIMeetingNotesBlock~
  }

  class AIMeetingTranscriptionNode {
    +BlockType type~AIMeetingTranscriptionBlock~
  }

  class AIMeetingSpeakerNode {
    +AIMeetingSpeakerBlockData data
    +BlockType type~AIMeetingSpeakerBlock~
  }

  class InlineReferenceData {
    +string[] blockIds
    +number number
  }

  class AIMeetingBlockComponent {
    +string titleState
    +number activeIndexState
    +boolean readOnly
    +handleTabChange(index number)
    +commitTitle()
  }

  class AIMeetingSectionComponent {
    +renderSummary()
    +renderNotes()
    +renderTranscription()
  }

  class AIMeetingSpeakerBlockComponent {
    +resolveSpeakerInfo()
    +formatTimestamp(value number)
  }

  class InlineReferenceComponent {
    +InlineReferenceData reference
    +buildStatuses(meetingNode Element)
    +scrollToTarget(blockId string)
  }

  BlockData <|-- AIMeetingBlockData
  BlockData <|-- AIMeetingSpeakerBlockData

  BlockNode <|-- AIMeetingNode
  BlockNode <|-- AIMeetingSummaryNode
  BlockNode <|-- AIMeetingNotesNode
  BlockNode <|-- AIMeetingTranscriptionNode
  BlockNode <|-- AIMeetingSpeakerNode

  AIMeetingNode --> AIMeetingBlockData
  AIMeetingSpeakerNode --> AIMeetingSpeakerBlockData

  AIMeetingBlockComponent --> AIMeetingNode
  AIMeetingBlockComponent --> AIMeetingSectionComponent

  AIMeetingSectionComponent --> AIMeetingSummaryNode
  AIMeetingSectionComponent --> AIMeetingNotesNode
  AIMeetingSectionComponent --> AIMeetingTranscriptionNode

  AIMeetingSpeakerBlockComponent --> AIMeetingSpeakerNode

  InlineReferenceComponent --> InlineReferenceData
  InlineReferenceComponent --> AIMeetingNode
  InlineReferenceComponent --> AIMeetingSpeakerNode
Loading

Flow diagram for selection toolbar visibility with AI meeting read-only logic

flowchart TD
  A[Selection change event] --> B{Editor has focus?}
  B -- No --> Z[Hide selection toolbar]
  B -- Yes --> C{Selection collapsed?}
  C -- Yes --> Z
  C -- No --> D{assistantTypeRef defined?}
  D -- Yes --> Z
  D -- No --> E{isSelectionInReadOnly?}
  E -- Yes --> Z
  E -- No --> F{selectedText nonempty and isExpanded and not isDragging}
  F -- No --> Z
  F -- Yes --> Y[Show selection toolbar]
Loading

File-Level Changes

Change Details Files
Implement rich AIMeeting block UI with editable title, tabbed sections, and read-only child content handling.
  • Replace placeholder AIMeetingBlock with a fully featured component that uses Slate/Yjs editor context and i18n for labels and default title.
  • Add logic to detect available summary/notes/transcript child blocks, compute selected tab with fallbacks, and toggle visibility via data attributes and CSS.
  • Make the meeting title editable with local state, commit on blur/Enter to block data, and respect editor read-only state.
  • Add tab bar UI with SVG icons and translated labels, persisting selected_tab_index in block data.
  • Handle clicks on read-only inner blocks by showing a non-intrusive read-only hint notification, while allowing reference popovers to remain interactive.
  • Add ai-meeting.scss styles to control visibility of sections per active tab and adjust spacing for nested blocks and speaker entries.
src/components/editor/components/blocks/ai-meeting/AIMeetingBlock.tsx
src/components/editor/components/blocks/ai-meeting/ai-meeting.scss
Introduce AI meeting sub-block types (summary, notes, transcript, speaker) and integrate them into the Slate schema, rendering, and behavior.
  • Extend BlockType enum with AIMeetingSummaryBlock, AIMeetingNotesBlock, AIMeetingTranscriptionBlock, and AIMeetingSpeakerBlock.
  • Expand AIMeetingBlockData fields for metadata like timestamps, transcript info, UI flags, and tab selection, plus define AIMeetingSpeakerBlockData.
  • Add node interfaces for AIMeetingSummaryNode, AIMeetingNotesNode, AIMeetingTranscriptionNode, and AIMeetingSpeakerNode in editor types.
  • Register AIMeetingSection and AIMeetingSpeakerBlock in the generic Element renderer so the editor can render these new block types.
  • Mark AI meeting sub-blocks as container block types and non-text for Yjs conversion/traversal and embed handling.
  • Update hover block controls to close when hovering inside an AI meeting block but not on the root, preventing controls on inner sections.
src/application/types.ts
src/components/editor/editor.type.ts
src/components/editor/components/element/Element.tsx
src/application/slate-yjs/command/const.ts
src/application/slate-yjs/utils/convert.ts
src/components/editor/components/toolbar/block-controls/HoverControls.hooks.ts
src/components/editor/components/blocks/ai-meeting/AIMeetingSection.tsx
src/components/editor/components/blocks/ai-meeting/AIMeetingSpeakerBlock.tsx
src/components/editor/components/blocks/ai-meeting/index.ts
Enforce read-only behavior for AI meeting content at the editor level and hide selection toolbar when selection is within read-only regions.
  • In withElement plugin, treat AIMeeting summary/notes/transcript blocks as read-only by short-circuiting isElementReadOnly when selection is inside them.
  • Extend SelectionToolbar visibility hook to detect when selection is within AI meeting read-only block types or any element marked read-only, and suppress the toolbar in that case.
  • Add AI_MEETING_READONLY_TYPES constant and supporting Slate Editor.above lookups for detecting these regions.
src/components/editor/plugins/withElement.ts
src/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks.ts
Add inline reference rendering for AI meeting summaries, linking numbered references to notes/transcript blocks with popovers and navigation.
  • Extend leaf attributes typing to include a structured or JSON-encoded reference field for inline references.
  • Adjust Leaf component to avoid code styling on reference spans and to wrap reference-marked text in a new InlineReference component alongside mentions/formulas.
  • Implement InlineReference component that parses reference data, resolves referenced blocks within the same AI meeting (notes/transcript), builds status metadata, and renders a clickable numbered badge with tooltip and rich popover content.
  • Support navigation from a reference to its source block, including switching the active tab on the meeting block, smooth scrolling, and temporary highlight of the target block.
  • Handle deleted references by showing an error state with a warning icon and localized messages.
  • Add utilities to parse and normalize inline reference data, timestamps, and to compute available meeting tabs.
src/slate-editor.d.ts
src/components/editor/components/leaf/Leaf.tsx
src/components/editor/components/leaf/reference/InlineReference.tsx
src/components/editor/components/leaf/reference/utils.ts
src/assets/icons/ai_summary_ref_notes.svg
src/assets/icons/ai_summary_ref_transcript.svg
src/assets/icons/ai_reference_warning.svg
Introduce speaker row UI within transcripts that shows speaker info, avatar, and timestamp, sourced from meeting metadata.
  • Implement AIMeetingSpeakerBlock that renders a speaker header with avatar, resolved name, and formatted timestamp, with transcript text as children.
  • Parse and normalize speaker_info_map from the parent AIMeeting node data (stringified JSON or object) and resolve per-speaker metadata including fallback names and unknown speaker handling via i18n.
  • Format timestamps into mm:ss or hh:mm:ss as appropriate and derive avatar fallback initials from speaker name or ID.
  • Style speaker rows within AI meeting blocks, ensuring proper spacing, alignment, and zero left indentation for nested content.
src/components/editor/components/blocks/ai-meeting/AIMeetingSpeakerBlock.tsx
src/components/editor/components/blocks/ai-meeting/ai-meeting.scss
Miscellaneous editor integration and assets for AI meeting feature.
  • Ensure traversal logic treats AI meeting-related blocks as non-text containers by clearing textId in traverseBlock for these block types.
  • Prevent code styling from applying to inline elements that also have reference metadata to avoid visual conflicts.
  • Wire in new AI meeting icons for tabs and references (summary, notes, transcript, warning).
  • Reserve translation keys in English and Chinese locale files for AI meeting labels, tabs, tooltips, and error messages (content not shown in diff).
src/application/slate-yjs/utils/convert.ts
src/components/editor/components/leaf/Leaf.tsx
src/assets/icons/ai_meeting_transcript_tab.svg
src/assets/icons/ai_notes.svg
src/assets/icons/ai_summary_tab.svg
src/assets/icons/ai_reference_warning.svg
src/assets/icons/ai_summary_ref_notes.svg
src/assets/icons/ai_summary_ref_transcript.svg
src/@types/translations/en.json
src/@types/translations/zh-CN.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

🥷 Ninja i18n – 🛎️ Translations need to be updated

Project /project.inlang

lint rule new reports level link
Missing translation 459 warning contribute (via Fink 🐦)

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In InlineReference, editor.isElementReadOnly(text as unknown as Element) is being called on a Text node and could throw if isElementReadOnly assumes an Element; consider deriving read-only from the surrounding element (e.g., via Editor.above) or just useReadOnly() instead of casting.
  • The formatTimestamp helper is duplicated in both AIMeetingSpeakerBlock and InlineReference; extracting a shared utility would reduce repetition and keep timestamp formatting consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `InlineReference`, `editor.isElementReadOnly(text as unknown as Element)` is being called on a `Text` node and could throw if `isElementReadOnly` assumes an `Element`; consider deriving read-only from the surrounding element (e.g., via `Editor.above`) or just `useReadOnly()` instead of casting.
- The `formatTimestamp` helper is duplicated in both `AIMeetingSpeakerBlock` and `InlineReference`; extracting a shared utility would reduce repetition and keep timestamp formatting consistent.

## Individual Comments

### Comment 1
<location> `src/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks.ts:14-18` </location>
<code_context>
 import { Decorate, useEditorContext } from '@/components/editor/EditorContext';
 import { createHotkey, HOT_KEY_NAME } from '@/utils/hotkeys';

+const AI_MEETING_READONLY_TYPES = new Set<BlockType>([
+  BlockType.AIMeetingSummaryBlock,
+  BlockType.AIMeetingNotesBlock,
+  BlockType.AIMeetingTranscriptionBlock,
+  BlockType.AIMeetingSpeakerBlock,
+]);
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Speaker blocks are treated as read-only for the toolbar but not for `isElementReadOnly`, which may lead to inconsistent behavior.

`AI_MEETING_READONLY_TYPES` includes `AIMeetingSpeakerBlock`, so the selection toolbar is hidden inside speaker blocks. But in `withElement.ts`, `aiMeetingReadOnlyTypes` only covers summary/notes/transcription, so `editor.isElementReadOnly` may still return `false` for speaker blocks. This can leave speaker content editable (including via shortcuts) but without a toolbar. Please either:

- Add `AIMeetingSpeakerBlock` to `aiMeetingReadOnlyTypes` in `withElement.ts`, or
- Remove it from `AI_MEETING_READONLY_TYPES` if speaker content should remain editable.

Keeping these checks consistent will avoid this mismatch in behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant