Skip to content

✨ feat: implemented ui for references (#332)#339

Open
maifeeulasad wants to merge 5 commits intoant-design:mainfrom
maifeeulasad:main
Open

✨ feat: implemented ui for references (#332)#339
maifeeulasad wants to merge 5 commits intoant-design:mainfrom
maifeeulasad:main

Conversation

@maifeeulasad
Copy link

@maifeeulasad maifeeulasad commented Nov 23, 2024

💻 变更类型 | Change Type

  • ✨ feat
  • 🐛 fix
  • 💄 style
  • 🔨 chore
  • 📝 docs

🔀 变更说明 | Description of Change

closes #332

📝 补充信息 | Additional Information

Here is a snap:
references for antd pro-chat

Open for further modification and discussion.

Summary by CodeRabbit

新功能

  • 聊天消息中新增参考资料显示功能,可展示相关资源的标题、描述和外部链接
  • 参考资料以卡片式网格布局呈现,支持水平滚动查看多个参考资源
  • 用户可直接点击参考链接打开外部资源

✏️ Tip: You can customize this high-level summary in your review settings.

@maifeeulasad
Copy link
Author

Tested with:

const references = [
      { url: 'https://www.a.com', description: 'lots of meawww', title: 'a' },
      { url: 'https://www.b.com', title: 'bb' },
      { url: 'http://woof.com', description: 'ghew ghew', title: 'dog' },
      { url: 'https://www.a.com', description: 'lots of meawww', title: 'bc' },
      { url: 'https://www.b.com', title: 'bb' },
      { url: 'http://woof.com', description: 'ghew ghew', title: 'dog' },
      { url: 'https://www.a.com', description: 'lots of meawww', title: 'bd' },
      { url: 'https://www.b.com', title: 'bb' },
      { url: 'http://woof.com', description: 'ghew ghew', title: 'dog' },
      { url: 'https://www.a.com', description: 'lots of meawww', title: 'bb' },
      { url: 'https://www.b.com', title: 'bb' },
      { url: 'http://woof.com', description: 'ghew ghew', title: 'dog' },
      { url: 'https://www.a.com', description: 'lots of meawww', title: 'bb' },
      { url: 'https://www.b.com', title: 'bb' },
      { url: 'http://woof.com', description: 'ghew ghew', title: 'dog' },
      { url: 'https://www.a.com', description: 'lots of meawww', title: 'bb' },
      { url: 'https://www.b.com', title: 'bb' },
      { url: 'http://woof.com', description: 'ghew ghew', title: 'dog' },
      { url: 'https://www.a.com', description: 'lots of meawww', title: 'bb' },
      { url: 'https://www.b.com', title: 'bb' },
      { url: 'http://woof.com', description: 'ghew ghew', title: 'dog' },
      { url: 'https://www.a.com', description: 'lots of meawww', title: 'bb' },
      { url: 'https://www.b.com', title: 'bb' },
      { url: 'http://woof.com', description: 'ghew ghew', title: 'dog' },
      { url: 'https://www.a.com', description: 'lots of meawww', title: 'bb' },
      { url: 'https://www.b.com', title: 'bb' },
      { url: 'http://woof.com', description: 'ghew ghew', title: 'dog' },
      { url: 'https://www.a.com', description: 'lots of meawww', title: 'bb' },
      { url: 'https://www.b.com', title: 'bb' },
      { url: 'http://woof.com', description: 'ghew ghew', title: 'dog' },
];

Copilot AI review requested due to automatic review settings January 7, 2026 11:27
@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

该PR为聊天消息添加了参考资源渲染功能。通过类型定义、样式定义和组件更新,引入了一个新的 ChatReference 类型,并在消息内容中以可点击的卡片形式展示参考链接。

Changes

类型分组 / 文件 变更摘要
类型定义
src/types/message.ts, src/ChatItem/type.ts
新增 ChatReference 接口(包含 titleurl 和可选的 description);扩展 ChatMessageChatItemProps 以支持可选的 references 数组属性
样式增强
src/ChatItem/style.ts
新增 messageReferences 样式,配置水平滚动布局,包含边距、间距和 flex 属性
组件逻辑与渲染
src/ChatItem/components/MessageContent.tsx
添加 renderReferences 函数以生成参考资源卡片网格;引入 CardListTooltip 等 antd 组件;实现通过 window.open 打开外链的行为;将渲染结果注入到消息内容树中
组件连接与数据流
src/ChatItem/index.tsx, src/ChatList/index.tsx
ChatItemProps 中添加 references 属性;通过 prop drilling 从 ChatListChatItemMessageContent 传递参考数据

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰✨ 参考之源闪闪亮,
卡片链接排成行,
窗口打开知识藏,
信息流动更通畅,
聊天添翼向远方!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰准确地描述了主要变更:实现了引用/参考资源的UI功能,与代码变更完全相符。
Linked Issues check ✅ Passed 代码实现与#332需求一致:添加了references字段到ChatMessage、ChatItem、MessageContent中;提供了UI展示(Card、List、ExternalLink组件);实现了点击打开链接的交互。
Out of Scope Changes check ✅ Passed 所有变更都与引用/参考资源UI功能相关,未发现超出范围的改动。类型定义、组件实现、样式设置都围绕该功能展开。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a UI feature to display references/sources for chat messages, addressing issue #332. The implementation adds a new optional references field to chat messages, which displays as interactive cards below message content.

Key changes:

  • Adds ChatReference interface with title, url, and optional description fields
  • Threads the references prop through the component hierarchy (ChatList → ChatItem → MessageContent)
  • Implements a card-based grid layout with clickable icons to open reference URLs in new tabs

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/types/message.ts Defines the ChatReference interface and adds optional references field to ChatMessage
src/ChatItem/type.ts Adds references prop to ChatItemProps interface
src/ChatList/index.tsx Extracts and passes references from message items to ChatItem
src/ChatItem/index.tsx Forwards references prop to MessageContent component
src/ChatItem/style.ts Adds CSS styling for the references container with flex layout and horizontal scrolling
src/ChatItem/components/MessageContent.tsx Implements the references UI with Card grid, ExternalLink icon component, and rendering logic

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return (
<>
<div>
<strong>References:</strong>
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The text "References:" is hardcoded in English, which breaks internationalization support for this component. The codebase appears to support multiple languages (Chinese comments exist throughout). Consider extracting this string to a localization system or using a translation key to support multiple languages.

Copilot uses AI. Check for mistakes.
</div>
<div className={`${cx(styles.messageReferences, `${prefixClass}-message-references`)}`}>
<List
grid={{ gutter: 16, column: 4 }}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The fixed 4-column grid layout will not work well on mobile or smaller screens, potentially causing layout issues or very narrow cards. Consider using responsive grid configurations, such as { gutter: 16, xs: 1, sm: 2, md: 3, lg: 4 } to adjust the number of columns based on screen size for better mobile experience.

Suggested change
grid={{ gutter: 16, column: 4 }}
grid={{ gutter: 16, xs: 1, sm: 2, md: 3, lg: 4 }}

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +34
interface ExternalLinkProps {
url: string;
}

const ExternalLink: React.FC<ExternalLinkProps> = ({ url }) => {
const handleClick = () => {
window.open(url, '_blank', 'noopener noreferrer');
};

return (
<Tooltip title={`Open in new tab: ${url}`}>
<InfoCircleOutlined
style={{
cursor: 'pointer',
transition: 'color 0.3s ease',
}}
onClick={handleClick}
/>
</Tooltip>
);
};
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The ExternalLink component is defined inside the MessageContent file but outside the main component. While this prevents re-creation on each render, it would be better organized in a separate file (e.g., components/ExternalLink.tsx) for better code maintainability and reusability, following the component structure pattern used in this codebase.

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +92
renderItem={({ title, description, url }) => (
<List.Item>
<Card hoverable title={title} extra={<ExternalLink url={url} />}>
{description}
</Card>
</List.Item>
)}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The Card component will render an empty body when description is undefined or empty, which may look odd. Consider adding a condition to handle this case, such as only rendering the Card body content when description exists, or providing a fallback message like "No description available".

Suggested change
renderItem={({ title, description, url }) => (
<List.Item>
<Card hoverable title={title} extra={<ExternalLink url={url} />}>
{description}
</Card>
</List.Item>
)}
renderItem={({ title, description, url }) => {
const hasDescription =
typeof description === 'string' && description.trim().length > 0;
return (
<List.Item>
<Card hoverable title={title} extra={<ExternalLink url={url} />}>
{hasDescription ? description : 'No description available'}
</Card>
</List.Item>
);
}}

Copilot uses AI. Check for mistakes.
);
};

const referencesContent = useMemo(() => renderReferences(), [references]);
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The useMemo hook is missing cx, styles, and prefixClass in its dependency array. The renderReferences function uses these values but they're not listed as dependencies, which could lead to stale closures and incorrect rendering. Add these dependencies to ensure the memoized value updates correctly when these values change.

Suggested change
const referencesContent = useMemo(() => renderReferences(), [references]);
const referencesContent = useMemo(() => renderReferences(), [references, cx, styles, prefixClass]);

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +31
<InfoCircleOutlined
style={{
cursor: 'pointer',
transition: 'color 0.3s ease',
}}
onClick={handleClick}
/>
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The ExternalLink icon is only clickable via mouse and lacks keyboard accessibility. Users navigating with keyboard cannot open these links. Add proper keyboard event handling (onKeyDown/onKeyPress for Enter/Space keys) and set tabIndex={0} to make the icon keyboard accessible and meet WCAG guidelines.

Copilot uses AI. Check for mistakes.
messageReferences: css`
margin-top: 8px;
display: flex;
overflow-x: scroll;
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The overflow-x: scroll property always shows a horizontal scrollbar even when content doesn't overflow, which creates unnecessary visual clutter. Use overflow-x: auto instead to only show the scrollbar when the content actually overflows the container width.

Suggested change
overflow-x: scroll;
overflow-x: auto;

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 (2)
src/ChatItem/index.tsx (1)

77-116: 在 useMemo 依赖数组中缺少 references

messageContentDomuseMemo 钩子在 JSX 中使用了 references (第100行),但依赖数组(第104-116行)中缺少该依赖项。这会导致当 references 更新时,记忆化的 DOM 不会重新计算,用户界面将显示过时的引用数据。

🔧 建议的修复
   }, [
     error,
     message,
     messageExtra,
     renderMessage,
     placement,
     primary,
     text,
     type,
     editing,
     errorMessage,
     originData,
+    references,
   ]);
src/ChatList/index.tsx (1)

98-98: 移除调试用的 console.log 语句

代码中遗留了调试用的 console.log 语句,应在合并到生产环境之前移除。

🐛 建议的修复
-          console.log('renderItems', renderItems);
-
           const historyLength = data.length;
🧹 Nitpick comments (4)
src/ChatList/index.tsx (1)

106-123: 可以简化 references 的传递

第106行的中间变量 references 是冗余的,可以直接在第123行传递 item.references,使代码更简洁。

♻️ 建议的重构
-          const references = item.references;
-
           return (
             <ShouldUpdateItem
               key={item.id}
               {...itemProps}
               {...item}
               shouldUpdate={itemShouldUpdate}
             >
               <Fragment>
                 <HistoryDivider enable={enableHistoryDivider} text={text?.history} />
                 <ChatItem
                   {...itemProps}
                   {...item}
                   originData={item}
                   chatItemRenderConfig={chatItemRenderConfig}
                   markdownProps={markdownProps}
-                  references={references}
+                  references={item.references}
                 />
src/ChatItem/style.ts (1)

176-182: 建议使用 overflow-x: auto 替代 overflow-x: scroll

当前使用 overflow-x: scroll 会在某些浏览器上始终显示滚动条,即使内容未溢出。使用 overflow-x: auto 可以只在需要时显示滚动条,提供更好的用户体验。

♻️ 建议的改进
       messageReferences: css`
         margin-top: 8px;
         display: flex;
-        overflow-x: scroll;
+        overflow-x: auto;
         width: 100%;
         gap: 8px;
       `,
src/ChatItem/components/MessageContent.tsx (2)

99-99: 可选的代码优化建议

当前的 useMemo 模式是有效的,但可以进一步优化:renderReferences 函数在每次渲染时都会重新创建,然后在 useMemo 中调用。

♻️ 可选的优化方案

方案 1:直接内联 JSX 到 useMemo

-const renderReferences = () => {
-  if (!references || references.length === 0) return null;
-  return (
-    <>
-      <div>
-        <strong>References:</strong>
-      </div>
-      <div className={`${cx(styles.messageReferences, `${prefixClass}-message-references`)}`}>
-        <List
-          grid={{ gutter: 16, column: 4 }}
-          dataSource={references}
-          renderItem={({ title, description, url }) => (
-            <List.Item>
-              <Card hoverable title={title} extra={<ExternalLink url={url} />}>
-                {description}
-              </Card>
-            </List.Item>
-          )}
-        />
-      </div>
-    </>
-  );
-};
-
-const referencesContent = useMemo(() => renderReferences(), [references]);
+const referencesContent = useMemo(() => {
+  if (!references || references.length === 0) return null;
+  return (
+    <>
+      <div>
+        <strong>References:</strong>
+      </div>
+      <div className={`${cx(styles.messageReferences, `${prefixClass}-message-references`)}`}>
+        <List
+          grid={{ gutter: 16, column: 4 }}
+          dataSource={references}
+          renderItem={({ title, description, url }) => (
+            <List.Item>
+              <Card hoverable title={title} extra={<ExternalLink url={url} />}>
+                {description}
+              </Card>
+            </List.Item>
+          )}
+        />
+      </div>
+    </>
+  );
+}, [references, cx, styles.messageReferences, prefixClass]);

注意:需要将 cxstyles.messageReferencesprefixClass 添加到依赖数组中。


128-128: 引用内容渲染位置合理,注意样式配置

referencesContent 在组件树中的位置正确,放置在 messageExtra 之后。渲染逻辑通过 useMemo 的条件判断自动处理空值情况。

不过需要注意:从 style.ts 中可以看到 messageReferences 样式使用了 overflow-x: scroll,这在某些浏览器中会始终显示滚动条。建议在样式文件中改用 overflow-x: auto

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73be565 and 1a696a2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • src/ChatItem/components/MessageContent.tsx
  • src/ChatItem/index.tsx
  • src/ChatItem/style.ts
  • src/ChatItem/type.ts
  • src/ChatList/index.tsx
  • src/types/message.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/ChatItem/type.ts (1)
src/types/message.ts (1)
  • ChatReference (13-17)
src/ChatItem/components/MessageContent.tsx (2)
src/ChatItem/type.ts (1)
  • ChatItemProps (16-131)
src/ChatItem/style.ts (1)
  • useStyles (3-198)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🔇 Additional comments (6)
src/types/message.ts (2)

13-17: LGTM!

ChatReference 接口定义清晰合理,必填字段 titleurl 符合引用的基本需求,可选的 description 字段提供了灵活性。


38-38: LGTM!

ChatMessage 添加可选的 references 字段是向后兼容的改动,不会影响现有代码。

src/ChatItem/type.ts (1)

6-6: LGTM!

正确导入了 ChatReference 类型,并将可选的 references 属性添加到 ChatItemProps 接口中。类型定义清晰且与其他文件保持一致。

Also applies to: 129-130

src/ChatItem/components/MessageContent.tsx (3)

2-2: 导入语句合理

新增的导入(useMemo、antd 组件和图标)都已正确使用,符合功能需求。

Also applies to: 7-7, 12-12


50-50: Props 类型定义正确

references 属性正确引用了 ChatItemProps['references'] 类型,保持了类型一致性。


68-68: Props 解构正确

references 属性正确地从组件 props 中解构,并在后续逻辑中使用。

Comment on lines +14 to +34
interface ExternalLinkProps {
url: string;
}

const ExternalLink: React.FC<ExternalLinkProps> = ({ url }) => {
const handleClick = () => {
window.open(url, '_blank', 'noopener noreferrer');
};

return (
<Tooltip title={`Open in new tab: ${url}`}>
<InfoCircleOutlined
style={{
cursor: 'pointer',
transition: 'color 0.3s ease',
}}
onClick={handleClick}
/>
</Tooltip>
);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

添加无障碍属性并优化 Tooltip 文本

当前实现存在以下问题:

  1. InfoCircleOutlined 缺少 aria-label 属性,屏幕阅读器无法识别其用途
  2. Tooltip 中显示完整 URL 可能过长,影响用户体验
  3. 定义了 color 过渡效果但未实际改变颜色
♿ 建议的改进方案
 const ExternalLink: React.FC<ExternalLinkProps> = ({ url }) => {
   const handleClick = () => {
     window.open(url, '_blank', 'noopener noreferrer');
   };
 
   return (
-    <Tooltip title={`Open in new tab: ${url}`}>
+    <Tooltip title="在新标签页中打开">
       <InfoCircleOutlined
+        aria-label="打开外部链接"
         style={{
           cursor: 'pointer',
           transition: 'color 0.3s ease',
         }}
         onClick={handleClick}
+        onMouseEnter={(e) => (e.currentTarget.style.color = '#1890ff')}
+        onMouseLeave={(e) => (e.currentTarget.style.color = '')}
       />
     </Tooltip>
   );
 };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +75 to +97
const renderReferences = () => {
if (!references || references.length === 0) return null;
return (
<>
<div>
<strong>References:</strong>
</div>
<div className={`${cx(styles.messageReferences, `${prefixClass}-message-references`)}`}>
<List
grid={{ gutter: 16, column: 4 }}
dataSource={references}
renderItem={({ title, description, url }) => (
<List.Item>
<Card hoverable title={title} extra={<ExternalLink url={url} />}>
{description}
</Card>
</List.Item>
)}
/>
</div>
</>
);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

修复响应式布局问题

当前实现存在以下问题:

  1. 响应式设计缺失(严重)List 组件的 grid 配置固定为 4 列,在移动设备和平板上会导致布局问题
  2. 空内容处理:当 description 为空时,Card 主体区域会显示为空白
  3. 语义化标签<strong>References:</strong> 应使用语义化的标题标签
📱 建议的响应式改进方案
 const renderReferences = () => {
   if (!references || references.length === 0) return null;
   return (
     <>
       <div>
-        <strong>References:</strong>
+        <h4 style={{ margin: 0, fontSize: '14px', fontWeight: 600 }}>References:</h4>
       </div>
       <div className={`${cx(styles.messageReferences, `${prefixClass}-message-references`)}`}>
         <List
-          grid={{ gutter: 16, column: 4 }}
+          grid={{
+            gutter: 16,
+            xs: 1,
+            sm: 2,
+            md: 3,
+            lg: 4,
+            xl: 4,
+            xxl: 4,
+          }}
           dataSource={references}
           renderItem={({ title, description, url }) => (
             <List.Item>
               <Card hoverable title={title} extra={<ExternalLink url={url} />}>
-                {description}
+                {description || <span style={{ color: '#00000040' }}>暂无描述</span>}
               </Card>
             </List.Item>
           )}
         />
       </div>
     </>
   );
 };
📝 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
const renderReferences = () => {
if (!references || references.length === 0) return null;
return (
<>
<div>
<strong>References:</strong>
</div>
<div className={`${cx(styles.messageReferences, `${prefixClass}-message-references`)}`}>
<List
grid={{ gutter: 16, column: 4 }}
dataSource={references}
renderItem={({ title, description, url }) => (
<List.Item>
<Card hoverable title={title} extra={<ExternalLink url={url} />}>
{description}
</Card>
</List.Item>
)}
/>
</div>
</>
);
};
const renderReferences = () => {
if (!references || references.length === 0) return null;
return (
<>
<div>
<h4 style={{ margin: 0, fontSize: '14px', fontWeight: 600 }}>References:</h4>
</div>
<div className={`${cx(styles.messageReferences, `${prefixClass}-message-references`)}`}>
<List
grid={{
gutter: 16,
xs: 1,
sm: 2,
md: 3,
lg: 4,
xl: 4,
xxl: 4,
}}
dataSource={references}
renderItem={({ title, description, url }) => (
<List.Item>
<Card hoverable title={title} extra={<ExternalLink url={url} />}>
{description || <span style={{ color: '#00000040' }}>暂无描述</span>}
</Card>
</List.Item>
)}
/>
</div>
</>
);
};

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.

👑 [需求] renders sources and related content as per message item

2 participants