chatbox--完善流程推送 生成视频

This commit is contained in:
北枳 2025-08-23 21:26:44 +08:00
parent 65c21e1c40
commit 2a7a9f4701
6 changed files with 125 additions and 53 deletions

View File

@ -11,7 +11,7 @@ export function LoadMoreButton({ onClick, loading = false }: LoadMoreButtonProps
<button
onClick={onClick}
disabled={loading}
className="w-full flex items-center justify-center gap-2 py-2 text-sm text-gray-400 hover:text-gray-300 hover:bg-gray-800/30 transition-colors disabled:opacity-50"
className="w-full flex items-center justify-center gap-2 text-sm text-gray-400 hover:text-gray-300 hover:bg-gray-800/30 transition-colors disabled:opacity-50"
data-alt="load-more-button"
>
{loading ? (

View File

@ -106,7 +106,16 @@ export function MessageRenderer({ msg }: MessageRendererProps) {
case "video":
return (
<div key={idx} className="overflow-hidden rounded-xl">
<video controls src={b.url} poster={b.poster} className="w-full max-h-80 bg-black" />
<video
controls
src={b.url}
poster={b.poster}
className="w-full max-h-80 bg-black"
controlsList="nodownload noremoteplayback"
disablePictureInPicture
disableRemotePlayback
onContextMenu={e => e.preventDefault()}
/>
</div>
);
case "audio":

View File

@ -86,8 +86,8 @@ export default function SmartChatBox({ isSmartChatBoxOpen, setIsSmartChatBoxOpen
<span>Chat</span>
{/* System push toggle */}
<Switch
checkedChildren="系统推送开"
unCheckedChildren="系统推送关"
checkedChildren="系统推送开"
unCheckedChildren="系统推送关"
checked={systemPush}
onChange={toggleSystemPush}
className="ml-2"

View File

@ -13,7 +13,8 @@ import {
ScriptSummary,
CharacterGeneration,
SketchGeneration,
ShotSketchGeneration
ShotSketchGeneration,
ShotVideoGeneration
} from "./types";
import { post } from "@/api/request";
@ -28,7 +29,6 @@ const MOCK_MESSAGES: RealApiMessage[] = [
content: '我想拍一个关于一个小女孩和她的机器人朋友的故事,故事发生在未来世界。'
}]),
created_at: '2024-03-20T10:00:00Z',
message_type: undefined,
function_name: undefined,
custom_data: undefined,
status: 'success',
@ -40,7 +40,6 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system',
content: '我会帮您创建一个温馨感人的科幻短片,讲述人工智能与人类情感的故事。',
created_at: '2024-03-20T10:00:10Z',
message_type: 'project_init',
function_name: 'create_project',
custom_data: {
project_data: {
@ -56,7 +55,6 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system',
content: '故事概要在2045年的未来城市10岁的小女孩艾米丽收到了一个特别的生日礼物——一个具有高度情感智能的机器人伙伴"小星"。随着时间推移,他们建立了深厚的友谊。当小星因能源耗尽即将永久关闭时,艾米丽想尽办法寻找解决方案,最终通过她的坚持和创意,成功为小星找到了新的能源,让这段跨越人机界限的友谊得以延续。',
created_at: '2024-03-20T10:01:00Z',
message_type: 'script_summary',
function_name: 'generate_script_summary',
custom_data: {
summary: '一个关于友谊和希望的温暖故事'
@ -70,12 +68,11 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system',
content: '主角艾米丽的形象已生成',
created_at: '2024-03-20T10:02:00Z',
message_type: 'character_generation',
function_name: 'generate_character',
custom_data: {
character_name: '艾米丽',
image_path: 'https://picsum.photos/seed/emily/300/400',
count: 1,
completed_count: 1,
total_count: 2
},
status: 'success',
@ -87,12 +84,11 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system',
content: '机器人小星的形象已生成',
created_at: '2024-03-20T10:03:00Z',
message_type: 'character_generation',
function_name: 'generate_character',
custom_data: {
character_name: '小星',
image_path: 'https://picsum.photos/seed/robot/300/400',
count: 2,
completed_count: 2,
total_count: 2
},
status: 'success',
@ -104,12 +100,11 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system',
content: '未来城市场景设计完成',
created_at: '2024-03-20T10:04:00Z',
message_type: 'sketch_generation',
function_name: 'generate_sketch',
custom_data: {
sketch_name: '未来城市街景',
image_path: 'https://picsum.photos/seed/city/600/400',
count: 1,
completed_count: 1,
total_count: 3
},
status: 'success',
@ -121,12 +116,11 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system',
content: '艾米丽的未来风格卧室设计完成',
created_at: '2024-03-20T10:05:00Z',
message_type: 'sketch_generation',
function_name: 'generate_sketch',
custom_data: {
sketch_name: '艾米丽的卧室',
image_path: 'https://picsum.photos/seed/room/600/400',
count: 2,
completed_count: 2,
total_count: 3
},
status: 'success',
@ -138,12 +132,11 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system',
content: '高科技实验室场景设计完成',
created_at: '2024-03-20T10:06:00Z',
message_type: 'sketch_generation',
function_name: 'generate_sketch',
custom_data: {
sketch_name: '未来实验室',
image_path: 'https://picsum.photos/seed/lab/600/400',
count: 3,
completed_count: 3,
total_count: 3
},
status: 'success',
@ -155,14 +148,13 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system',
content: '第一个分镜:艾米丽收到礼物时的场景',
created_at: '2024-03-20T10:07:00Z',
message_type: 'shot_sketch_generation',
function_name: 'generate_shot_sketch',
custom_data: {
shot_type: '中景',
atmosphere: '温馨、期待',
key_action: '艾米丽惊喜地打开礼物盒,小星缓缓启动',
url: 'https://picsum.photos/seed/shot1/600/400',
count: 1,
completed_count: 1,
total_count: 3
},
status: 'success',
@ -174,14 +166,13 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system',
content: '第二个分镜:小星能源耗尽的场景',
created_at: '2024-03-20T10:08:00Z',
message_type: 'shot_sketch_generation',
function_name: 'generate_shot_sketch',
custom_data: {
shot_type: '特写',
atmosphere: '紧张、担忧',
key_action: '小星的能源指示灯闪烁微弱,艾米丽神情焦急',
url: 'https://picsum.photos/seed/shot2/600/400',
count: 2,
completed_count: 2,
total_count: 3
},
status: 'success',
@ -193,19 +184,34 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system',
content: '第三个分镜:找到新能源解决方案的场景',
created_at: '2024-03-20T10:09:00Z',
message_type: 'shot_sketch_generation',
function_name: 'generate_shot_sketch',
custom_data: {
shot_type: '全景',
atmosphere: '欢欣、胜利',
key_action: '实验室中艾米丽成功激活新能源,小星重新焕发活力',
url: 'https://picsum.photos/seed/shot3/600/400',
count: 3,
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: {
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,
@ -215,7 +221,6 @@ const MOCK_MESSAGES: RealApiMessage[] = [
content: '这个故事设计太棒了!特别喜欢艾米丽和小星的互动场景。'
}]),
created_at: '2024-03-20T10:10:00Z',
message_type: undefined,
function_name: undefined,
custom_data: undefined,
status: 'success',
@ -230,7 +235,6 @@ const MOCK_MESSAGES: RealApiMessage[] = [
content: '谢谢您的肯定!我们可以继续优化任何场景或角色设计,您觉得有什么地方需要调整吗?'
}]),
created_at: '2024-03-20T10:10:10Z',
message_type: undefined,
function_name: undefined,
custom_data: undefined,
status: 'success',
@ -250,15 +254,19 @@ function isScriptSummary(data: any): data is ScriptSummary {
}
function isCharacterGeneration(data: any): data is CharacterGeneration {
return data && 'character_name' in data && 'image_path' in data && 'count' in data && 'total_count' in data;
return data && 'character_name' in data && 'image_path' in data && 'completed_count' in data && 'total_count' in data;
}
function isSketchGeneration(data: any): data is SketchGeneration {
return data && 'sketch_name' in data && 'image_path' in data && 'count' in data && 'total_count' in data;
return data && 'sketch_name' in data && 'image_path' in data && 'completed_count' in data && 'total_count' in data;
}
function isShotSketchGeneration(data: any): data is ShotSketchGeneration {
return data && 'shot_type' in data && 'atmosphere' in data && 'key_action' in data && 'url' in data && 'count' in data && 'total_count' in data;
return data && 'shot_type' in data && 'atmosphere' in data && 'key_action' in data && 'url' in data && 'completed_count' in data && 'total_count' in data;
}
function isShotVideoGeneration(data: any): data is ShotVideoGeneration {
return data && 'core_atmosphere' in data && 'urls' in data && 'completed_count' in data && 'total_count' in data;
}
/**
@ -267,7 +275,7 @@ function isShotSketchGeneration(data: any): data is ShotSketchGeneration {
function transformSystemMessage(
functionName: FunctionName,
content: string,
customData: ProjectInit | ScriptSummary | CharacterGeneration | SketchGeneration | ShotSketchGeneration
customData: ProjectInit | ScriptSummary | CharacterGeneration | SketchGeneration | ShotSketchGeneration | ShotVideoGeneration
): MessageBlock[] {
let blocks: MessageBlock[] = [];
@ -303,9 +311,9 @@ function transformSystemMessage(
text: '图片中演员形象仅供参考,后续可根据视频生成后进行调整。'
}, {
type: 'progress',
value: customData.count,
value: customData.completed_count,
total: customData.total_count,
label: `已生成 ${customData.count} 个演员,剧本中共有 ${customData.total_count}`
label: `已生成 ${customData.completed_count} 个演员,剧本中共有 ${customData.total_count}`
}];
}
break;
@ -323,9 +331,9 @@ function transformSystemMessage(
text: '图片中场景仅供参考,后续可根据视频生成后进行调整。'
}, {
type: 'progress',
value: customData.count,
value: customData.completed_count,
total: customData.total_count,
label: `已生成 ${customData.count} 个场景,剧本中共有 ${customData.total_count}`
label: `已生成 ${customData.completed_count} 个场景,剧本中共有 ${customData.total_count}`
}];
}
break;
@ -343,9 +351,29 @@ function transformSystemMessage(
text: '图片中故事板静帧仅供参考,后续可根据视频生成后进行调整。'
}, {
type: 'progress',
value: customData.count,
value: customData.completed_count,
total: customData.total_count,
label: `已生成 ${customData.count} 个故事板静帧,剧本中共有 ${customData.total_count}`
label: `已生成 ${customData.completed_count} 个故事板静帧,剧本中共有 ${customData.total_count}`
}];
}
break;
case 'generate_video':
if (isShotVideoGeneration(customData)) {
blocks = [{
type: 'text',
text: `🎬 分镜视频生成 \n核心氛围${customData.core_atmosphere}`
}, {
type: 'video',
url: customData.urls[0] || ''
}, {
type: 'text',
text: '后续可在剪辑线上进行编辑。'
}, {
type: 'progress',
value: customData.completed_count,
total: customData.total_count,
label: `已生成 ${customData.completed_count} 个分镜视频,剧本中共有 ${customData.total_count}`
}];
}
break;

View File

@ -77,8 +77,7 @@ export interface MessagesResponse {
}
type ContentType = "text" | "image" | "video" | "audio";
export type MessageType = "project_init" | "script_summary" | "character_generation" | "sketch_generation" | "shot_sketch_generation";
export type FunctionName = "create_project" | "generate_script_summary" | "generate_character" | "generate_sketch" | "generate_shot_sketch";
export type FunctionName = "create_project" | "generate_script_summary" | "generate_character" | "generate_sketch" | "generate_shot_sketch" | "generate_video";
// 项目创建
export interface ProjectInit {
@ -94,14 +93,14 @@ export interface ScriptSummary {
export interface CharacterGeneration {
character_name: string; // 角色名称
image_path: string; // 角色图片
count: number; // 生成数量
completed_count: number; // 生成数量
total_count: number; // 总数量
}
// 场景草图生成
export interface SketchGeneration {
sketch_name: string; // 场景草图名称
image_path: string; // 场景草图图片
count: number; // 生成数量
completed_count: number; // 生成数量
total_count: number; // 总数量
}
// 分镜草图生成
@ -110,7 +109,14 @@ export interface ShotSketchGeneration {
atmosphere: string; // 氛围描述
key_action: string; // 关键动作描述
url: string; // 分镜草图图片
count: number; // 生成数量
completed_count: number; // 生成数量
total_count: number; // 总数量
}
// 分镜视频生成
export interface ShotVideoGeneration {
core_atmosphere: string; // 核心氛围
urls: string[]; // 分镜视频
completed_count: number; // 生成数量
total_count: number; // 总数量
}
@ -124,9 +130,8 @@ export interface RealApiMessage {
id: number;
role: Role;
content: string;
message_type?: MessageType;
function_name?: FunctionName;
custom_data?: ProjectInit | ScriptSummary | CharacterGeneration | SketchGeneration | ShotSketchGeneration;
custom_data?: ProjectInit | ScriptSummary | CharacterGeneration | SketchGeneration | ShotSketchGeneration | ShotVideoGeneration;
status: MessageStatus;
intent_type: IntentType;
}

View File

@ -23,14 +23,28 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
// 系统推送状态
const [systemPush, setSystemPush] = useState(true);
const systemPushDisabledTimeRef = useRef<number | null>(null);
// 状态引用
const configRef = useRef(config);
const isInitialLoadRef = useRef(true);
const timeoutIdRef = useRef<NodeJS.Timeout | null>(null);
const isPollingRef = useRef(false);
const isViewingHistoryRef = useRef(false);
const prevTotalCountRef = useRef(totalCount);
const isPollingRef = useRef(false);
// 过滤消息
const filterMessages = useCallback((messages: ChatMessage[]) => {
if (systemPush || !systemPushDisabledTimeRef.current) {
return messages;
}
// 系统推送关闭时,只显示关闭前的系统消息和所有非系统消息
return messages.filter(msg =>
msg.role !== 'system' ||
msg.createdAt <= systemPushDisabledTimeRef.current!
);
}, [systemPush]);
// 合并和去重消息
const mergeMessages = useCallback((oldMessages: ChatMessage[], newMessages: ChatMessage[]) => {
@ -42,9 +56,12 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
});
// 转回数组并按 id 排序
return Array.from(messageMap.values())
const merged = Array.from(messageMap.values())
.sort((a, b) => Number(a.id) - Number(b.id));
}, []);
// 过滤系统消息
return filterMessages(merged);
}, [filterMessages]);
// 更新 config 引用
useEffect(() => {
@ -68,9 +85,11 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
}
const response = await fetchMessages(configRef.current, 0, PAGE_SIZE);
setLatestMessages(response.messages);
const filteredMessages = filterMessages(response.messages);
setLatestMessages(response.messages); // 保存完整的消息列表
if (!isViewingHistoryRef.current) {
setDisplayMessages(response.messages);
setDisplayMessages(filteredMessages); // 显示过滤后的消息
}
setTotalCount(response.totalCount);
setHasMore(response.hasMore);
@ -83,7 +102,7 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
setIsLoading(false);
}
}
}, []);
}, [filterMessages]);
// 加载更多历史消息
const loadMoreMessages = useCallback(async () => {
@ -111,9 +130,9 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
// 返回最新消息
const backToLatest = useCallback(async () => {
isViewingHistoryRef.current = false;
setDisplayMessages(latestMessages);
setDisplayMessages(filterMessages(latestMessages));
onMessagesUpdate?.(true);
}, [latestMessages, onMessagesUpdate]);
}, [latestMessages, filterMessages, onMessagesUpdate]);
// 发送消息
const handleSendMessage = useCallback(async (blocks: MessageBlock[]) => {
@ -177,8 +196,19 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
// 系统推送开关
const toggleSystemPush = useCallback(() => {
setSystemPush(prev => !prev);
}, []);
setSystemPush(prev => {
if (prev) {
// 关闭系统推送时,记录当前时间
systemPushDisabledTimeRef.current = Date.now();
} else {
// 开启系统推送时,清除时间记录并更新显示
systemPushDisabledTimeRef.current = null;
// 立即更新显示的消息
setDisplayMessages(filterMessages(latestMessages));
}
return !prev;
});
}, [latestMessages, filterMessages]);
// 轮询获取最新消息
useEffect(() => {