优化chatbox

This commit is contained in:
北枳 2025-08-26 23:53:23 +08:00
parent f40436492b
commit 13e482ade3
2 changed files with 123 additions and 248 deletions

View File

@ -18,225 +18,16 @@ import {
} from "./types";
import { post } from "@/api/request";
// Mock 数据
const MOCK_MESSAGES: RealApiMessage[] = [
// 用户发送剧本
// 空消息 默认展示
const EMPTY_MESSAGES: RealApiMessage[] = [
{
id: 1,
role: 'user',
content: JSON.stringify([{
type: 'text',
content: '我想拍一个关于一个小女孩和她的机器人朋友的故事,故事发生在未来世界。'
}]),
created_at: '2024-03-20T10:00:00Z',
function_name: undefined,
custom_data: undefined,
status: 'success',
intent_type: 'chat'
},
// 项目初始化
{
id: 2,
role: 'system',
content: '我会帮您创建一个温馨感人的科幻短片,讲述人工智能与人类情感的故事。',
created_at: '2024-03-20T10:00:10Z',
function_name: 'create_project',
custom_data: {
project_data: {
script: '小女孩和机器人朋友的故事'
}
},
status: 'success',
intent_type: 'procedure'
},
// 剧本总结
{
id: 3,
role: 'system',
content: '故事概要在2045年的未来城市10岁的小女孩艾米丽收到了一个特别的生日礼物——一个具有高度情感智能的机器人伙伴"小星"。随着时间推移,他们建立了深厚的友谊。当小星因能源耗尽即将永久关闭时,艾米丽想尽办法寻找解决方案,最终通过她的坚持和创意,成功为小星找到了新的能源,让这段跨越人机界限的友谊得以延续。',
created_at: '2024-03-20T10:01:00Z',
function_name: 'generate_script_summary',
custom_data: {
summary: '一个关于友谊和希望的温暖故事'
},
status: 'success',
intent_type: 'procedure'
},
// 角色生成 - 艾米丽
{
id: 4,
role: 'system',
content: '主角艾米丽的形象已生成',
created_at: '2024-03-20T10:02:00Z',
function_name: 'generate_character',
custom_data: {
character_name: '艾米丽',
image_path: 'https://picsum.photos/seed/emily/300/400',
completed_count: 1,
total_count: 2
},
status: 'success',
intent_type: 'procedure'
},
// 角色生成 - 小星
{
id: 5,
role: 'system',
content: '机器人小星的形象已生成',
created_at: '2024-03-20T10:03:00Z',
function_name: 'generate_character',
custom_data: {
character_name: '小星',
image_path: 'https://picsum.photos/seed/robot/300/400',
completed_count: 2,
total_count: 2
},
status: 'success',
intent_type: 'procedure'
},
// 场景生成 - 未来城市
{
id: 6,
role: 'system',
content: '未来城市场景设计完成',
created_at: '2024-03-20T10:04:00Z',
function_name: 'generate_sketch',
custom_data: {
sketch_name: '未来城市街景',
image_path: 'https://picsum.photos/seed/city/600/400',
completed_count: 1,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 场景生成 - 艾米丽的房间
{
id: 7,
role: 'system',
content: '艾米丽的未来风格卧室设计完成',
created_at: '2024-03-20T10:05:00Z',
function_name: 'generate_sketch',
custom_data: {
sketch_name: '艾米丽的卧室',
image_path: 'https://picsum.photos/seed/room/600/400',
completed_count: 2,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 场景生成 - 实验室
{
id: 8,
role: 'system',
content: '高科技实验室场景设计完成',
created_at: '2024-03-20T10:06:00Z',
function_name: 'generate_sketch',
custom_data: {
sketch_name: '未来实验室',
image_path: 'https://picsum.photos/seed/lab/600/400',
completed_count: 3,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 分镜生成 - 相遇
{
id: 9,
role: 'system',
content: '第一个分镜:艾米丽收到礼物时的场景',
created_at: '2024-03-20T10:07:00Z',
function_name: 'generate_shot_sketch',
custom_data: {
shot_type: '中景',
atmosphere: '温馨、期待',
key_action: '艾米丽惊喜地打开礼物盒,小星缓缓启动',
url: 'https://picsum.photos/seed/shot1/600/400',
completed_count: 1,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 分镜生成 - 危机
{
id: 10,
role: 'system',
content: '第二个分镜:小星能源耗尽的场景',
created_at: '2024-03-20T10:08:00Z',
function_name: 'generate_shot_sketch',
custom_data: {
shot_type: '特写',
atmosphere: '紧张、担忧',
key_action: '小星的能源指示灯闪烁微弱,艾米丽神情焦急',
url: 'https://picsum.photos/seed/shot2/600/400',
completed_count: 2,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 分镜生成 - 解决
{
id: 11,
role: 'system',
content: '第三个分镜:找到新能源解决方案的场景',
created_at: '2024-03-20T10:09:00Z',
function_name: 'generate_shot_sketch',
custom_data: {
shot_type: '全景',
atmosphere: '欢欣、胜利',
key_action: '实验室中艾米丽成功激活新能源,小星重新焕发活力',
url: 'https://picsum.photos/seed/shot3/600/400',
completed_count: 3,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 分镜视频生成
{
id: 11.1,
role: 'system',
content: '分镜视频生成完成',
created_at: '2024-03-20T10:10:00Z',
function_name: 'generate_video',
custom_data: {
prompt_json: {
core_atmosphere: '欢欣、胜利',
},
urls: ['https://cdn.qikongjian.com/faces/1755798635_facefusion_output_1755798635.mp4'],
completed_count: 1,
total_count: 1
},
status: 'success',
intent_type: 'procedure'
},
// 用户反馈
{
id: 12,
role: 'user',
content: JSON.stringify([{
type: 'text',
content: '这个故事设计太棒了!特别喜欢艾米丽和小星的互动场景。'
}]),
created_at: '2024-03-20T10:10:00Z',
function_name: undefined,
custom_data: undefined,
status: 'success',
intent_type: 'function_call'
},
// 助手回复
{
id: 13,
role: 'assistant',
content: JSON.stringify([{
type: 'text',
content: '谢谢您的肯定!我们可以继续优化任何场景或角色设计,您觉得有什么地方需要调整吗?'
content: '🌟欢迎来到 MovieFlow 🎬✨\n快把您的创意告诉我吧💡\n我是您的专属AI小伙伴🤖可以帮您\n🎭 生成专属演员形象\n📽 搭建场景 & 分镜\n🎞 完成整部视频创作\n\n一起开启奇妙的创作之旅吧'
}]),
created_at: '2024-03-20T10:10:10Z',
created_at: new Date().toISOString(),
function_name: undefined,
custom_data: undefined,
status: 'success',
@ -494,13 +285,13 @@ export async function fetchMessages(
}
// 转换消息并按时间排序
// if (response.data.messages.length === 0) {
// return {
// messages: MOCK_MESSAGES.map(transformMessage),
// hasMore: false,
// totalCount: 0
// };
// }
if (response.data.messages.length === 0) {
return {
messages: EMPTY_MESSAGES.map(transformMessage),
hasMore: false,
totalCount: 0
};
}
return {
messages: response.data.messages
.map(transformMessage)

View File

@ -3,28 +3,46 @@
import '../pages/style/top-bar.css';
import { Button } from '@/components/ui/button';
import { GradientText } from '@/components/ui/gradient-text';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useTheme } from 'next-themes';
import {
Sun,
Moon,
User,
Settings,
Sparkles,
LogOut,
Bell,
PanelsLeftBottom
PanelsLeftBottom,
Library
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { useRouter } from 'next/navigation';
import React, { useRef, useEffect } from 'react';
export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onToggleSidebar: () => void }) {
const { theme, setTheme } = useTheme();
const router = useRouter();
const [isOpen, setIsOpen] = React.useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const currentUser = localStorage.getItem('currentUser');
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
menuRef.current &&
!menuRef.current.contains(event.target as Node) &&
buttonRef.current &&
!buttonRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
const handleAnimationEnd = (event: React.AnimationEvent<HTMLDivElement>) => {
const element = event.currentTarget;
@ -99,25 +117,91 @@ export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onT
</Button> */}
{/* User Menu */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<User className="h-4 w-4" />
<span className="ml-2">User</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Settings className="mr-2 h-4 w-4" />
Settings
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut className="mr-2 h-4 w-4" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div className="relative">
<Button
ref={buttonRef}
variant="ghost"
size="sm"
onClick={() => setIsOpen(!isOpen)}
data-alt="user-menu-trigger"
>
<User className="h-4 w-4" />
</Button>
<AnimatePresence>
{isOpen && (
<motion.div
ref={menuRef}
initial={{ opacity: 0, scale: 0.95, y: -20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: -20 }}
transition={{ duration: 0.2 }}
className="absolute right-0 mt-2 w-56 bg-[#1E1E1E] rounded-lg shadow-lg overflow-hidden z-50"
data-alt="user-menu-dropdown"
>
{/* User Info */}
<div className="p-4">
<div className="flex items-center space-x-3">
<div className="h-10 w-10 rounded-full bg-[#1E4D3E] flex items-center justify-center text-white font-semibold">
A
</div>
<div>
<p className="text-sm font-medium">admin-live</p>
<p className="text-xs text-gray-500">admin-live.com</p>
</div>
</div>
</div>
{/* AI Points */}
<div className="px-4 py-3 flex items-center justify-between">
<div className="flex items-center space-x-2">
<Sparkles className="h-4 w-4" />
<span className="text-white underline text-sm">100 </span>
</div>
<Button
variant="outline"
size="sm"
className="text-white border-white hover:bg-white/10 rounded-full px-8"
>
</Button>
</div>
{/* Menu Items */}
<div className="p-2">
<motion.button
whileHover={{ backgroundColor: 'rgba(255,255,255,0.1)' }}
className="w-full flex items-center space-x-2 px-3 py-2 rounded-md text-sm text-white"
onClick={() => router.push('/my-library')}
data-alt="my-library-button"
>
<Library className="h-4 w-4" />
<span></span>
</motion.button>
<motion.button
whileHover={{ backgroundColor: 'rgba(255,255,255,0.1)' }}
className="w-full flex items-center space-x-2 px-3 py-2 rounded-md text-sm text-white"
onClick={() => {
// 处理退出登录
setIsOpen(false);
}}
data-alt="logout-button"
>
<LogOut className="h-4 w-4" />
<span>退</span>
</motion.button>
{/* Footer */}
<div className="mt-4 px-3 py-2 text-xs text-gray-400 text-center">
<div> · </div>
<div>250819215404 | 2025/8/20 06:00:50</div>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
</div>