feat: polish workflow studio and workspace assets
This commit is contained in:
@@ -101,6 +101,11 @@ const WELCOME_MESSAGE: AgentMessage = {
|
||||
'你好,我是视频助手。把脚本、分镜或角色想法发给我;确认后我会分步生成角色四视图、场景图、TTS 和 ComfyUI 工作流。',
|
||||
};
|
||||
|
||||
const MAX_ATTACHMENT_COUNT = 8;
|
||||
const MAX_INLINE_IMAGE_BYTES = 12 * 1024 * 1024;
|
||||
const MAX_INLINE_TOTAL_BYTES = 36 * 1024 * 1024;
|
||||
const MAX_ATTACHMENT_BYTES = 64 * 1024 * 1024;
|
||||
|
||||
const QUICK_PROMPTS = [
|
||||
'帮我把这个创意拆成角色、场景和镜头节奏',
|
||||
'开始制作,先生成角色和场景,不要一次输出工作流',
|
||||
@@ -308,6 +313,18 @@ function fileExtension(name: string): string {
|
||||
return name.split('.').pop()?.trim().toLowerCase() || '';
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number): string {
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
let value = bytes;
|
||||
let unitIndex = 0;
|
||||
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||
value /= 1024;
|
||||
unitIndex += 1;
|
||||
}
|
||||
return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
|
||||
}
|
||||
|
||||
function isTextAttachment(file: File, mimeType: string): boolean {
|
||||
if (mimeType.startsWith('text/')) return true;
|
||||
if (['application/json', 'application/x-ndjson', 'application/xml'].includes(mimeType)) {
|
||||
@@ -400,10 +417,35 @@ async function fileToAttachment(file: File): Promise<AgentAttachment> {
|
||||
mediaKind,
|
||||
};
|
||||
|
||||
if (mimeType.startsWith('image/') || mimeType.startsWith('video/') || mimeType.startsWith('audio/')) {
|
||||
if (mimeType.startsWith('image/')) {
|
||||
if (file.size > MAX_INLINE_IMAGE_BYTES) {
|
||||
return {
|
||||
...base,
|
||||
text: [
|
||||
`[图片素材: ${file.name}]`,
|
||||
`类型: ${mimeType}`,
|
||||
`大小: ${formatBytes(file.size)}`,
|
||||
`图片超过 ${formatBytes(MAX_INLINE_IMAGE_BYTES)},已作为引用记录,未内联发送原图数据。`,
|
||||
'如需精确参考,请压缩图片或上传较小版本。',
|
||||
].join('\n'),
|
||||
};
|
||||
}
|
||||
return { ...base, dataUrl: await fileToDataUrl(file) };
|
||||
}
|
||||
|
||||
if (mimeType.startsWith('video/') || mimeType.startsWith('audio/')) {
|
||||
return {
|
||||
...base,
|
||||
text: [
|
||||
`[${mimeType.startsWith('video/') ? '视频' : '音频'}素材: ${file.name}]`,
|
||||
`类型: ${mimeType}`,
|
||||
`大小: ${formatBytes(file.size)}`,
|
||||
'当前版本会记录该素材引用和用途说明,不会把完整二进制内容塞进对话请求。',
|
||||
'请在输入框说明需要参考的镜头、节奏、音色或时间段;后续接入服务端解析后会自动提取媒体信息。',
|
||||
].join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
if (isDocumentAttachment(file, mimeType) && !isTextAttachment(file, mimeType)) {
|
||||
return {
|
||||
...base,
|
||||
@@ -1236,8 +1278,29 @@ export default function ComfyUIAgent() {
|
||||
if (!files?.length) return;
|
||||
setReadingAttachments(true);
|
||||
try {
|
||||
const next = await Promise.all(Array.from(files).slice(0, 8).map(fileToAttachment));
|
||||
const withReferences = assignAttachmentReferences([...attachments, ...next].slice(0, 8));
|
||||
const selectedFiles = Array.from(files).slice(0, MAX_ATTACHMENT_COUNT);
|
||||
const oversized = selectedFiles.filter((file) => file.size > MAX_ATTACHMENT_BYTES);
|
||||
if (oversized.length > 0) {
|
||||
toast.error(`附件过大:${oversized[0].name} 超过 ${formatBytes(MAX_ATTACHMENT_BYTES)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const existingInlineBytes = attachments.reduce((sum, attachment) => (
|
||||
sum + (attachment.dataUrl ? attachment.size : 0)
|
||||
), 0);
|
||||
const nextInlineBytes = selectedFiles.reduce((sum, file) => (
|
||||
sum + (file.type.startsWith('image/') && file.size <= MAX_INLINE_IMAGE_BYTES ? file.size : 0)
|
||||
), 0);
|
||||
if (existingInlineBytes + nextInlineBytes > MAX_INLINE_TOTAL_BYTES) {
|
||||
toast.error(`图片附件总量超过 ${formatBytes(MAX_INLINE_TOTAL_BYTES)},请减少数量或压缩后再上传`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (files.length > MAX_ATTACHMENT_COUNT || attachments.length + selectedFiles.length > MAX_ATTACHMENT_COUNT) {
|
||||
toast.info(`最多保留 ${MAX_ATTACHMENT_COUNT} 个附件,超出的部分已忽略。`);
|
||||
}
|
||||
const next = await Promise.all(selectedFiles.map(fileToAttachment));
|
||||
const withReferences = assignAttachmentReferences([...attachments, ...next].slice(0, MAX_ATTACHMENT_COUNT));
|
||||
setAttachments(withReferences);
|
||||
if (next.some((attachment) => isDocumentAttachment(
|
||||
new File([], attachment.name, { type: attachment.mimeType }),
|
||||
|
||||
Reference in New Issue
Block a user