qwe
This commit is contained in:
@ -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>
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
19
app/page.tsx
19
app/page.tsx
@ -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]);
|
||||||
|
|||||||
@ -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()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
1455
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user