完成世界书、骰子、apiconfig页面处理
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
/* frontend-react/src/components/Mid/ChatBox/ChatBox.css */
|
||||
.chat-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-xl);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 80%;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
border-radius: var(--radius-lg);
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message.user {
|
||||
align-self: flex-end;
|
||||
background: var(--gradient-primary);
|
||||
color: white;
|
||||
border-bottom-right-radius: var(--radius-sm);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.message.ai {
|
||||
align-self: flex-start;
|
||||
background-color: var(--color-bg-secondary);
|
||||
color: var(--color-text-primary);
|
||||
border-bottom-left-radius: var(--radius-sm);
|
||||
box-shadow: var(--shadow-sm);
|
||||
border: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.message-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.message-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.message-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.message-id {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.message-toolbar {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.toolbar-buttons {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.toolbar-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all var(--transition-fast);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.toolbar-button:hover {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.message-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--radius-lg);
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.message.user .bubble {
|
||||
background: transparent;
|
||||
color: white;
|
||||
border-bottom-right-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.message.ai .bubble {
|
||||
background: transparent;
|
||||
color: var(--color-text-primary);
|
||||
border-bottom-left-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.edit-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.edit-textarea {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.edit-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.cancel-button, .save-button {
|
||||
padding: 5px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.cancel-button:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.save-button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.swipe-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.swipe-button {
|
||||
background: none;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
padding: 2px 8px;
|
||||
font-size: 0.8rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.swipe-button:hover:not(:disabled) {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.swipe-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.swipe-counter {
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.chat-input-wrapper {
|
||||
flex-shrink: 0;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
background-color: var(--color-bg-secondary);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.options-wrapper {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.options-toggle {
|
||||
width: 24px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
color: var(--color-text-muted);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast);
|
||||
font-size: 1.4rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.options-toggle:hover {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.options-toggle.active {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.options-toggle svg {
|
||||
display: none; /* Hide SVG, use text instead */
|
||||
}
|
||||
|
||||
.chat-options {
|
||||
position: absolute;
|
||||
bottom: calc(100% + var(--spacing-sm));
|
||||
left: 0;
|
||||
background-color: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-sm);
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: var(--z-dropdown);
|
||||
min-width: 140px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.option-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.option-checkbox input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all var(--transition-fast);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.option-checkbox:hover .checkmark {
|
||||
border-color: var(--color-accent);
|
||||
background-color: var(--color-accent-light);
|
||||
}
|
||||
|
||||
.option-checkbox input:checked ~ .checkmark {
|
||||
background-color: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
left: 5px;
|
||||
top: 2px;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
border: solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.option-checkbox input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.option-divider {
|
||||
height: 1px;
|
||||
background-color: var(--color-border-light);
|
||||
margin: var(--spacing-xs) 0;
|
||||
}
|
||||
|
||||
.chat-input-area {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-md);
|
||||
background-color: transparent;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 0.9rem;
|
||||
resize: none; /* 禁用手动调整大小 */
|
||||
overflow-y: hidden; /* 隐藏滚动条 */
|
||||
min-height: 28px; /* 最小高度(一行) */
|
||||
max-height: 300px; /* 最大高度 */
|
||||
transition: height 0.15s ease; /* 平滑过渡 */
|
||||
font-family: inherit;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.message-input:focus {
|
||||
outline: none;
|
||||
background-color: var(--color-bg-primary);
|
||||
border-color: var(--color-border);
|
||||
box-shadow: 0 0 0 2px var(--color-accent-ultra-light);
|
||||
}
|
||||
|
||||
.message-input::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
width: 24px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
color: var(--color-text-muted);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast);
|
||||
font-size: 1.2rem;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.send-button:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.send-button:active {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.send-button svg {
|
||||
display: none; /* Hide SVG, use text instead */
|
||||
}
|
||||
|
||||
.loading, .error {
|
||||
text-align: center;
|
||||
padding: var(--spacing-xl);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
// frontend-react/src/components/ChatBox/ChatBox.jsx
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import useChatBoxStore from '../../../Store/Mid/ChatBoxSlice';
|
||||
import './ChatBox.css';
|
||||
|
||||
const ChatBox = () => {
|
||||
const [editingId, setEditingId] = useState(null);
|
||||
const [editContent, setEditContent] = useState('');
|
||||
const messagesEndRef = useRef(null);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [showOptions, setShowOptions] = useState(false);
|
||||
const optionsRef = useRef(null);
|
||||
|
||||
// 新增:管理每条消息的当前显示的swipe版本
|
||||
const [currentSwipeId, setCurrentSwipeId] = useState({});
|
||||
|
||||
// 从 ChatBoxStore 获取状态和方法
|
||||
const {
|
||||
messages,
|
||||
isLoading,
|
||||
error,
|
||||
userName,
|
||||
characterName,
|
||||
updateMessage,
|
||||
isGenerating,
|
||||
sendMessage,
|
||||
stopGeneration,
|
||||
options,
|
||||
toggleOption
|
||||
} = useChatBoxStore();
|
||||
|
||||
const [inputHeight, setInputHeight] = useState(42);
|
||||
|
||||
// 点击外部关闭选项面板
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (optionsRef.current && !optionsRef.current.contains(event.target) &&
|
||||
!event.target.closest('.options-button')) {
|
||||
setShowOptions(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (showOptions) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [showOptions]);
|
||||
|
||||
const handleInputHeight = (e) => {
|
||||
const textarea = e.target;
|
||||
|
||||
// 标准方案:先重置为 auto,再设置为 scrollHeight
|
||||
// 这是业界公认的最佳实践,确保高度计算准确
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = textarea.scrollHeight + 'px';
|
||||
};
|
||||
|
||||
// 处理发送或终止
|
||||
const handleSendOrStop = () => {
|
||||
if (isGenerating) {
|
||||
stopGeneration();
|
||||
} else {
|
||||
sendMessage(inputValue);
|
||||
setInputValue('');
|
||||
setInputHeight(24); // 重置为一行高度
|
||||
}
|
||||
};
|
||||
|
||||
// 处理键盘事件
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendOrStop();
|
||||
}
|
||||
};
|
||||
|
||||
// 自动滚动到底部
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
|
||||
// 处理编辑消息
|
||||
const handleEdit = (message) => {
|
||||
setEditingId(message.floor);
|
||||
setEditContent(message.mes);
|
||||
};
|
||||
|
||||
// 保存编辑
|
||||
const handleSaveEdit = (messageId) => {
|
||||
// 调用 store 中的 updateMessage 方法
|
||||
updateMessage(messageId, editContent);
|
||||
setEditingId(null);
|
||||
setEditContent('');
|
||||
};
|
||||
|
||||
// 取消编辑
|
||||
const handleCancelEdit = () => {
|
||||
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 toggleOptionsPanel = () => {
|
||||
setShowOptions(!showOptions);
|
||||
};
|
||||
|
||||
// 渲染单条消息
|
||||
const renderMessage = (message) => {
|
||||
const isUser = message.is_user;
|
||||
const isEditing = editingId === message.floor;
|
||||
|
||||
// 判断是否为最新消息
|
||||
const isLatestMessage = messages.length > 0 && message.floor === messages[messages.length - 1].floor;
|
||||
|
||||
// 根据消息类型设置显示名称
|
||||
const displayName = isUser ? userName : characterName;
|
||||
|
||||
// 确定当前显示的消息内容
|
||||
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">
|
||||
{options.htmlRender && !isUser ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: currentMes }} />
|
||||
) : (
|
||||
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>
|
||||
</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">
|
||||
<div className="input-container">
|
||||
<div className="options-wrapper" ref={optionsRef}>
|
||||
<button
|
||||
className={`options-toggle ${showOptions ? 'active' : ''}`}
|
||||
title="Toggle Options"
|
||||
onClick={() => setShowOptions(!showOptions)}
|
||||
>
|
||||
{showOptions ? '×' : '≡'}
|
||||
</button>
|
||||
{showOptions && (
|
||||
<div className="chat-options">
|
||||
<label className="option-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={options.htmlRender}
|
||||
onChange={() => toggleOption('htmlRender')}
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
<span className="option-label">HTML渲染</span>
|
||||
</label>
|
||||
<label className="option-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={options.streamOutput}
|
||||
onChange={() => toggleOption('streamOutput')}
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
<span className="option-label">流式输出</span>
|
||||
</label>
|
||||
<label className="option-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={options.dynamicTable}
|
||||
onChange={() => toggleOption('dynamicTable')}
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
<span className="option-label">动态表格</span>
|
||||
</label>
|
||||
|
||||
{/* 生图工作流选项 */}
|
||||
<label className="option-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={options.imageWorkflow}
|
||||
onChange={() => toggleOption('imageWorkflow')}
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
<span className="option-label">🎨 生图工作流</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="chat-input-area">
|
||||
<textarea
|
||||
className="message-input"
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
setInputValue(e.target.value);
|
||||
handleInputHeight(e);
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
style={{
|
||||
height: `${inputHeight}px`,
|
||||
overflowY: inputHeight >= 300 ? 'auto' : 'hidden'
|
||||
}}
|
||||
placeholder="Type your message..."
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className={`send-button ${isGenerating ? 'stopping' : ''}`}
|
||||
onClick={handleSendOrStop}
|
||||
disabled={!inputValue.trim() && !isGenerating}
|
||||
title="Send Message"
|
||||
>
|
||||
{isGenerating ? '■' : '>'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatBox;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './ChatBox';
|
||||
@@ -0,0 +1,3 @@
|
||||
// Mid 区域主组件
|
||||
// 目前主要包含 ChatBox
|
||||
export { default as ChatBox } from './ChatBox';
|
||||
Reference in New Issue
Block a user