diff --git a/video-gen-app/src/pages/GenerateConver.tsx b/video-gen-app/src/pages/GenerateConver.tsx index c7506169..2f339269 100644 --- a/video-gen-app/src/pages/GenerateConver.tsx +++ b/video-gen-app/src/pages/GenerateConver.tsx @@ -1,912 +1,187 @@ -/** - * AI对话页面组件 - * - * 功能说明: - * - 提供AI聊天界面,支持创建对话、发送消息、上传图片/视频、删除对话等功能 - * - 支持媒体类型选择(图片/视频)和生成数量选择(1-10) - * - 消息列表自动滚动到底部 - * - 响应式侧边栏(可收起/展开) - * - * 技术栈:React + TypeScript + Ant Design - * - * 页面结构: - * - 左侧:对话列表侧边栏 - * - 右侧:消息展示区 + 输入区域 - * - * @component AIChatPage - * @returns {React.ReactElement} AI聊天页面组件 - */ -import React, { useState, useRef, useEffect } from 'react'; - -// Ant Design组件导入 +import React, { useState } from 'react'; import { - Layout, // 布局组件 - Button, // 按钮组件 - Input, // 输入框组件 - Select, // 选择器组件 - Upload, // 文件上传组件 - message, // 消息提示组件 - Space, // 间距组件 - Typography, // 排版组件 - Tooltip, // 提示组件 - Popconfirm, // 确认弹窗组件 + Layout, + Button, + Input, + Select, + Upload, + message, + Space, + Typography, } from 'antd'; - -// Ant Design图标导入 import { - PlusOutlined, // 加号图标(新建对话) - MenuUnfoldOutlined, // 展开菜单图标 - MenuFoldOutlined, // 收起菜单图标 - SendOutlined, // 发送图标 - DeleteOutlined, // 删除图标 - RobotOutlined, // 机器人图标(AI头像) - LoadingOutlined, // 加载图标 - PictureOutlined, // 图片图标 - VideoCameraOutlined, // 视频图标 + PlusOutlined, + MenuUnfoldOutlined, + MenuFoldOutlined, + SendOutlined, + UploadOutlined, } from '@ant-design/icons'; -// 解构Layout组件 const { Header, Sider, Content } = Layout; -// 解构Input组件 const { TextArea } = Input; -// 解构Select组件 const { Option } = Select; -// 解构Typography组件 const { Text } = Typography; -/** - * 消息类型定义 - * @interface Message - * @property {string} id - 消息唯一标识 - * @property {'user' | 'bot'} type - 消息发送者类型 - * @property {string} content - 消息文本内容 - * @property {string} timestamp - 消息发送时间 - * @property {string[]} [images] - 消息附带的图片URL列表(可选) - */ -interface Message { - id: string; - type: 'user' | 'bot'; - content: string; - timestamp: string; - images?: string[]; -} - -/** - * 对话类型定义 - * @interface Conversation - * @property {string} id - 对话唯一标识 - * @property {string} title - 对话标题 - * @property {string} lastMessage - 最后一条消息预览 - * @property {string} timestamp - 最后消息时间 - * @property {Message[]} messages - 消息列表 - */ -interface Conversation { - id: string; - title: string; - lastMessage: string; - timestamp: string; - messages: Message[]; -} - -/** - * 模拟文件上传函数 - * @param {File} file - 要上传的文件对象 - * @returns {Promise<{ url: string }>} 返回包含文件URL的Promise - * @description 模拟真实的文件上传过程,延迟500ms后返回文件的Object URL - */ -const mockUpload = (file: File): Promise<{ url: string }> => { - return new Promise((resolve) => { - setTimeout(() => { - // 使用URL.createObjectURL创建本地文件预览URL - const url = URL.createObjectURL(file); - resolve({ url }); - }, 500); - }); -}; - -/** - * 模拟AI回复函数 - * @param {string} content - 用户输入的内容 - * @returns {Promise} 返回AI回复内容的Promise - * @description 模拟AI响应过程,延迟1500ms后随机返回一条预设回复 - */ -const mockAIResponse = (content: string): Promise => { - return new Promise((resolve) => { - setTimeout(() => { - const responses = [ - `好的,我来帮您创作关于"${content}"的内容...`, - `您的想法很有趣!关于"${content}",我有以下建议:`, - `收到!正在为您生成"${content}"相关的内容...`, - `太棒了!"${content}"是一个很棒的主题,让我来帮您实现。`, - ]; - // 随机选择一条回复 - resolve(responses[Math.floor(Math.random() * responses.length)]); - }, 1500); - }); -}; - -/** - * AI聊天页面主组件 - * @function AIChatPage - * @returns {React.ReactElement} AI聊天页面 - */ const AIChatPage: React.FC = () => { - // ==================== 状态定义 ==================== - - /** - * 侧边栏收起/展开状态 - * @state {boolean} collapsed - true表示收起,false表示展开 - */ + // 侧边栏收起/展开状态 const [collapsed, setCollapsed] = useState(false); - - /** - * 当前选中的对话ID - * @state {string | null} currentConversationId - 当前活跃对话的ID,null表示未选中任何对话 - */ - const [currentConversationId, setCurrentConversationId] = useState(null); - - /** - * 对话列表 - * @state {Conversation[]} conversations - 存储所有对话数据,包含初始模拟数据 - */ - const [conversations, setConversations] = useState([ - { - id: '1', - title: '女生产品文案', - lastMessage: '女生拿着这个产品', - timestamp: '2026-05-26 09:41:07', - messages: [ - { - id: 'm1', - type: 'user', - content: '女生拿着这个产品', - timestamp: '2026-05-26 09:41:07', - images: ['https://neeko-copilot.bytedance.net/api/text_to_image?prompt=woman%20holding%20luxury%20cream%20jar%20elegant%20bathroom&image_size=portrait_4_3'], - }, - { - id: 'm2', - type: 'bot', - content: '好的,我来为您生成关于这个产品的创意内容...', - timestamp: '2026-05-26 09:41:10', - }, - ], - }, - { - id: '2', - title: '旅行视频脚本', - lastMessage: '帮我写一个旅行vlog脚本', - timestamp: '2026-05-25 14:30:22', - messages: [ - { - id: 'm3', - type: 'user', - content: '帮我写一个旅行vlog脚本', - timestamp: '2026-05-25 14:30:22', - }, - { - id: 'm4', - type: 'bot', - content: '当然!我来帮您构思一个精彩的旅行vlog脚本...', - timestamp: '2026-05-25 14:30:25', - }, - ], - }, - ]); - - /** - * 输入框内容 - * @state {string} inputValue - 用户在文本输入框中输入的内容 - */ + // 输入框内容 const [inputValue, setInputValue] = useState(''); - - /** - * 媒体类型选择(图片/视频) - * @state {string} mediaType - 'image'表示图片,'video'表示视频 - */ + // 第一个选择器:文件类型(图片/视频) const [mediaType, setMediaType] = useState('image'); - - /** - * 生成数量选择(1-10) - * @state {string} countType - 生成内容的数量,值为'1'到'10' - */ + // 第二个选择器:1/2/3 const [countType, setCountType] = useState('1'); - - /** - * 文件上传状态 - * @state {boolean} uploading - true表示正在上传文件 - */ - const [uploading, setUploading] = useState(false); - - /** - * AI响应加载状态 - * @state {boolean} loading - true表示AI正在生成回复 - */ - const [loading, setLoading] = useState(false); - - /** - * 当前上传的图片URL列表 - * @state {string[]} currentImages - 存储当前会话中待发送的图片URL - */ - const [currentImages, setCurrentImages] = useState([]); - // ==================== 引用定义 ==================== - - /** - * 消息列表底部引用,用于自动滚动 - * @ref {HTMLDivElement | null} messagesEndRef - 指向消息列表最后一个元素 - */ - const messagesEndRef = useRef(null); - - // ==================== 派生数据 ==================== - - /** - * 获取当前选中的对话对象 - * @const {Conversation | undefined} currentConversation - 当前活跃对话数据 - */ - const currentConversation = conversations.find((c) => c.id === currentConversationId); - - // ==================== 副作用 ==================== - - /** - * 自动滚动到底部 - * @effect 当消息列表更新时,自动滚动到最新消息位置 - * @dependency {currentConversation?.messages} - 监听消息列表变化 - */ - useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [currentConversation?.messages]); - - // ==================== 事件处理函数 ==================== - - /** - * 创建新对话 - * @function handleNewChat - * @description 创建一个新的空对话,并切换到该对话 - */ + // 模拟“新对话”点击 const handleNewChat = () => { - // 生成唯一ID(使用时间戳) - const newId = Date.now().toString(); - const newConversation: Conversation = { - id: newId, - title: '新对话', - lastMessage: '', - timestamp: new Date().toLocaleString('zh-CN'), - messages: [], - }; - // 将新对话插入到列表顶部 - setConversations((prev) => [newConversation, ...prev]); - // 切换到新对话 - setCurrentConversationId(newId); - // 清空输入框和已上传图片 + message.info('开启新对话'); setInputValue(''); - setCurrentImages([]); - // 显示提示消息 - message.info('已开启新对话'); }; - /** - * 删除对话 - * @function handleDeleteChat - * @param {string} conversationId - 要删除的对话ID - * @description 删除指定对话,并自动切换到其他对话 - */ - const handleDeleteChat = (conversationId: string) => { - // 过滤掉要删除的对话 - setConversations((prev) => prev.filter((c) => c.id !== conversationId)); - // 如果删除的是当前选中的对话,切换到其他对话 - if (currentConversationId === conversationId) { - setCurrentConversationId( - conversations[0]?.id === conversationId - ? conversations[1]?.id || null - : conversations[0]?.id || null - ); - } - // 显示成功提示 - message.success('对话已删除'); - }; - - /** - * 选择对话 - * @function handleSelectChat - * @param {string} conversationId - 要选择的对话ID - * @description 切换到指定对话,清空已上传图片 - */ - const handleSelectChat = (conversationId: string) => { - setCurrentConversationId(conversationId); - setCurrentImages([]); - }; - - /** - * 发送消息 - * @function handleSend - * @async - * @description 发送用户消息,等待AI回复,并更新对话状态 - */ - const handleSend = async () => { - // 验证:必须有内容或图片 - if (!inputValue.trim() && currentImages.length === 0) { - message.warning('请输入内容或上传图片'); + // 发送消息 + const handleSend = () => { + if (!inputValue.trim()) { + message.warning('请输入内容'); return; } - - // 创建用户消息对象 - const newMessage: Message = { - id: `m${Date.now()}`, - type: 'user', - content: inputValue.trim(), - timestamp: new Date().toLocaleString('zh-CN'), - images: currentImages.length > 0 ? [...currentImages] : undefined, - }; - - // 更新对话列表,添加用户消息 - setConversations((prev) => - prev.map((c) => - c.id === currentConversationId - ? { - ...c, - messages: [...c.messages, newMessage], - lastMessage: inputValue.trim() || '[图片]', - timestamp: new Date().toLocaleString('zh-CN'), - // 如果是新对话,使用第一条消息作为标题 - title: c.title === '新对话' && inputValue.trim() - ? inputValue.trim().substring(0, 20) - : c.title, - } - : c - ) - ); - - // 清空输入框和已上传图片 + console.log('发送消息:', inputValue, mediaType, countType); + message.success('消息已发送'); setInputValue(''); - setCurrentImages([]); - // 设置加载状态 - setLoading(true); - - try { - // 调用模拟AI回复 - const response = await mockAIResponse(inputValue.trim() || '图片请求'); - // 创建AI回复消息对象 - const botMessage: Message = { - id: `m${Date.now() + 1}`, - type: 'bot', - content: response, - timestamp: new Date().toLocaleString('zh-CN'), - }; - - // 更新对话列表,添加AI回复 - setConversations((prev) => - prev.map((c) => - c.id === currentConversationId - ? { - ...c, - messages: [...c.messages, botMessage], - lastMessage: response.substring(0, 30) + (response.length > 30 ? '...' : ''), - timestamp: new Date().toLocaleString('zh-CN'), - } - : c - ) - ); - } catch (error) { - // 处理错误 - message.error('发送失败,请重试'); - } finally { - // 无论成功与否,取消加载状态 - setLoading(false); - } }; - /** - * 文件上传处理 - * @function handleUpload - * @async - * @param {File} file - 要上传的文件 - * @returns {false} - 返回false阻止自动上传,由自定义逻辑处理 - * @description 验证文件类型和大小,上传文件并添加到当前图片列表 - */ - const handleUpload = async (file: File) => { - // 验证文件类型 - const isImage = file.type.startsWith('image/'); - const isVideo = file.type.startsWith('video/'); - - if (!isImage && !isVideo) { - message.error('仅支持图片或视频文件'); - return false; + // 文件上传前校验(仅示意) + const beforeUpload = (file: File) => { + const isImageOrVideo = file.type.startsWith('image/') || file.type.startsWith('video/'); + if (!isImageOrVideo) { + message.error('只能上传图片或视频文件'); } - - // 验证文件大小 - const maxMB = isVideo ? 100 : 10; - if (file.size / 1024 / 1024 > maxMB) { - message.error(`${isVideo ? '视频' : '图片'}大小不能超过${maxMB}MB`); - return false; - } - - // 验证图片数量(最多4张) - const imageCount = currentImages.filter((_, i) => i < 4).length; - if (imageCount >= 4) { - message.error('最多上传4张图片'); - return false; - } - - // 设置上传状态 - setUploading(true); - - try { - // 调用模拟上传 - const res = await mockUpload(file); - // 添加到图片列表 - setCurrentImages((prev) => [...prev, res.url]); - message.success(`${isImage ? '图片' : '视频'}上传成功`); - } catch (error) { - message.error('上传失败'); - } finally { - setUploading(false); - } - - // 返回false阻止Ant Design的自动上传行为 - return false; + return isImageOrVideo; }; - /** - * 移除已上传的图片 - * @function handleRemoveImage - * @param {number} index - 要移除的图片索引 - * @description 从当前图片列表中移除指定索引的图片 - */ - const handleRemoveImage = (index: number) => { - setCurrentImages((prev) => prev.filter((_, i) => i !== index)); - }; - - /** - * 按回车键发送 - * @function handleKeyPress - * @param {React.KeyboardEvent} e - 键盘事件对象 - * @description 监听回车键,非Shift+Enter时发送消息 - */ - const handleKeyPress = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - handleSend(); - } - }; - - // ==================== 渲染 ==================== - return ( - - {/* 左侧边栏 - 对话列表 */} + + {/* 左侧边栏 */} -
- {/* 收起/展开按钮 */} +
)} - - {/* 对话列表 - 展开状态显示 */} - {!collapsed && conversations.length > 0 && ( -
- {conversations.map((conversation) => ( -
handleSelectChat(conversation.id)} - style={{ - display: 'flex', - alignItems: 'center', - padding: '10px 12px', - marginBottom: 4, - borderRadius: 8, - cursor: 'pointer', - background: currentConversationId === conversation.id ? '#f0f5ff' : 'transparent', - border: currentConversationId === conversation.id ? '1px solid #e0e8ff' : 'none', - transition: 'all 0.2s', - }} - onMouseEnter={(e) => { - e.currentTarget.style.background = currentConversationId === conversation.id ? '#f0f5ff' : '#fafafa'; - }} - onMouseLeave={(e) => { - e.currentTarget.style.background = currentConversationId === conversation.id ? '#f0f5ff' : 'transparent'; - }} - > - {/* 对话信息区域 */} -
-

- {conversation.title} -

-

- {conversation.lastMessage || '暂无消息'} -

-
- {/* 删除按钮 */} - handleDeleteChat(conversation.id)} - okText="确定" - cancelText="取消" - > -
- ))} -
- )}
{/* 主内容区 */} - - {/* 头部 - 显示对话标题和模型信息 */} +
-
- - {currentConversation?.title || '开启创作'} - -
-
- 模型: Gemini 3 Pro - 比例: 9:16 - 消耗 15 积分 -
+ 开启创作
- {/* 消息区域 */} - {/* 空状态 - 未选择对话时显示 */} - {!currentConversation && ( -
-
+

你好,想创作什么?

+
+ + {/* 底部输入区域 */} +
+
+ {/* 文件上传按钮 */} + - -
-

- 你好,想创作什么? -

-

- 输入想法、剧本或上传参考,开始你的创作之旅 -

+