qwe
Some checks failed
Docker Build and Deploy / build-and-push (push) Has been cancelled
Docker Build and Deploy / deploy-hk (push) Has been cancelled

This commit is contained in:
zch1234qq
2025-10-21 09:17:52 +08:00
parent ecc8347146
commit 4afe6529fb
7 changed files with 1529 additions and 32 deletions

View File

@ -2,11 +2,11 @@ import React from 'react';
interface ChatMessageProps { interface ChatMessageProps {
content: string; content: string;
sender: 'user' | 'bot'; role: 'user' | 'assistant';
timestamp: Date; timestamp: Date;
} }
export const ChatMessage: React.FC<ChatMessageProps> = ({ content, sender, timestamp }) => { export const ChatMessage: React.FC<ChatMessageProps> = ({ content, role, timestamp }) => {
// 格式化时间戳 // 格式化时间戳
const formattedTime = timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const formattedTime = timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
@ -14,14 +14,14 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ content, sender, times
const messageParts = content.split('\n'); const messageParts = content.split('\n');
return ( return (
<div className={`flex items-end mb-4 ${sender === 'user' ? 'justify-end' : 'justify-start'}`}> <div className={`flex items-end mb-4 ${role === 'user' ? 'justify-end' : 'justify-start'}`}>
{sender === 'bot' && ( {role === 'assistant' && (
<div className="user-avatar mr-2"> <div className="user-avatar mr-2">
<span>AI</span> <span>AI</span>
</div> </div>
)} )}
<div className={`p-3 rounded-lg ${sender === 'user' ? 'message-user' : 'message-bot'}`}> <div className={`p-3 rounded-lg ${role === 'user' ? 'message-user' : 'message-bot'}`}>
{messageParts.map((part, index) => ( {messageParts.map((part, index) => (
<React.Fragment key={index}> <React.Fragment key={index}>
{part} {part}
@ -30,7 +30,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ content, sender, times
))} ))}
</div> </div>
{sender === 'user' && ( {role === 'user' && (
<div className="user-avatar ml-2"> <div className="user-avatar ml-2">
<span></span> <span></span>
</div> </div>

View File

@ -1,5 +1,10 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { Box, Paper, Typography, Avatar, useMediaQuery, useTheme } from '@mui/material'; import { Box, Paper, Typography, Avatar, useMediaQuery, useTheme } from '@mui/material';
import ReactMarkdown from 'react-markdown';
// @ts-ignore // 临时忽略类型声明缺失,等待官方类型包或自定义声明
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
// @ts-ignore
import { dark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import type { Message as MessageType } from '../types/types'; import type { Message as MessageType } from '../types/types';
interface MessageProps { interface MessageProps {
@ -8,7 +13,7 @@ interface MessageProps {
} }
const Message: React.FC<MessageProps> = ({ message, isLast = false }) => { const Message: React.FC<MessageProps> = ({ message, isLast = false }) => {
const isUser = message.sender === 'user'; const isUser = message.role === 'user';
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const paperRef = useRef<HTMLDivElement>(null); const paperRef = useRef<HTMLDivElement>(null);
@ -67,14 +72,49 @@ const Message: React.FC<MessageProps> = ({ message, isLast = false }) => {
} }
}} }}
> >
<div style={{ lineHeight: 1.6 }}> <ReactMarkdown
{message.content.split('\n').map((line, index) => ( components={{
<React.Fragment key={index}> //@ts-ignore // 临时忽略类型声明缺失,等待官方类型包或自定义声明
{line} code({ inline, className, children, ...props }) {
{index < message.content.split('\n').length - 1 && <br />} const match = /language-(\w+)/.exec(className || '');
</React.Fragment> return !inline && match ? (
))} <SyntaxHighlighter
</div> style={dark}
language={match[1]}
PreTag="div"
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
p: ({ ...props }) => <Typography variant="body1" paragraph sx={{ margin: '0.5em 0' }} {...props} />,
h1: ({ ...props }) => <Typography variant="h4" gutterBottom {...props} />,
h2: ({ ...props }) => <Typography variant="h5" gutterBottom {...props} />,
h3: ({ ...props }) => <Typography variant="h6" gutterBottom {...props} />,
ul: ({ ...props }) => <ul style={{ paddingLeft: '1.5em', margin: '0.5em 0' }} {...props} />,
ol: ({ ...props }) => <ol style={{ paddingLeft: '1.5em', margin: '0.5em 0' }} {...props} />,
li: ({ ...props }) => <li style={{ margin: '0.25em 0' }} {...props} />,
a: ({ ...props }) => <a style={{ color: theme.palette.primary.main, textDecoration: 'underline' }} {...props} />,
blockquote: ({ ...props }) => (
<blockquote style={{
borderLeft: `4px solid ${theme.palette.divider}`,
marginLeft: 0,
paddingLeft: '1em',
fontStyle: 'italic',
color: theme.palette.text.secondary
}} {...props} />
),
strong: ({ ...props }) => <strong style={{ fontWeight: 600 }} {...props} />,
em: ({ ...props }) => <em style={{ fontStyle: 'italic' }} {...props} />
}}
>
{message.content}
</ReactMarkdown>
<Typography <Typography
variant="caption" variant="caption"

View File

@ -26,7 +26,8 @@ import { Sidebar } from './components/Sidebar';
const welcomeMessage: MessageType = { const welcomeMessage: MessageType = {
id: '1', id: '1',
content: '终于等到你了!我是你的爆款文案策划师,开始之前,我需要你提供一些简单的信息,让我能给你更有针对性的运营方案。\n1. 你的行业/背调/产品/服务(即使没有现有产品,可以说想要的产品/服务)\n2. 你的目标用户(根据你从业经验描绘的目标用户)', content: '终于等到你了!我是你的爆款文案策划师,开始之前,我需要你提供一些简单的信息,让我能给你更有针对性的运营方案。\n1. 你的行业/背调/产品/服务(即使没有现有产品,可以说想要的产品/服务)\n2. 你的目标用户(根据你从业经验描绘的目标用户)',
sender: 'bot', role: 'assistant',
content_type: 'text',
timestamp: new Date(Date.now() - 60000), timestamp: new Date(Date.now() - 60000),
}; };
@ -85,7 +86,8 @@ const ChatInterface: React.FC = () => {
const userMessage: MessageType = { const userMessage: MessageType = {
id: Date.now().toString(), id: Date.now().toString(),
content: input, content: input,
sender: 'user', role: 'user',
content_type: 'text',
timestamp: new Date(), timestamp: new Date(),
}; };
@ -107,12 +109,12 @@ const ChatInterface: React.FC = () => {
// 更新消息列表,找到临时消息或创建新消息 // 更新消息列表,找到临时消息或创建新消息
setMessages(prev => { setMessages(prev => {
// 检查是否已存在临时bot消息 // 检查是否已存在临时bot消息
const hasTempMessage = prev.some(msg => msg.id === tempBotMessageId && msg.sender === 'bot'); const hasTempMessage = prev.some(msg => msg.id === tempBotMessageId && msg.role === 'assistant');
if (hasTempMessage) { if (hasTempMessage) {
// 更新已存在的临时消息 // 更新已存在的临时消息
return prev.map(msg => return prev.map(msg =>
msg.id === tempBotMessageId && msg.sender === 'bot' msg.id === tempBotMessageId && msg.role === 'assistant'
? { ...msg, content: partialText } ? { ...msg, content: partialText }
: msg : msg
); );
@ -121,7 +123,8 @@ const ChatInterface: React.FC = () => {
const tempBotMessage: MessageType = { const tempBotMessage: MessageType = {
id: tempBotMessageId, id: tempBotMessageId,
content: partialText, content: partialText,
sender: 'bot', role: 'assistant',
content_type: 'text',
timestamp: new Date(), timestamp: new Date(),
}; };
return [...prev, tempBotMessage]; return [...prev, tempBotMessage];
@ -137,7 +140,8 @@ const ChatInterface: React.FC = () => {
const mockBotMessage: MessageType = { const mockBotMessage: MessageType = {
id: (Date.now() + 1).toString(), id: (Date.now() + 1).toString(),
content: '您的消息已收到!这是一条智能回复,为您提供专业的文案建议。\n\n根据您的需求我可以为您提供\n- 吸引人的标题创意\n- 产品描述优化\n- 社交媒体文案\n- 广告标语设计\n\n请继续告诉我更多关于您的产品或服务信息我会提供更有针对性的建议', content: '您的消息已收到!这是一条智能回复,为您提供专业的文案建议。\n\n根据您的需求我可以为您提供\n- 吸引人的标题创意\n- 产品描述优化\n- 社交媒体文案\n- 广告标语设计\n\n请继续告诉我更多关于您的产品或服务信息我会提供更有针对性的建议',
sender: 'bot', role: 'assistant',
content_type: 'text',
timestamp: new Date(), timestamp: new Date(),
}; };
setMessages(prev => [...prev, mockBotMessage]); setMessages(prev => [...prev, mockBotMessage]);
@ -149,7 +153,8 @@ const ChatInterface: React.FC = () => {
const errorMessage: MessageType = { const errorMessage: MessageType = {
id: (Date.now() + 2).toString(), id: (Date.now() + 2).toString(),
content: `抱歉,处理您的请求时遇到了问题。请稍后再试,或刷新页面重试。`, content: `抱歉,处理您的请求时遇到了问题。请稍后再试,或刷新页面重试。`,
sender: 'bot', role: 'assistant',
content_type: 'text',
timestamp: new Date(), timestamp: new Date(),
}; };
setMessages(prev => [...prev, errorMessage]); setMessages(prev => [...prev, errorMessage]);

View File

@ -168,7 +168,8 @@ class CozeService {
const botMessage: Message = { const botMessage: Message = {
id: messageId, id: messageId,
content: fullContent, content: fullContent,
sender: 'bot', role: 'assistant',
content_type: 'text',
timestamp: new Date() timestamp: new Date()
}; };
@ -218,7 +219,8 @@ class CozeService {
return { return {
id: Date.now().toString(), id: Date.now().toString(),
content: mockContent, content: mockContent,
sender: 'bot', role: 'assistant',
content_type: 'text',
timestamp: new Date() timestamp: new Date()
}; };
} }

View File

@ -6,17 +6,15 @@ export interface Message {
* 消息唯一标识符 * 消息唯一标识符
*/ */
id: string; id: string;
/** /**
* 消息内容 * 消息内容
*/ */
content: string; content: string;
content_type: 'text'|'card';
/** /**
* 发送者类型 * 发送者类型
*/ */
sender: 'user' | 'bot'; role: 'user' | 'assistant';
/** /**
* 消息时间戳 * 消息时间戳
*/ */
@ -31,7 +29,6 @@ export interface CozeConfig {
* Coze API 密钥 * Coze API 密钥
*/ */
apiKey: string; apiKey: string;
/** /**
* Bot ID * Bot ID
*/ */

1455
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,9 @@
"@mui/material": "^7.3.4", "@mui/material": "^7.3.4",
"next": "15.5.5", "next": "15.5.5",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0" "react-dom": "19.1.0",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^15.6.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",