完成初版聊天界面,开始进行读取本地聊天文件开发
This commit is contained in:
@@ -1,36 +1,58 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
// 引入各区域组件(后续创建)
|
// 引入各区域组件
|
||||||
import PresetPanel from './components/PresetPanel';
|
import PresetPanel from './components/PresetPanel';
|
||||||
import ChatBox from './components/ChatBox';
|
import ChatBox from './components/ChatBox';
|
||||||
import ImageDisplay from './components/ImageDisplay';
|
import ImageDisplay from './components/ImageDisplay';
|
||||||
import DicePanel from './components/DicePanel';
|
import DicePanel from './components/DicePanel';
|
||||||
|
import RoleSelector from './components/RoleSelector';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isToolbarExpanded, setIsToolbarExpanded] = useState(false);
|
const [isToolbarExpanded, setIsToolbarExpanded] = useState(false);
|
||||||
|
const [selectedRole, setSelectedRole] = useState(null);
|
||||||
|
const [selectedChat, setSelectedChat] = useState(null);
|
||||||
|
|
||||||
const toggleToolbar = () => {
|
const toggleToolbar = () => {
|
||||||
setIsToolbarExpanded(!isToolbarExpanded);
|
setIsToolbarExpanded(!isToolbarExpanded);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理角色选择变化
|
||||||
|
const handleRoleChange = (role) => {
|
||||||
|
setSelectedRole(role);
|
||||||
|
console.log('选择的角色:', role);
|
||||||
|
// 这里可以添加其他逻辑,比如加载角色的聊天记录等
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理聊天选择变化
|
||||||
|
const handleChatChange = (role, chat) => {
|
||||||
|
setSelectedChat(chat);
|
||||||
|
console.log('选择的聊天:', role, chat);
|
||||||
|
// 这里可以添加其他逻辑,比如加载聊天内容等
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-container">
|
<div className="app-container">
|
||||||
{/* 顶部工具栏 */}
|
{/* 顶部工具栏 */}
|
||||||
<header className={`toolbar ${isToolbarExpanded ? 'expanded' : ''}`}>
|
<header className={`toolbar ${isToolbarExpanded ? 'expanded' : ''}`}>
|
||||||
<div className="toolbar-title">AI Chat Studio</div>
|
{/* 工具栏内容区域 */}
|
||||||
|
{isToolbarExpanded && (
|
||||||
|
<div className="toolbar-content">
|
||||||
|
{/* 角色选择器 */}
|
||||||
|
<RoleSelector
|
||||||
|
onRoleChange={handleRoleChange}
|
||||||
|
onChatChange={handleChatChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 工具栏切换按钮 */}
|
||||||
<button
|
<button
|
||||||
className="toolbar-toggle-btn"
|
className={`toolbar-toggle-btn ${isToolbarExpanded ? 'expanded' : ''}`}
|
||||||
onClick={toggleToolbar}
|
onClick={toggleToolbar}
|
||||||
aria-label="Toggle Toolbar"
|
aria-label="Toggle Toolbar"
|
||||||
>
|
>
|
||||||
{isToolbarExpanded ? '▼' : '▲'}
|
{isToolbarExpanded ? '▼' : '▲'}
|
||||||
</button>
|
</button>
|
||||||
{/* 展开后的内容区域(预留) */}
|
|
||||||
{isToolbarExpanded && (
|
|
||||||
<div className="toolbar-content">
|
|
||||||
{/* 工具栏内容待定 */}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* 主体内容区域 */}
|
{/* 主体内容区域 */}
|
||||||
|
|||||||
@@ -5,16 +5,26 @@ const ChatBox = () => {
|
|||||||
const [isImageGen, setIsImageGen] = useState(false);
|
const [isImageGen, setIsImageGen] = useState(false);
|
||||||
const [isDynamicTable, setIsDynamicTable] = useState(false);
|
const [isDynamicTable, setIsDynamicTable] = useState(false);
|
||||||
const [isSettingsExpanded, setIsSettingsExpanded] = useState(false);
|
const [isSettingsExpanded, setIsSettingsExpanded] = useState(false);
|
||||||
|
const [editingId, setEditingId] = useState(null);
|
||||||
|
const [editContent, setEditContent] = useState('');
|
||||||
|
|
||||||
const textareaRef = useRef(null);
|
const textareaRef = useRef(null);
|
||||||
|
|
||||||
// 自动调整 Textarea 高度
|
// 自动调整 Textarea 高度
|
||||||
const adjustHeight = () => {
|
const adjustHeight = () => {
|
||||||
const textarea = textareaRef.current;
|
const textarea = textareaRef.current;
|
||||||
if (textarea) {
|
const wrapper = textarea?.closest('.chat-input-wrapper');
|
||||||
textarea.style.height = 'auto'; // 重置高度以获取正确的 scrollHeight
|
|
||||||
// 重新设置高度,限制最大高度由 CSS max-height 控制
|
if (textarea && wrapper) {
|
||||||
textarea.style.height = textarea.scrollHeight + 'px';
|
textarea.style.height = '42px';
|
||||||
|
wrapper.style.height = 'auto';
|
||||||
|
const newHeight = textarea.scrollHeight;
|
||||||
|
|
||||||
|
if (newHeight > 42) {
|
||||||
|
textarea.style.height = newHeight + 'px';
|
||||||
|
} else {
|
||||||
|
textarea.style.height = '42px';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,6 +32,34 @@ const ChatBox = () => {
|
|||||||
adjustHeight();
|
adjustHeight();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 开始编辑消息
|
||||||
|
const startEdit = (id, content) => {
|
||||||
|
setEditingId(id);
|
||||||
|
setEditContent(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存编辑的消息
|
||||||
|
const saveEdit = (id) => {
|
||||||
|
// 这里可以添加向后端发送请求的代码
|
||||||
|
console.log(`保存消息 ${id} 的编辑内容: ${editContent}`);
|
||||||
|
|
||||||
|
// 更新前端显示
|
||||||
|
const messageIndex = messages.findIndex(msg => msg.id === id);
|
||||||
|
if (messageIndex !== -1) {
|
||||||
|
messages[messageIndex].content = editContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出编辑模式
|
||||||
|
setEditingId(null);
|
||||||
|
setEditContent('');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消编辑
|
||||||
|
const cancelEdit = () => {
|
||||||
|
setEditingId(null);
|
||||||
|
setEditContent('');
|
||||||
|
};
|
||||||
|
|
||||||
// 生成示例数据
|
// 生成示例数据
|
||||||
const generateMessages = () => {
|
const generateMessages = () => {
|
||||||
const messages = [];
|
const messages = [];
|
||||||
@@ -30,6 +68,7 @@ const ChatBox = () => {
|
|||||||
messages.push({
|
messages.push({
|
||||||
id: i,
|
id: i,
|
||||||
role: isUser ? 'user' : 'ai',
|
role: isUser ? 'user' : 'ai',
|
||||||
|
name: isUser ? '我' : 'AI助手',
|
||||||
content: isUser
|
content: isUser
|
||||||
? `这是第 ${i} 条用户消息。这是一段比较长的文本,用来测试气泡的换行效果以及滚动条的表现。`
|
? `这是第 ${i} 条用户消息。这是一段比较长的文本,用来测试气泡的换行效果以及滚动条的表现。`
|
||||||
: `这是第 ${i} 条 AI 回复。<b>包含 HTML 标签</b>的内容。如果渲染开关开启,这里应该显示粗体字。如果不开启,应该显示原始标签。`
|
: `这是第 ${i} 条 AI 回复。<b>包含 HTML 标签</b>的内容。如果渲染开关开启,这里应该显示粗体字。如果不开启,应该显示原始标签。`
|
||||||
@@ -87,14 +126,64 @@ const ChatBox = () => {
|
|||||||
{/* 消息列表 */}
|
{/* 消息列表 */}
|
||||||
{messages.map((msg) => (
|
{messages.map((msg) => (
|
||||||
<div key={msg.id} className={`message ${msg.role}`}>
|
<div key={msg.id} className={`message ${msg.role}`}>
|
||||||
|
<div className="message-container">
|
||||||
|
{/* 消息名称和工具栏在同一行 */}
|
||||||
|
<div className="message-header">
|
||||||
|
<div className="message-name">{msg.name}</div>
|
||||||
|
|
||||||
|
{/* 消息工具栏 */}
|
||||||
|
<div className="message-toolbar">
|
||||||
|
<span className="message-id">ID: {msg.id}</span>
|
||||||
|
<div className="toolbar-buttons">
|
||||||
|
<button
|
||||||
|
className="toolbar-button edit-button"
|
||||||
|
onClick={() => startEdit(msg.id, msg.content)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</button>
|
||||||
|
<button className="toolbar-button expand-button">
|
||||||
|
⋮
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 消息内容 */}
|
||||||
|
<div className="message-content">
|
||||||
<div className="bubble">
|
<div className="bubble">
|
||||||
{isHtmlRender ? (
|
{editingId === msg.id ? (
|
||||||
|
<div className="edit-container">
|
||||||
|
<textarea
|
||||||
|
value={editContent}
|
||||||
|
onChange={(e) => setEditContent(e.target.value)}
|
||||||
|
className="edit-textarea"
|
||||||
|
/>
|
||||||
|
<div className="edit-buttons">
|
||||||
|
<button
|
||||||
|
className="save-button"
|
||||||
|
onClick={() => saveEdit(msg.id)}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="cancel-button"
|
||||||
|
onClick={cancelEdit}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
isHtmlRender ? (
|
||||||
<div dangerouslySetInnerHTML={{ __html: msg.content }} />
|
<div dangerouslySetInnerHTML={{ __html: msg.content }} />
|
||||||
) : (
|
) : (
|
||||||
<div>{msg.content}</div>
|
<div>{msg.content}</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -106,7 +195,6 @@ const ChatBox = () => {
|
|||||||
placeholder="输入消息..."
|
placeholder="输入消息..."
|
||||||
onInput={handleInput}
|
onInput={handleInput}
|
||||||
rows="1"
|
rows="1"
|
||||||
// 关键修改:显式设置初始样式高度,确保可见
|
|
||||||
style={{ height: '42px' }}
|
style={{ height: '42px' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
95
frontend-react/src/components/RoleSelector.jsx
Normal file
95
frontend-react/src/components/RoleSelector.jsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const RoleSelector = ({ onRoleChange, onChatChange }) => {
|
||||||
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||||
|
const [roleData, setRoleData] = useState({});
|
||||||
|
const [selectedRole, setSelectedRole] = useState(null);
|
||||||
|
const [selectedChat, setSelectedChat] = useState(null);
|
||||||
|
|
||||||
|
// 获取角色和聊天数据
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchRoleData = async () => {
|
||||||
|
try {
|
||||||
|
console.log('开始获取角色数据...');
|
||||||
|
const response = await fetch('/api/get_all_role_and_chat');
|
||||||
|
console.log('响应状态:', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('获取到的数据:', data);
|
||||||
|
setRoleData(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取角色数据失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchRoleData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 处理角色选择
|
||||||
|
const handleRoleSelect = (role) => {
|
||||||
|
setSelectedRole(role);
|
||||||
|
setSelectedChat(null);
|
||||||
|
if (onRoleChange) {
|
||||||
|
onRoleChange(role);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理聊天选择
|
||||||
|
const handleChatSelect = (chat) => {
|
||||||
|
setSelectedChat(chat);
|
||||||
|
setIsDropdownOpen(false);
|
||||||
|
if (onChatChange) {
|
||||||
|
onChatChange(selectedRole, chat);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="role-selector">
|
||||||
|
<div className="dropdown-container">
|
||||||
|
<div
|
||||||
|
className="dropdown-trigger"
|
||||||
|
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
||||||
|
>
|
||||||
|
{selectedRole || '选择角色'}
|
||||||
|
<span className="dropdown-arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isDropdownOpen && (
|
||||||
|
<div className="dropdown-menu">
|
||||||
|
{Object.keys(roleData).map(role => (
|
||||||
|
<div
|
||||||
|
key={role}
|
||||||
|
className={`dropdown-item ${selectedRole === role ? 'selected' : ''}`}
|
||||||
|
onClick={() => handleRoleSelect(role)}
|
||||||
|
>
|
||||||
|
{role}
|
||||||
|
{selectedRole === role && (
|
||||||
|
<div className="nested-dropdown">
|
||||||
|
{roleData[role].map(chat => (
|
||||||
|
<div
|
||||||
|
key={chat}
|
||||||
|
className={`dropdown-item ${selectedChat === chat ? 'selected' : ''}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleChatSelect(chat);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{chat}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RoleSelector;
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user