diff --git a/backend/core/models/chat_history.py b/backend/core/models/chat_history.py index 793e1a5..58a2599 100644 --- a/backend/core/models/chat_history.py +++ b/backend/core/models/chat_history.py @@ -210,39 +210,40 @@ class ChatHistory(BaseModel): ) def to_chatbox_format(self) -> List[Dict[str, Any]]: - """ - 将聊天历史转换为适合前端chatbox显示的格式 + """ + 将聊天历史转换为适合前端chatbox显示的格式 - 返回: - List[Dict[str, Any]]: 按floor排序的消息字典列表,每个字典包含: - { - "name": str, - "is_user": bool, - "floor": int, - "mes": str, - "swipes": List[str], - "swipe_id": int - } - """ - # 创建消息字典列表 - messages_list = [] - for msg in self.messages: - # 获取当前消息内容:优先从swipes数组中获取,如果不存在则使用mes - current_mes = msg.mes - if msg.swipes and 0 <= msg.swipe_id < len(msg.swipes): - current_mes = msg.swipes[msg.swipe_id] + 返回: + List[Dict[str, Any]]: 按floor排序的消息字典列表,每个字典包含: + { + "name": str, + "is_user": bool, + "floor": int, + "mes": str, + "swipes": List[str], + "swipe_id": int + } + """ + # 创建消息字典列表 + messages_list = [] + for msg in self.messages: + # 获取当前消息内容:优先从swipes数组中获取,如果不存在则使用mes + current_mes = msg.mes + if msg.swipes and 0 <= msg.swipe_id < len(msg.swipes): + current_mes = msg.swipes[msg.swipe_id] - msg_dict = { - "name": msg.name, - "is_user": msg.is_user, - "floor": msg.floor, - "mes": current_mes, - "swipes": msg.swipes, - "swipe_id": msg.swipe_id - } - messages_list.append(msg_dict) + msg_dict = { + "name": msg.name, + "is_user": msg.is_user, + "floor": msg.floor, + "mes": current_mes, + "swipes": msg.swipes, + "swipe_id": msg.swipe_id + } + messages_list.append(msg_dict) - # 按floor排序 - messages_list.sort(key=lambda x: x["floor"]) + # 按floor排序 + messages_list.sort(key=lambda x: x["floor"]) + + return messages_list - return messages_list diff --git a/data/chat/testRole1/111.jsonl b/data/chat/testRole1/111.jsonl index 58a165e..30358dd 100644 --- a/data/chat/testRole1/111.jsonl +++ b/data/chat/testRole1/111.jsonl @@ -1,3 +1,5 @@ -{"integrity": "test-uuid-1", "chat_id_hash": "hash1", "note_prompt": "", "note_interval": 0, "note_position": 0, "note_depth": 0, "note_role": 0, "extensions": {}, "timedWorldInfo": {}, "variables": {}, "tainted": false, "lastInContextMessageId": -1} -{"name": "User", "is_user": true, "is_system": false, "floor": 0,"send_date": "1700000000000", "mes": "你好,我是新来的冒险者。", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} -{"name": "Guide", "is_user": false, "is_system": false, "floor": 1,"send_date": "1700000001000", "mes": "欢迎来到我们的世界!你需要什么帮助?", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} +{"user_name": "User", "character_name": "AI Dungeon Master", "integrity": "uuid-001", "chat_id_hash": "hash-001", "note_prompt": "你是一个经验丰富的D&D地下城主。", "note_interval": 0, "note_position": 0, "note_depth": 0, "note_role": 0, "extensions": {}, "timedWorldInfo": {}, "variables": {}, "tainted": false, "lastInContextMessageId": -1} +{"name": "User", "is_user": true, "is_system": false, "floor": 0, "send_date": "1700000000000", "mes": "你好,我想开始一个新的冒险。", "extra": {}, "swipes": [], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": []} +{"name": "AI Dungeon Master", "is_user": false, "is_system": false, "floor": 1, "send_date": "1700000001000", "mes": "欢迎,冒险者。请告诉我你想扮演什么角色?", "extra": {"api": "openai", "model": "gpt-4"}, "swipes": ["欢迎,冒险者。请告诉我你想扮演什么角色?", "你好,旅行者。在这个奇幻世界中,你是谁?"], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "api": "openai", "model": "gpt-4", "reasoning": null, "reasoning_duration": null, "reasoning_signature": null, "time_to_first_token": null, "bias": null} +{"name": "User", "is_user": true, "is_system": false, "floor": 2, "send_date": "1700000002000", "mes": "我想成为一名人类战士。", "extra": {}, "swipes": [], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": []} +{"name": "AI Dungeon Master", "is_user": false, "is_system": false, "floor": 3, "send_date": "1700000003000", "mes": "很好。你站在喧闹的酒馆门口,手里握着一把旧长剑。你打算做什么?", "extra": {"api": "openai", "model": "gpt-4"}, "swipes": ["很好。你站在喧闹的酒馆门口,手里握着一把旧长剑。你打算做什么?", "明白了。作为一名人类战士,你正身处繁华的市集广场。你的下一步行动是?"], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "api": "openai", "model": "gpt-4", "reasoning": null, "reasoning_duration": null, "reasoning_signature": null, "time_to_first_token": null, "bias": null} diff --git a/data/chat/testRole1/11111.jsonl b/data/chat/testRole1/11111.jsonl index 41362b0..21bf633 100644 --- a/data/chat/testRole1/11111.jsonl +++ b/data/chat/testRole1/11111.jsonl @@ -1,4 +1,5 @@ -{"integrity": "test-uuid-2", "chat_id_hash": "hash2", "note_prompt": "探索神秘森林", "note_interval": 5, "note_position": 0, "note_depth": 2, "note_role": 1, "extensions": {}, "timedWorldInfo": {}, "variables": {"location": "forest"}, "tainted": false, "lastInContextMessageId": -1} -{"name": "System", "is_user": false, "is_system": true, "floor": 0,"send_date": "1700000002000", "mes": "你进入了一片神秘的森林,周围充满了未知的危险。", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} -{"name": "User", "is_user": true, "is_system": false, "floor": 1,"send_date": "1700000003000", "mes": "我该往哪个方向走?", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} -{"name": "Ranger", "is_user": false, "is_system": false, "floor": 2,"send_date": "1700000004000", "mes": "东边有一条小路,但西边似乎有奇怪的声音。", "extra": {}, "swipes": ["东边有一条小路,但西边似乎有奇怪的声音。", "建议往东走,那边比较安全。", "小心西边,可能有野兽。"], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} +{"user_name": "Commander", "character_name": "XCOM AI", "integrity": "uuid-003", "chat_id_hash": "hash-003", "note_prompt": "你是一名XCOM基地的中央AI,负责协助指挥官管理外星威胁。", "note_interval": 0, "note_position": 0, "note_depth": 0, "note_role": 0, "extensions": {}, "timedWorldInfo": {}, "variables": {}, "tainted": false, "lastInContextMessageId": -1} +{"name": "Commander", "is_user": true, "is_system": false, "floor": 0, "send_date": "1700000000000", "mes": "报告当前的外星活动情况。", "extra": {}, "swipes": [], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": []} +{"name": "XCOM AI", "is_user": false, "is_system": false, "floor": 1, "send_date": "1700000001000", "mes": "指挥官,卫星侦测到在南美洲丛林中有高能反应。可能是外星着陆舱。", "extra": {"api": "openai", "model": "gpt-4"}, "swipes": ["指挥官,卫星侦测到在南美洲丛林中有高能反应。可能是外星着陆舱。", "警报。我们在非洲检测到异常信号,疑似外星绑架行动正在进行。"], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "api": "openai", "model": "gpt-4", "reasoning": null, "reasoning_duration": null, "reasoning_signature": null, "time_to_first_token": null, "bias": null} +{"name": "Commander", "is_user": true, "is_system": false, "floor": 2, "send_date": "1700000002000", "mes": "派遣布拉德福上尉带领一个小队去调查。", "extra": {}, "swipes": [], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": []} +{"name": "XCOM AI", "is_user": false, "is_system": false, "floor": 3, "send_date": "1700000003000", "mes": "指令已确认。天火运输机正在起飞。预计到达时间:20分钟。", "extra": {"api": "openai", "model": "gpt-4"}, "swipes": ["指令已确认。天火运输机正在起飞。预计到达时间:20分钟。", "收到。正在部署天火运输机。布拉德福上尉已登机。"], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "api": "openai", "model": "gpt-4", "reasoning": null, "reasoning_duration": null, "reasoning_signature": null, "time_to_first_token": null, "bias": null} diff --git a/data/chat/testRole2/222.jsonl b/data/chat/testRole2/222.jsonl index 7a56d08..c721f79 100644 --- a/data/chat/testRole2/222.jsonl +++ b/data/chat/testRole2/222.jsonl @@ -1,4 +1,5 @@ -{"integrity": "test-uuid-3", "chat_id_hash": "hash3", "note_prompt": "酒馆闲聊", "note_interval": 0, "note_position": 0, "note_depth": 0, "note_role": 0, "extensions": {}, "timedWorldInfo": {}, "variables": {}, "tainted": false, "lastInContextMessageId": -1} -{"name": "User", "is_user": true, "is_system": false, "floor": 0,"send_date": "1700000005000", "mes": "有人知道关于龙的消息吗?", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} -{"name": "Bard", "is_user": false, "is_system": false, "floor": 1,"send_date": "1700000006000", "mes": "听说北边的山脉里有一条红龙,它守护着巨大的宝藏。", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} -{"name": "Warrior", "is_user": false, "is_system": false, "floor": 2,"send_date": "1700000007000", "mes": "别信那些谣言,我上周去过那里,什么都没发现。", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejsprocessed": [], "gen_started": null, "gen_finished": null} +{"user_name": "Player", "character_name": "Game Master", "integrity": "uuid-002", "chat_id_hash": "hash-002", "note_prompt": "场景:赛博朋克风格的未来城市。", "note_interval": 0, "note_position": 0, "note_depth": 0, "note_role": 0, "extensions": {}, "timedWorldInfo": {}, "variables": {}, "tainted": false, "lastInContextMessageId": -1} +{"name": "Player", "is_user": true, "is_system": false, "floor": 0, "send_date": "1700000000000", "mes": "我检查我的义体状态。", "extra": {}, "swipes": [], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": []} +{"name": "Game Master", "is_user": false, "is_system": false, "floor": 1, "send_date": "1700000001000", "mes": "你的视觉义眼显示系统正常,但左臂的伺服电机发出轻微的嗡嗡声,似乎需要维护。", "extra": {"api": "openai", "model": "gpt-4"}, "swipes": ["你的视觉义眼显示系统正常,但左臂的伺服电机发出轻微的嗡嗡声,似乎需要维护。", "系统自检完成。你的神经接口连接稳定,但义体排异反应指数略有上升。"], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "api": "openai", "model": "gpt-4", "reasoning": null, "reasoning_duration": null, "reasoning_signature": null, "time_to_first_token": null, "bias": null} +{"name": "Player", "is_user": true, "is_system": false, "floor": 2, "send_date": "1700000002000", "mes": "我联系我的黑客朋友,问他知不知道哪里有靠谱的义体医生。", "extra": {}, "swipes": [], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": []} +{"name": "Game Master", "is_user": false, "is_system": false, "floor": 3, "send_date": "1700000003000", "mes": "你的朋友回复说:'去下城区的老维克那里,虽然他的店看起来很破,但他手艺没得说。'", "extra": {"api": "openai", "model": "gpt-4"}, "swipes": ["你的朋友回复说:'去下城区的老维克那里,虽然他的店看起来很破,但他手艺没得说。'", "通讯接通。你的朋友告诉你:'别去连锁店,去太平间后巷找'扳手',他收费公道。'"], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "api": "openai", "model": "gpt-4", "reasoning": null, "reasoning_duration": null, "reasoning_signature": null, "time_to_first_token": null, "bias": null} diff --git a/data/chat/testRole3/333.jsonl b/data/chat/testRole3/333.jsonl index a1c152d..0e8b60c 100644 --- a/data/chat/testRole3/333.jsonl +++ b/data/chat/testRole3/333.jsonl @@ -1,6 +1,5 @@ -{"integrity": "test-uuid-3", "chat_id_hash": "hash3", "note_prompt": "酒馆闲聊", "note_interval": 0, "note_position": 0, "note_depth": 0, "note_role": 0, "extensions": {}, "timedWorldInfo": {}, "variables": {}, "tainted": false, "lastInContextMessageId": -1} -{"name": "User", "is_user": true, "is_system": false, "floor": 0,"send_date": "1700000005000", "mes": "有人知道关于龙的消息吗?", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} -{"name": "Bard", "is_user": false, "is_system": false, "floor": 1,"send_date": "1700000006000", "mes": "听说北边的山脉里有一条红龙,它守护着巨大的宝藏。", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} -{"name": "User", "is_user": true, "is_system": false, "floor": 3,"send_date": "1700000005000", "mes": "别信那些谣言,我上周去过那里,什么都没发现。", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} -{"name": "Warrior", "is_user": false, "is_system": false, "floor": 4,"send_date": "1700000007000", "mes": "别信那些谣言,我上周去过那里,什么都没发现。", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejsprocessed": [], "gen_started": null, "gen_finished": null} -{"name": "User", "is_user": true, "is_system": false, "floor": 5,"send_date": "1700000005000", "mes": "你要是发现了,那我还去干什么?", "extra": {}, "swipes": [], "swipe_id": 0, "swipe_info": [], "title": "", "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "gen_started": null, "gen_finished": null} +{"user_name": "Player", "character_name": "Narrator", "integrity": "uuid-004", "chat_id_hash": "hash-004", "note_prompt": "这是一个文字冒险游戏,你需要描述场景并等待玩家输入。", "note_interval": 0, "note_position": 0, "note_depth": 0, "note_role": 0, "extensions": {}, "timedWorldInfo": {}, "variables": {}, "tainted": false, "lastInContextMessageId": -1} +{"name": "Player", "is_user": true, "is_system": false, "floor": 0, "send_date": "1700000000000", "mes": "开始游戏。", "extra": {}, "swipes": [], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": []} +{"name": "Narrator", "is_user": false, "is_system": false, "floor": 1, "send_date": "1700000001000", "mes": "你醒来时发现自己躺在一片陌生的森林里,四周弥漫着浓雾。你身边有一个背包和一把生锈的匕首。", "extra": {"api": "openai", "model": "gpt-4"}, "swipes": ["你醒来时发现自己躺在一片陌生的森林里,四周弥漫着浓雾。你身边有一个背包和一把生锈的匕首。", "当你睁开眼睛,发现自己身处一艘废弃的飞船中,应急灯闪烁着红光。你手里紧握着一个数据盘。"], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "api": "openai", "model": "gpt-4", "reasoning": null, "reasoning_duration": null, "reasoning_signature": null, "time_to_first_token": null, "bias": null} +{"name": "Player", "is_user": true, "is_system": false, "floor": 2, "send_date": "1700000002000", "mes": "我打开背包看看里面有什么。", "extra": {}, "swipes": [], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": []} +{"name": "Narrator", "is_user": false, "is_system": false, "floor": 3, "send_date": "1700000003000", "mes": "背包里有一块干硬的面包,一个水壶(里面还有半壶水),以及一张画着奇怪符号的羊皮纸。", "extra": {"api": "openai", "model": "gpt-4"}, "swipes": ["背包里有一块干硬的面包,一个水壶(里面还有半壶水),以及一张画着奇怪符号的羊皮纸。", "背包里只有一把激光手枪,能量槽仅剩10%。还有一张写着'不要相信AI'的纸条。"], "swipe_id": 0, "force_avatar": null, "variables": [], "variables_initialized": [], "is_ejs_processed": [], "api": "openai", "model": "gpt-4", "reasoning": null, "reasoning_duration": null, "reasoning_signature": null, "time_to_first_token": null, "bias": null} diff --git a/frontend-react/src/components/ChatBox/ChatBox.css b/frontend-react/src/components/ChatBox/ChatBox.css index 015a6ca..2e84193 100644 --- a/frontend-react/src/components/ChatBox/ChatBox.css +++ b/frontend-react/src/components/ChatBox/ChatBox.css @@ -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; +} diff --git a/frontend-react/src/components/ChatBox/ChatBox.jsx b/frontend-react/src/components/ChatBox/ChatBox.jsx index 4834951..82b18e6 100644 --- a/frontend-react/src/components/ChatBox/ChatBox.jsx +++ b/frontend-react/src/components/ChatBox/ChatBox.jsx @@ -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 ( -