fix: show claw image upload progress

This commit is contained in:
134 2026-06-28 14:47:05 +08:00
parent 36d575b825
commit d37cb6cffc

View File

@ -1116,6 +1116,14 @@ type CanvasTypewriterTextProps = {
onComplete?: (typingKey: string) => void;
};
type CanvasComposerUploadProgress = {
title: string;
message: string;
current: number;
total: number;
fileName?: string;
};
type MarkdownTableBlockRange = {
start: number;
end: number;
@ -1353,6 +1361,60 @@ function CanvasAssistantTypingPlaceholder({ hint }: { hint?: string | null }) {
);
}
function CanvasComposerUploadProgressDialog({
progress,
}: {
progress: CanvasComposerUploadProgress | null;
}) {
if (!progress) return null;
const percent =
progress.total > 1
? Math.max(
8,
Math.min(100, Math.round((progress.current / progress.total) * 100))
)
: 100;
const isSingleFile = progress.total <= 1;
return createPortal(
<div className="fixed inset-0 z-[1000] flex items-center justify-center bg-black/35 px-4 backdrop-blur-[2px]">
<div className="w-full max-w-[360px] rounded-[22px] border border-white/12 bg-[#111215]/95 p-5 text-white shadow-2xl">
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-white/10 text-[#e0ff3a]">
<Loader2 className="h-5 w-5 animate-spin" />
</div>
<div className="min-w-0 flex-1">
<div className="text-[15px] font-semibold leading-5">
{progress.title}
</div>
{progress.fileName ? (
<div className="mt-1 truncate text-[12px] leading-5 text-zinc-400">
{progress.fileName}
</div>
) : null}
</div>
</div>
<div className="mt-4 h-1.5 overflow-hidden rounded-full bg-white/10">
<div
className={cn(
"h-full rounded-full bg-[#e0ff3a] transition-all duration-300",
isSingleFile && "animate-pulse"
)}
style={{ width: `${percent}%` }}
/>
</div>
<div className="mt-3 text-[12px] leading-5 text-zinc-400">
{progress.message}
</div>
</div>
</div>,
document.body
);
}
function normalizeCanvasAgentText(value: string): string {
return String(value || "")
.replace(/\r\n/g, "\n")
@ -1941,6 +2003,8 @@ export function CanvasChatPanel({
} | null>(null);
const [downloadProgress, setDownloadProgress] =
useState<CanvasImageDownloadProgress | null>(null);
const [composerUploadProgress, setComposerUploadProgress] =
useState<CanvasComposerUploadProgress | null>(null);
const [downloadingOriginalUrl, setDownloadingOriginalUrl] = useState("");
const [addingClawdImageUrl, setAddingClawdImageUrl] = useState("");
const downloadAbortControllerRef = useRef<AbortController | null>(null);
@ -10641,30 +10705,45 @@ export function CanvasChatPanel({
if (isClawdMode) {
let handledCount = 0;
for (const file of files) {
const uploaded = await uploadImageFileForComposerAttachment(file);
if (!uploaded) continue;
try {
for (const [index, file] of files.entries()) {
setComposerUploadProgress({
title:
files.length === 1
? "正在上传图片"
: `正在上传 ${files.length} 张图片`,
message: `正在上传第 ${index + 1}/${files.length} 张,图片较大时可能需要一点时间。`,
current: index + 1,
total: files.length,
fileName: file.name,
});
handledCount += 1;
setAttachments((prev) => {
const newIndex = prev.length;
setTimeout(() => {
insertImageTagInEditor(uploaded.imageUrl, uploaded.name, newIndex);
(textareaRef.current as HTMLDivElement | null)?.focus();
}, 0);
const uploaded = await uploadImageFileForComposerAttachment(file);
if (!uploaded) continue;
return [
...prev,
{
url: uploaded.imageUrl,
name: uploaded.name,
contentType: uploaded.contentType,
assetId: uploaded.assetId,
width: uploaded.width,
height: uploaded.height,
},
];
});
handledCount += 1;
setAttachments((prev) => {
const newIndex = prev.length;
setTimeout(() => {
insertImageTagInEditor(uploaded.imageUrl, uploaded.name, newIndex);
(textareaRef.current as HTMLDivElement | null)?.focus();
}, 0);
return [
...prev,
{
url: uploaded.imageUrl,
name: uploaded.name,
contentType: uploaded.contentType,
assetId: uploaded.assetId,
width: uploaded.width,
height: uploaded.height,
},
];
});
}
} finally {
setComposerUploadProgress(null);
}
if (handledCount > 0) {
@ -10887,30 +10966,47 @@ export function CanvasChatPanel({
}
let handledImageCount = 0;
for (const file of imageFiles) {
const uploaded = await uploadImageFileForComposerAttachment(file);
if (!uploaded) continue;
if (imageFiles.length > 0) {
try {
for (const [index, file] of imageFiles.entries()) {
setComposerUploadProgress({
title:
imageFiles.length === 1
? "正在上传图片"
: `正在上传 ${imageFiles.length} 张图片`,
message: `正在上传第 ${index + 1}/${imageFiles.length} 张,图片较大时可能需要一点时间。`,
current: index + 1,
total: imageFiles.length,
fileName: file.name,
});
handledImageCount += 1;
setAttachments((prev) => {
const newIndex = prev.length;
setTimeout(() => {
insertImageTagInEditor(uploaded.imageUrl, uploaded.name, newIndex);
(textareaRef.current as HTMLDivElement | null)?.focus();
}, 0);
const uploaded = await uploadImageFileForComposerAttachment(file);
if (!uploaded) continue;
return [
...prev,
{
url: uploaded.imageUrl,
name: uploaded.name,
contentType: uploaded.contentType,
assetId: uploaded.assetId,
width: uploaded.width,
height: uploaded.height,
},
];
});
handledImageCount += 1;
setAttachments((prev) => {
const newIndex = prev.length;
setTimeout(() => {
insertImageTagInEditor(uploaded.imageUrl, uploaded.name, newIndex);
(textareaRef.current as HTMLDivElement | null)?.focus();
}, 0);
return [
...prev,
{
url: uploaded.imageUrl,
name: uploaded.name,
contentType: uploaded.contentType,
assetId: uploaded.assetId,
width: uploaded.width,
height: uploaded.height,
},
];
});
}
} finally {
setComposerUploadProgress(null);
}
}
if (handledImageCount > 0) {
@ -11200,7 +11296,9 @@ export function CanvasChatPanel({
);
const isLoading = status === 'streaming' || status === 'submitted';
const isSubmitting = isLoading || isDetailGenerating || isClawdSubmitting;
const isComposerUploading = Boolean(composerUploadProgress);
const isSubmitting =
isLoading || isDetailGenerating || isClawdSubmitting || isComposerUploading;
const hasInputOrAttachments = Boolean(
input.trim() || attachments.length > 0 || (!isDetailGenerationMode && selectedSkillTag)
);
@ -12851,6 +12949,7 @@ export function CanvasChatPanel({
onCancel={handleCancelOriginalImageDownload}
progress={downloadProgress}
/>
<CanvasComposerUploadProgressDialog progress={composerUploadProgress} />
{!isWorkspacePresentation && shouldShowCanvasBanner ? (
<div className="px-[10px] pb-0">