From c7a89f999782d46cf1156037e7ea67aee5a1ef87 Mon Sep 17 00:00:00 2001
From: z2_cc <170238968@qq.com>
Date: Wed, 13 May 2026 11:40:21 +0800
Subject: [PATCH] feat: add ChatMessage component with source links
---
src/components/ChatMessage/index.jsx | 31 +++++++
src/components/ChatMessage/styles.module.css | 96 ++++++++++++++++++++
2 files changed, 127 insertions(+)
create mode 100644 src/components/ChatMessage/index.jsx
create mode 100644 src/components/ChatMessage/styles.module.css
diff --git a/src/components/ChatMessage/index.jsx b/src/components/ChatMessage/index.jsx
new file mode 100644
index 0000000..07ce066
--- /dev/null
+++ b/src/components/ChatMessage/index.jsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import styles from './styles.module.css';
+
+export default function ChatMessage({ message, isStreaming }) {
+ const isUser = message.role === 'user';
+
+ return (
+
+
+ {message.content}
+ {!isUser && isStreaming && }
+
+ {!isUser && message.sources && message.sources.length > 0 && (
+
+ )}
+
+ );
+}
diff --git a/src/components/ChatMessage/styles.module.css b/src/components/ChatMessage/styles.module.css
new file mode 100644
index 0000000..725a07c
--- /dev/null
+++ b/src/components/ChatMessage/styles.module.css
@@ -0,0 +1,96 @@
+.message {
+ margin-bottom: 12px;
+ display: flex;
+ flex-direction: column;
+}
+
+.messageUser {
+ align-items: flex-end;
+}
+
+.messageAssistant {
+ align-items: flex-start;
+}
+
+.bubble {
+ max-width: 85%;
+ padding: 10px 14px;
+ border-radius: 12px;
+ font-size: 14px;
+ line-height: 1.6;
+ word-break: break-word;
+ white-space: pre-wrap;
+}
+
+.bubbleUser {
+ background: #e8e8e8;
+ color: #333;
+ border-bottom-right-radius: 4px;
+}
+
+.bubbleAssistant {
+ background: #f0f4ff;
+ color: #333;
+ border-bottom-left-radius: 4px;
+}
+
+[data-theme='dark'] .bubbleUser {
+ background: #3a3a3a;
+ color: #e0e0e0;
+}
+
+[data-theme='dark'] .bubbleAssistant {
+ background: #2a2e4a;
+ color: #e0e0e0;
+}
+
+.sources {
+ margin-top: 8px;
+ padding: 8px 10px;
+ background: rgba(255, 255, 255, 0.6);
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ max-width: 85%;
+ font-size: 12px;
+}
+
+[data-theme='dark'] .sources {
+ background: rgba(255, 255, 255, 0.08);
+ border-color: #444;
+}
+
+.sourcesTitle {
+ color: #666;
+ margin-bottom: 4px;
+ font-weight: 500;
+}
+
+[data-theme='dark'] .sourcesTitle {
+ color: #aaa;
+}
+
+.sourceLink {
+ display: block;
+ color: #466aff;
+ text-decoration: none;
+ padding: 2px 0;
+}
+
+.sourceLink:hover {
+ text-decoration: underline;
+}
+
+.cursor {
+ display: inline-block;
+ width: 2px;
+ height: 16px;
+ background: #466aff;
+ margin-left: 2px;
+ vertical-align: text-bottom;
+ animation: blink 1s infinite;
+}
+
+@keyframes blink {
+ 0%, 50% { opacity: 1; }
+ 51%, 100% { opacity: 0; }
+}