Skip to content

Conversation

ONLY-yours
Copy link
Collaborator

@ONLY-yours ONLY-yours commented Jul 18, 2025

💻 变更类型 | Change Type

Ref: #8442

  • 支持任意 ChatItem 右键快速唤起操作内容
  • 选中片段支持快速引用到输入区域
  • 细节样式调整
  • ✨ feat
  • 🐛 fix
  • ♻️ refactor
  • 💄 style
  • 👷 build
  • ⚡️ perf
  • 📝 docs
  • 🔨 chore

🔀 变更说明 | Description of Change

📝 补充信息 | Additional Information

Copy link

vercel bot commented Jul 18, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
lobe-chat-database ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 23, 2025 0:31am
lobe-chat-preview ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 23, 2025 0:31am

Copy link
Contributor

sourcery-ai bot commented Jul 18, 2025

Reviewer's Guide

Implements a right-click context menu in ChatItem by adding a custom hook and ContextMenu component, wiring menu actions to chat store functions, enhancing the ActionsBar for inline quoting, and cleaning up outdated desktop routes.

Sequence diagram for context menu action handling in ChatItem

sequenceDiagram
    actor User
    participant ChatItem
    participant useChatItemContextMenu
    participant ContextMenu
    participant ChatStore
    User->>ChatItem: Right-click on message
    ChatItem->>useChatItemContextMenu: handleContextMenu(event)
    useChatItemContextMenu->>ContextMenu: Show menu at position with selectedText
    User->>ContextMenu: Clicks menu item (e.g., Quote)
    ContextMenu->>useChatItemContextMenu: handleMenuClick(action)
    useChatItemContextMenu->>ChatItem: onActionClick(action)
    ChatItem->>ChatStore: Dispatches action (e.g., updateInputMessage, copyMessage, etc.)
Loading

Class diagram for new and updated ChatItem context menu components and hooks

classDiagram
    class ChatItem {
      +handleContextMenuAction(action)
      +renderMessage(editableContent)
    }
    class ContextMenu {
      +onMenuClick(action)
      +visible
      +position
      +selectedText
    }
    class useChatItemContextMenu {
      +containerRef
      +contextMenuState
      +handleContextMenu(event)
      +handleMenuClick(action)
      +hideContextMenu()
    }
    ChatItem --|> useChatItemContextMenu : uses
    ChatItem --|> ContextMenu : renders
    ContextMenu --|> useChatItemContextMenu : uses state/handlers
Loading

Class diagram for ActionsBar enhancements (inline quoting)

classDiagram
    class ActionsBar {
      +handleActionClick(action)
    }
    ActionsBar --|> ChatStore : uses updateInputMessage
Loading

File-Level Changes

Change Details Files
Introduce custom context menu for ChatItem
  • Add useChatItemContextMenu hook to manage context menu state and handlers
  • Create ContextMenu component rendering antd Menu via portal
  • Integrate onContextMenu/handleContextMenu and render ContextMenu in ChatItem
src/features/Conversation/components/ChatItem/index.tsx
src/features/Conversation/components/ChatItem/ContextMenu.tsx
src/features/Conversation/hooks/useChatItemContextMenu.tsx
Wire up menu actions to chat store operations
  • Expose updateInputMessage, deleteMessage, regenerateMessage, copyMessage, openThreadCreator, delAndRegenerateMessage, translateMessage, ttsMessage selectors in ChatItem
  • Implement handleContextMenuAction mapping action keys to store methods
src/features/Conversation/components/ChatItem/index.tsx
Enhance ActionsBar for quoting selected text
  • Add updateInputMessage store selector in ActionsBar
  • Extend handleActionClick to support 'quote' action and show success message
src/features/Conversation/components/ChatItem/ActionsBar.tsx
Remove obsolete desktop routes and pages
  • Delete desktop trpc route
  • Delete desktop devtools page
  • Delete desktop layout file
src/app/(backend)/trpc/desktop/[trpc]/route.ts
src/app/desktop/devtools/page.tsx
src/app/desktop/layout.tsx

Possibly linked issues

  • #0: The PR adds a context menu to chat items, enabling users to quote selected text, directly fulfilling the issue's request for quoting historical chat records.

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

@lobehubbot
Copy link
Member

👍 @ONLY-yours

