完成后端chatmessage格式修改,可以在前端完成swipe切换显示
This commit is contained in:
@@ -100,6 +100,21 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 气泡本体 */
|
||||
.message .bubble {
|
||||
max-width: 100%;
|
||||
padding: 10px 15px;
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
/* 新增:确保内容区域可以容纳swipe控件 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 区分左右布局 */
|
||||
.message.user {
|
||||
justify-content: flex-end; /* 用户消息靠右 */
|
||||
@@ -352,7 +367,7 @@
|
||||
/* ==================== 输入区域 ==================== */
|
||||
|
||||
.chat-input-wrapper {
|
||||
background-color: #fff;
|
||||
background-color: #f9f9f9;
|
||||
border-top: 1px solid #ddd;
|
||||
padding: 10px 20px;
|
||||
height: 62px; /* 明确设置高度 */
|
||||
@@ -478,3 +493,91 @@
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.options-button {
|
||||
height: 42px;
|
||||
width: 42px;
|
||||
background-color: transparent;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.options-button:hover {
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
height: 42px;
|
||||
width: 42px; /* 添加固定宽度,使按钮为正方形 */
|
||||
padding: 0;
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex; /* 添加 flex 布局 */
|
||||
align-items: center; /* 垂直居中 */
|
||||
justify-content: center; /* 水平居中 */
|
||||
flex-shrink: 0;
|
||||
font-size: 18px; /* 设置图标大小 */
|
||||
}
|
||||
|
||||
.send-button:hover {
|
||||
background-color: #40a9ff;
|
||||
}
|
||||
|
||||
.send-button.stopping {
|
||||
background-color: #ff4d4f; /* 终止状态下的背景色 */
|
||||
}
|
||||
|
||||
.send-button.stopping:hover {
|
||||
background-color: #ff7875;
|
||||
}
|
||||
|
||||
/* Swipe控制按钮样式 */
|
||||
.swipe-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.swipe-button {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: #555;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.swipe-button:hover:not(:disabled) {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.swipe-button:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.swipe-counter {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
@@ -8,179 +8,247 @@ const ChatBox = () => {
|
||||
const messagesEndRef = useRef(null);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
// 从 ChatBoxStore 获取状态和方法
|
||||
const {
|
||||
messages,
|
||||
isLoading,
|
||||
error,
|
||||
userName,
|
||||
characterName,
|
||||
updateMessage,
|
||||
isGenerating,
|
||||
sendMessage,
|
||||
stopGeneration
|
||||
} = useChatBoxStore();
|
||||
// 新增:管理每条消息的当前显示的swipe版本
|
||||
const [currentSwipeId, setCurrentSwipeId] = useState({});
|
||||
|
||||
const [inputHeight, setInputHeight] = useState(42);
|
||||
// 从 ChatBoxStore 获取状态和方法
|
||||
const {
|
||||
messages,
|
||||
isLoading,
|
||||
error,
|
||||
userName,
|
||||
characterName,
|
||||
updateMessage,
|
||||
isGenerating,
|
||||
sendMessage,
|
||||
stopGeneration
|
||||
} = useChatBoxStore();
|
||||
|
||||
// 添加输入框高度自适应处理
|
||||
const handleInputHeight = (e) => {
|
||||
const textarea = e.target;
|
||||
const newHeight = Math.min(Math.max(textarea.scrollHeight, 42), 300);
|
||||
setInputHeight(newHeight);
|
||||
};
|
||||
const [inputHeight, setInputHeight] = useState(42);
|
||||
|
||||
// 处理发送或终止
|
||||
const handleSendOrStop = () => {
|
||||
if (isGenerating) {
|
||||
stopGeneration();
|
||||
} else {
|
||||
sendMessage(inputValue);
|
||||
setInputValue('');
|
||||
setInputHeight(42);
|
||||
}
|
||||
};
|
||||
// 添加输入框高度自适应处理
|
||||
const handleInputHeight = (e) => {
|
||||
const textarea = e.target;
|
||||
const newHeight = Math.min(Math.max(textarea.scrollHeight, 42), 300);
|
||||
setInputHeight(newHeight);
|
||||
};
|
||||
|
||||
// 处理键盘事件
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendOrStop();
|
||||
}
|
||||
};
|
||||
// 处理发送或终止
|
||||
const handleSendOrStop = () => {
|
||||
if (isGenerating) {
|
||||
stopGeneration();
|
||||
} else {
|
||||
sendMessage(inputValue);
|
||||
setInputValue('');
|
||||
setInputHeight(42);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理键盘事件
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendOrStop();
|
||||
}
|
||||
};
|
||||
|
||||
// 自动滚动到底部
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
|
||||
// 处理编辑消息
|
||||
const handleEdit = (message) => {
|
||||
setEditingId(message.floor);
|
||||
setEditContent(message.mes);
|
||||
setEditingId(message.floor);
|
||||
setEditContent(message.mes);
|
||||
};
|
||||
|
||||
// 保存编辑
|
||||
const handleSaveEdit = (messageId) => {
|
||||
// 调用 store 中的 updateMessage 方法
|
||||
updateMessage(messageId, editContent);
|
||||
setEditingId(null);
|
||||
setEditContent('');
|
||||
// 调用 store 中的 updateMessage 方法
|
||||
updateMessage(messageId, editContent);
|
||||
setEditingId(null);
|
||||
setEditContent('');
|
||||
};
|
||||
|
||||
// 取消编辑
|
||||
const handleCancelEdit = () => {
|
||||
setEditingId(null);
|
||||
setEditContent('');
|
||||
setEditingId(null);
|
||||
setEditContent('');
|
||||
};
|
||||
|
||||
// 新增:处理swipe切换
|
||||
const handleSwipeChange = (messageId, direction) => {
|
||||
const message = messages.find(m => m.floor === messageId);
|
||||
if (message && message.swipes && message.swipes.length > 0) {
|
||||
const currentIndex = currentSwipeId[messageId] !== undefined
|
||||
? currentSwipeId[messageId]
|
||||
: message.swipe_id;
|
||||
const newIndex = currentIndex + direction;
|
||||
|
||||
if (newIndex >= 0 && newIndex < message.swipes.length) {
|
||||
setCurrentSwipeId(prev => ({
|
||||
...prev,
|
||||
[messageId]: newIndex
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染单条消息
|
||||
const renderMessage = (message) => {
|
||||
const isUser = message.is_user;
|
||||
const isEditing = editingId === message.floor;
|
||||
const isUser = message.is_user;
|
||||
const isEditing = editingId === message.floor;
|
||||
|
||||
// 根据消息类型设置显示名称
|
||||
const displayName = isUser ? userName : characterName;
|
||||
// 判断是否为最新消息
|
||||
const isLatestMessage = messages.length > 0 && message.floor === messages[messages.length - 1].floor;
|
||||
|
||||
return (
|
||||
<div key={message.floor} className={`message ${isUser ? 'user' : 'ai'}`}>
|
||||
<div className="message-container">
|
||||
<div className="message-header">
|
||||
<span className="message-name">{displayName}</span>
|
||||
<span className="message-id">#{message.floor}</span>
|
||||
<div className="message-toolbar">
|
||||
<div className="toolbar-buttons">
|
||||
<button
|
||||
className="toolbar-button"
|
||||
onClick={() => handleEdit(message)}
|
||||
title="编辑"
|
||||
>
|
||||
✎
|
||||
</button>
|
||||
<button
|
||||
className="toolbar-button"
|
||||
title="更多"
|
||||
>
|
||||
•••
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="message-content">
|
||||
{isEditing ? (
|
||||
<div className="edit-container">
|
||||
<textarea
|
||||
className="edit-textarea"
|
||||
value={editContent}
|
||||
onChange={(e) => setEditContent(e.target.value)}
|
||||
/>
|
||||
<div className="edit-buttons">
|
||||
<button
|
||||
className="cancel-button"
|
||||
onClick={handleCancelEdit}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
className="save-button"
|
||||
onClick={() => handleSaveEdit(message.floor)}
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bubble">
|
||||
{message.mes}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// 根据消息类型设置显示名称
|
||||
const displayName = isUser ? userName : characterName;
|
||||
|
||||
return (
|
||||
<div className="chat-box">
|
||||
<div className="chat-messages">
|
||||
{isLoading ? (
|
||||
<div className="loading">加载中...</div>
|
||||
) : error ? (
|
||||
<div className="error">{error}</div>
|
||||
) : messages.length === 0 ? (
|
||||
<div className="loading">暂无消息</div>
|
||||
) : (
|
||||
messages.map(renderMessage)
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
<div className="chat-input-wrapper">
|
||||
<div className="chat-input-area">
|
||||
<textarea
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
setInputValue(e.target.value);
|
||||
handleInputHeight(e);
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
style={{ height: `${inputHeight}px` }}
|
||||
placeholder="输入消息..."
|
||||
/>
|
||||
// 确定当前显示的消息内容
|
||||
let currentMes = message.mes;
|
||||
let hasSwipes = message.swipes && message.swipes.length > 0;
|
||||
let currentSwipeIndex = message.swipe_id;
|
||||
|
||||
if (hasSwipes) {
|
||||
// 如果有swipes数组
|
||||
if (currentSwipeId[message.floor] !== undefined) {
|
||||
// 如果用户已经切换过版本,使用用户选择的版本
|
||||
currentSwipeIndex = currentSwipeId[message.floor];
|
||||
} else {
|
||||
// 否则使用默认的swipe_id
|
||||
currentSwipeIndex = message.swipe_id;
|
||||
}
|
||||
|
||||
if (currentSwipeIndex >= 0 && currentSwipeIndex < message.swipes.length) {
|
||||
currentMes = message.swipes[currentSwipeIndex];
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={message.floor} className={`message ${isUser ? 'user' : 'ai'}`}>
|
||||
<div className="message-container">
|
||||
<div className="message-header">
|
||||
<span className="message-name">{displayName}</span>
|
||||
<span className="message-id">#{message.floor}</span>
|
||||
<div className="message-toolbar">
|
||||
<div className="toolbar-buttons">
|
||||
<button
|
||||
className="toolbar-button"
|
||||
onClick={() => handleEdit(message)}
|
||||
title="编辑"
|
||||
>
|
||||
✎
|
||||
</button>
|
||||
<button
|
||||
className="toolbar-button"
|
||||
title="更多"
|
||||
>
|
||||
•••
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="message-content">
|
||||
{isEditing ? (
|
||||
<div className="edit-container">
|
||||
<textarea
|
||||
className="edit-textarea"
|
||||
value={editContent}
|
||||
onChange={(e) => setEditContent(e.target.value)}
|
||||
/>
|
||||
<div className="edit-buttons">
|
||||
<button
|
||||
className="cancel-button"
|
||||
onClick={handleCancelEdit}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
className="save-button"
|
||||
onClick={() => handleSaveEdit(message.floor)}
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bubble">
|
||||
{currentMes}
|
||||
{hasSwipes && isLatestMessage && !isUser && (
|
||||
<div className="swipe-controls">
|
||||
<button
|
||||
className="swipe-button"
|
||||
onClick={() => handleSwipeChange(message.floor, -1)}
|
||||
disabled={currentSwipeIndex === 0}
|
||||
>
|
||||
◀
|
||||
</button>
|
||||
<span className="swipe-counter">
|
||||
{currentSwipeIndex + 1}/{message.swipes.length}
|
||||
</span>
|
||||
<button
|
||||
className="swipe-button"
|
||||
onClick={() => handleSwipeChange(message.floor, 1)}
|
||||
disabled={currentSwipeIndex === message.swipes.length - 1}
|
||||
>
|
||||
▶
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className={`send-button ${isGenerating ? 'stopping' : ''}`}
|
||||
onClick={handleSendOrStop}
|
||||
disabled={!inputValue.trim() && !isGenerating}
|
||||
>
|
||||
{isGenerating ? '终止' : '发送'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-box">
|
||||
<div className="chat-messages">
|
||||
{isLoading ? (
|
||||
<div className="loading">加载中...</div>
|
||||
) : error ? (
|
||||
<div className="error">{error}</div>
|
||||
) : messages.length === 0 ? (
|
||||
<div className="loading">暂无消息</div>
|
||||
) : (
|
||||
messages.map(renderMessage)
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
<div className="chat-input-wrapper">
|
||||
<button className="options-button" title="展开选项">
|
||||
☰
|
||||
</button>
|
||||
<div className="chat-input-area">
|
||||
<textarea
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
setInputValue(e.target.value);
|
||||
handleInputHeight(e);
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
style={{ height: `${inputHeight}px` }}
|
||||
placeholder="输入消息..."
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className={`send-button ${isGenerating ? 'stopping' : ''}`}
|
||||
onClick={handleSendOrStop}
|
||||
disabled={!inputValue.trim() && !isGenerating}
|
||||
>
|
||||
{isGenerating ? '■' : '➤'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatBox;
|
||||
|
||||
Reference in New Issue
Block a user