Skip to content

Commit bacf8d2

Browse files
author
User
committed
前端代码工程化
1 parent fd2ff30 commit bacf8d2

22 files changed

Lines changed: 4154 additions & 1817 deletions

frontend/src/App.js

Lines changed: 159 additions & 1817 deletions
Large diffs are not rendered by default.

frontend/src/App.js.backup

Lines changed: 1968 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
import React from 'react';
2+
import { motion, AnimatePresence } from 'framer-motion';
3+
import { Heart, User, Bot, Loader2, Paperclip, Send, Link, ExternalLink, X, MessageSquarePlus } from 'lucide-react';
4+
import {
5+
ChatContainer as ChatContainerStyled,
6+
Header,
7+
Title,
8+
Subtitle,
9+
MessagesContainer,
10+
MessageBubble,
11+
Avatar,
12+
MessageWrapper,
13+
MessageContent,
14+
FeedbackButtons,
15+
FeedbackButton,
16+
EmotionTag,
17+
MessageTimestamp,
18+
Suggestions,
19+
SuggestionChip,
20+
WelcomeMessage,
21+
LoadingIndicator,
22+
InputContainer,
23+
InputRow,
24+
MessageInput,
25+
AttachmentButton,
26+
SendButton,
27+
FileInput,
28+
AttachmentsPreview,
29+
AttachmentItem,
30+
AttachmentIcon,
31+
RemoveAttachmentButton,
32+
URLPreview,
33+
URLText,
34+
URLButton
35+
} from '../styles';
36+
import { emotionLabels } from '../constants/emotions';
37+
import { formatTimestamp, formatFileSize } from '../utils/formatters';
38+
import TypewriterComponent from './TypewriterText';
39+
import { getFileIcon } from '../utils/fileUtils';
40+
41+
const ChatContainer = ({
42+
messages,
43+
isLoading,
44+
suggestions,
45+
inputValue,
46+
attachments,
47+
detectedURLs,
48+
messagesEndRef,
49+
inputRef,
50+
attachmentButtonRef,
51+
sendButtonRef,
52+
fileInputRef,
53+
onInputChange,
54+
onKeyPress,
55+
onTabNavigation,
56+
onSendMessage,
57+
onFileUpload,
58+
onRemoveAttachment,
59+
onSuggestionClick,
60+
onOpenFeedbackModal
61+
}) => {
62+
return (
63+
<ChatContainerStyled
64+
initial={{ opacity: 0, x: 20 }}
65+
animate={{ opacity: 1, x: 0 }}
66+
transition={{ duration: 0.5 }}
67+
>
68+
<Header>
69+
<Title>
70+
<Heart size={24} />
71+
情感聊天机器人
72+
</Title>
73+
<Subtitle>温暖陪伴,理解倾听</Subtitle>
74+
</Header>
75+
76+
<MessagesContainer>
77+
<AnimatePresence initial={false}>
78+
{messages.length === 0 ? (
79+
<WelcomeMessage
80+
key="welcome"
81+
initial={{ opacity: 0 }}
82+
animate={{ opacity: 1 }}
83+
exit={{ opacity: 0 }}
84+
transition={{ delay: 0.5 }}
85+
>
86+
<h3>👋 你好!我是你的情感支持伙伴</h3>
87+
<p>
88+
我在这里倾听你的心声,理解你的感受。<br/>
89+
无论是开心、难过、焦虑还是困惑,我都愿意陪伴你。<br/>
90+
请随意分享你的想法和感受吧!
91+
</p>
92+
</WelcomeMessage>
93+
) : (
94+
messages.map((message) => (
95+
<MessageBubble
96+
key={message.id}
97+
isUser={message.role === 'user'}
98+
initial={{ opacity: 0, y: 20 }}
99+
animate={{ opacity: 1, y: 0 }}
100+
exit={{ opacity: 0, y: -20 }}
101+
transition={{ duration: 0.3 }}
102+
>
103+
<Avatar isUser={message.role === 'user'}>
104+
{message.role === 'user' ? <User size={20} /> : <Bot size={20} />}
105+
</Avatar>
106+
<MessageWrapper>
107+
<MessageContent
108+
isUser={message.role === 'user'}
109+
emotion={message.emotion}
110+
>
111+
{message.role === 'assistant' && !message.isHistory ? (
112+
<TypewriterComponent
113+
text={message.content}
114+
speed={message.emotion === 'sad' ? 40 : message.emotion === 'angry' ? 20 : message.emotion === 'happy' ? 25 : 30}
115+
showCursor={true}
116+
cursorColor={message.emotion === 'sad' ? '#74b9ff' : message.emotion === 'angry' ? '#ff7675' : message.emotion === 'happy' ? '#00b894' : '#333'}
117+
isUser={false}
118+
/>
119+
) : (
120+
message.content
121+
)}
122+
{message.emotion && message.emotion !== 'neutral' && (
123+
<EmotionTag emotion={message.emotion}>
124+
{emotionLabels[message.emotion] || message.emotion}
125+
</EmotionTag>
126+
)}
127+
</MessageContent>
128+
<MessageTimestamp isUser={message.role === 'user'}>
129+
{formatTimestamp(message.timestamp)}
130+
</MessageTimestamp>
131+
{message.role === 'assistant' && (
132+
<FeedbackButtons>
133+
<FeedbackButton
134+
onClick={() => onOpenFeedbackModal(message)}
135+
whileHover={{ scale: 1.05 }}
136+
whileTap={{ scale: 0.95 }}
137+
>
138+
<MessageSquarePlus size={14} />
139+
反馈
140+
</FeedbackButton>
141+
</FeedbackButtons>
142+
)}
143+
</MessageWrapper>
144+
</MessageBubble>
145+
))
146+
)}
147+
</AnimatePresence>
148+
149+
{isLoading && (
150+
<LoadingIndicator
151+
initial={{ opacity: 0, y: 10 }}
152+
animate={{ opacity: 1, y: 0 }}
153+
exit={{ opacity: 0, y: -10 }}
154+
>
155+
<Loader2 size={18} className="spinner" />
156+
<span>正在思考中</span>
157+
<span className="dots">
158+
<span>.</span>
159+
<span>.</span>
160+
<span>.</span>
161+
</span>
162+
</LoadingIndicator>
163+
)}
164+
165+
{suggestions.length > 0 && (
166+
<Suggestions>
167+
<AnimatePresence>
168+
{suggestions.map((suggestion, index) => (
169+
<SuggestionChip
170+
key={index}
171+
initial={{ opacity: 0, scale: 0.8 }}
172+
animate={{ opacity: 1, scale: 1 }}
173+
exit={{ opacity: 0, scale: 0.8 }}
174+
transition={{ delay: index * 0.1 }}
175+
onClick={() => onSuggestionClick(suggestion)}
176+
>
177+
{suggestion}
178+
</SuggestionChip>
179+
))}
180+
</AnimatePresence>
181+
</Suggestions>
182+
)}
183+
184+
<div ref={messagesEndRef} />
185+
</MessagesContainer>
186+
187+
<InputContainer>
188+
{/* URL预览 */}
189+
{detectedURLs.length > 0 && (
190+
<URLPreview
191+
initial={{ opacity: 0, y: -10 }}
192+
animate={{ opacity: 1, y: 0 }}
193+
exit={{ opacity: 0, y: -10 }}
194+
>
195+
<Link size={16} />
196+
<URLText>{detectedURLs[0]}</URLText>
197+
<URLButton onClick={() => window.open(detectedURLs[0], '_blank')}>
198+
<ExternalLink size={14} />
199+
</URLButton>
200+
</URLPreview>
201+
)}
202+
203+
{/* 附件预览 */}
204+
{attachments.length > 0 && (
205+
<AttachmentsPreview>
206+
<AnimatePresence>
207+
{attachments.map((attachment) => (
208+
<AttachmentItem
209+
key={attachment.id}
210+
initial={{ opacity: 0, scale: 0.8 }}
211+
animate={{ opacity: 1, scale: 1 }}
212+
exit={{ opacity: 0, scale: 0.8 }}
213+
>
214+
<AttachmentIcon>
215+
{getFileIcon(attachment.type)}
216+
</AttachmentIcon>
217+
<span>{attachment.name}</span>
218+
<span>({formatFileSize(attachment.size)})</span>
219+
<RemoveAttachmentButton
220+
onClick={() => onRemoveAttachment(attachment.id)}
221+
>
222+
<X size={12} />
223+
</RemoveAttachmentButton>
224+
</AttachmentItem>
225+
))}
226+
</AnimatePresence>
227+
</AttachmentsPreview>
228+
)}
229+
230+
<InputRow>
231+
<MessageInput
232+
ref={inputRef}
233+
type="text"
234+
value={inputValue}
235+
onChange={onInputChange}
236+
onKeyPress={onKeyPress}
237+
onKeyDown={onTabNavigation}
238+
placeholder="分享你的想法和感受..."
239+
disabled={isLoading}
240+
aria-label="消息输入框"
241+
aria-describedby="input-hint"
242+
/>
243+
<AttachmentButton
244+
ref={attachmentButtonRef}
245+
onClick={() => fileInputRef.current?.click()}
246+
disabled={isLoading}
247+
onKeyDown={onTabNavigation}
248+
whileHover={{ scale: 1.05 }}
249+
whileTap={{ scale: 0.95 }}
250+
aria-label="添加附件"
251+
title="添加附件 (图片、PDF、文档)"
252+
>
253+
<Paperclip size={20} />
254+
</AttachmentButton>
255+
<SendButton
256+
ref={sendButtonRef}
257+
onClick={onSendMessage}
258+
disabled={(!inputValue.trim() && attachments.length === 0) || isLoading}
259+
onKeyDown={onTabNavigation}
260+
whileHover={{ scale: 1.05 }}
261+
whileTap={{ scale: 0.95 }}
262+
aria-label="发送消息"
263+
aria-disabled={(!inputValue.trim() && attachments.length === 0) || isLoading}
264+
title="发送消息 (Enter)"
265+
>
266+
<Send size={20} />
267+
</SendButton>
268+
</InputRow>
269+
270+
<FileInput
271+
ref={fileInputRef}
272+
type="file"
273+
multiple
274+
accept="image/*,application/pdf,.doc,.docx,.txt"
275+
onChange={onFileUpload}
276+
/>
277+
</InputContainer>
278+
</ChatContainerStyled>
279+
);
280+
};
281+
282+
export default ChatContainer;
283+

0 commit comments

Comments
 (0)