Thank you for raising your pull request and contributing to our Community
Please make sure you have followed our contributing guidelines. We will review it as soon as possible.
If you encounter any problems, please feel free to connect with us.
非常感谢您提出拉取请求并为我们的社区做出贡献,请确保您已经遵循了我们的贡献指南,我们会尽快审查它。
如果您遇到任何问题,请随时与我们联系。

Copy link
Contributor

gru-agent bot commented Jul 18, 2025

TestGru Assignment

Summary

Link CommitId Status Reason
Detail 16bcb90 🚫 Skipped No files need to be tested {"src/app/(backend)/trpc/desktop/[trpc]/route.ts":"File path does not match include patterns.","src/app/desktop/devtools/page.tsx":"File path does not match include patterns.","src/app/desktop/layout.tsx":"File path does not match include patterns.","src/features/Conversation/components/ChatItem/ActionsBar.tsx":"File path does not match include patterns.","src/features/Conversation/components/ChatItem/ContextMenu.tsx":"File path does not match include patterns.","src/features/Conversation/components/ChatItem/index.tsx":"File path does not match include patterns.","src/features/Conversation/hooks/useChatItemContextMenu.tsx":"File path does not match include patterns."}

History Assignment

Tip

You can @gru-agent and leave your feedback. TestGru will make adjustments based on your input

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jul 18, 2025
Copy link
Contributor

@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 @ONLY-yours - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `src/features/Conversation/components/ChatItem/ContextMenu.tsx:37` </location>
<code_context>
+  const { t } = useTranslation('common');
+  const { regenerate, edit, copy, del, branching, delAndRegenerate, export: exportAction, share } = useChatListActionsBar();
+
+  const renderIcon = useCallback((IconComponent: any) => {
+    console.log("IconComponent", IconComponent)
+    return <ActionIcon icon={<IconComponent size={16} />} size={'small'} />
+    return <IconComponent size={16} />
+    if (!IconComponent) return null;
</code_context>

<issue_to_address>
The renderIcon function contains unreachable code after the first return statement.

Since the function returns immediately, the remaining code is never executed. Please remove or refactor this code as needed.
</issue_to_address>

### Comment 2
<location> `src/features/Conversation/components/ChatItem/index.tsx:120` </location>
<code_context>
+    const handleContextMenuAction = useCallback(async (action: any) => {
</code_context>

<issue_to_address>
The handleContextMenuAction function uses a broad 'any' type for the action parameter.

Defining a specific type for the action parameter will improve type safety and maintainability.
</issue_to_address>

### Comment 3
<location> `src/features/Conversation/components/ChatItem/ActionsBar.tsx:72` </location>
<code_context>

   const handleActionClick = useCallback(
-    async (action: ActionIconGroupEvent) => {
+    async (action: ActionIconGroupEvent & { selectedText?: string }) => {
       switch (action.key) {
         case 'edit': {
</code_context>

<issue_to_address>
The dependency array for handleActionClick omits some dependencies used in the function.

'toggleMessageEditing', 'id', 'virtuosoRef', and 'index' should be added to the dependency array to prevent stale closures.
</issue_to_address>

### Comment 4
<location> `src/features/Conversation/hooks/useChatItemContextMenu.tsx:23` </location>
<code_context>
+
+  const containerRef = useRef<HTMLDivElement>(null);
+
+  const handleContextMenu = useCallback((event: React.MouseEvent) => {
+    event.preventDefault();
+    event.stopPropagation();
+
+    // Get selected text
+    const selection = window.getSelection();
+    const selectedText = selection?.toString().trim() || '';
+
+    setContextMenuState({
+      position: { x: event.clientX, y: event.clientY },
+      selectedText,
</code_context>

<issue_to_address>
Selected text is always taken from window.getSelection(), which may not be scoped to the chat item.

Scoping the selection to the chat item's container will prevent the context menu from using unrelated selections.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
  const handleContextMenu = useCallback((event: React.MouseEvent) => {
    event.preventDefault();
    event.stopPropagation();

    // Get selected text
    const selection = window.getSelection();
    const selectedText = selection?.toString().trim() || '';

    setContextMenuState({
      position: { x: event.clientX, y: event.clientY },
      selectedText,
      visible: true,
    });
  }, []);
=======
  const handleContextMenu = useCallback((event: React.MouseEvent) => {
    event.preventDefault();
    event.stopPropagation();

    // Get selected text scoped to the container
    let selectedText = '';
    const selection = window.getSelection();
    if (selection && selection.rangeCount > 0) {
      const range = selection.getRangeAt(0);
      const container = containerRef.current;
      if (container && range && container.contains(range.commonAncestorContainer)) {
        selectedText = selection.toString().trim();
      }
    }

    setContextMenuState({
      position: { x: event.clientX, y: event.clientY },
      selectedText,
      visible: true,
    });
  }, []);
>>>>>>> REPLACE

</suggested_fix>

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.

Comment on lines 37 to 39
const renderIcon = useCallback((IconComponent: any) => {
console.log("IconComponent", IconComponent)
return <ActionIcon icon={<IconComponent size={16} />} size={'small'} />
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: The renderIcon function contains unreachable code after the first return statement.

Since the function returns immediately, the remaining code is never executed. Please remove or refactor this code as needed.

Comment on lines 120 to 129
const handleContextMenuAction = useCallback(async (action: any) => {
switch (action.key) {
case 'quote': {
if (action.selectedText) {
const currentInput = useChatStore.getState().inputMessage;
const quotedText = `> ${action.selectedText.replaceAll('\n', '\n> ')}\n\n`;
updateInputMessage(currentInput + quotedText);
}
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: The handleContextMenuAction function uses a broad 'any' type for the action parameter.

Defining a specific type for the action parameter will improve type safety and maintainability.

Comment on lines 148 to +151
translateMessage(id, lang);
}
},
[item],
[item, updateInputMessage, t, message],
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): The dependency array for handleActionClick omits some dependencies used in the function.

'toggleMessageEditing', 'id', 'virtuosoRef', and 'index' should be added to the dependency array to prevent stale closures.

Comment on lines 23 to 36
const handleContextMenu = useCallback((event: React.MouseEvent) => {
event.preventDefault();
event.stopPropagation();

// Get selected text
const selection = window.getSelection();
const selectedText = selection?.toString().trim() || '';

setContextMenuState({
position: { x: event.clientX, y: event.clientY },
selectedText,
visible: true,
});
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Selected text is always taken from window.getSelection(), which may not be scoped to the chat item.

Scoping the selection to the chat item's container will prevent the context menu from using unrelated selections.

Suggested change
const handleContextMenu = useCallback((event: React.MouseEvent) => {
event.preventDefault();
event.stopPropagation();
// Get selected text
const selection = window.getSelection();
const selectedText = selection?.toString().trim() || '';
setContextMenuState({
position: { x: event.clientX, y: event.clientY },
selectedText,
visible: true,
});
}, []);
const handleContextMenu = useCallback((event: React.MouseEvent) => {
event.preventDefault();
event.stopPropagation();
// Get selected text scoped to the container
let selectedText = '';
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const container = containerRef.current;
if (container && range && container.contains(range.commonAncestorContainer)) {
selectedText = selection.toString().trim();
}
}
setContextMenuState({
position: { x: event.clientX, y: event.clientY },
selectedText,
visible: true,
});
}, []);

@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jul 23, 2025
@ONLY-yours ONLY-yours changed the title 「WIP」: feat: 支持在 ChatItem 侧右键快速唤起 ContextMenu & 支持上下文片段引用 feat: 支持在 ChatItem 侧右键快速唤起 ContextMenu & 支持上下文片段引用 Jul 23, 2025
Copy link

codecov bot commented Jul 23, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.55%. Comparing base (7dd65f0) to head (b1a130d).
⚠️ Report is 452 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8488      +/-   ##
==========================================
+ Coverage   85.31%   85.55%   +0.23%     
==========================================
  Files         908      912       +4     
  Lines       68519    69378     +859     
  Branches     6514     6547      +33     
==========================================
+ Hits        58455    59354     +899     
+ Misses      10064    10024      -40     
Flag Coverage Δ
app 85.55% <ø> (+0.23%) ⬆️
server 96.26% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@arvinxx arvinxx left a comment

Choose a reason for hiding this comment

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

引用和右键 contextMenu 分成两个 pr 吧

Copy link
Member

Choose a reason for hiding this comment

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

这里revert

Copy link
Member

Choose a reason for hiding this comment

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

revert

Copy link
Member

Choose a reason for hiding this comment

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

revert

@ONLY-yours ONLY-yours changed the base branch from main-archived to main September 2, 2025 03:05
@ONLY-yours
Copy link
Collaborator Author

#9034 @arvinxx 分开做,移动到这边

@ONLY-yours ONLY-yours closed this Sep 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🌠 Feature Request New feature or request | 特性与建议 size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants