feat: improve mindspace asset handling and local runtime paths
This commit is contained in:
@@ -48,7 +48,8 @@ const CHAT_PLACEHOLDER_PROMPTS = [
|
||||
type PendingChatImage = {
|
||||
id: string;
|
||||
url: string;
|
||||
local: boolean;
|
||||
previewUrl: string;
|
||||
uploading: boolean;
|
||||
};
|
||||
|
||||
function FileIcon() {
|
||||
@@ -87,7 +88,7 @@ export function ChatPanel({
|
||||
session: Session | null;
|
||||
capabilities?: CapabilityMap;
|
||||
grantedSkills?: string[];
|
||||
onSubmit: (text: string, imageUrls?: string[]) => void | Promise<void>;
|
||||
onSubmit: (text: string, imageUrls?: string[], previewImageUrls?: string[]) => void | Promise<void>;
|
||||
onUploadImage?: (file: File) => Promise<string>;
|
||||
onStop: () => void | Promise<void>;
|
||||
onApproveTool: (allow: boolean) => void | Promise<void>;
|
||||
@@ -124,7 +125,7 @@ export function ChatPanel({
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
pendingImagesRef.current.forEach((item) => {
|
||||
if (item.local) URL.revokeObjectURL(item.url);
|
||||
if (item.previewUrl.startsWith('blob:')) URL.revokeObjectURL(item.previewUrl);
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
@@ -183,17 +184,18 @@ export function ChatPanel({
|
||||
const imageAttachmentDisabled = !onUploadImage || busy || uploadingImage || offlineBlocked || !!pendingTool;
|
||||
|
||||
const revokePendingImage = (item: PendingChatImage) => {
|
||||
if (item.local) URL.revokeObjectURL(item.url);
|
||||
if (item.previewUrl.startsWith('blob:')) URL.revokeObjectURL(item.previewUrl);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const trimmed = input.trim();
|
||||
if ((!trimmed && pendingImages.length === 0) || voiceDisabled) return;
|
||||
if (uploadingImage || pendingImages.some((item) => item.local)) {
|
||||
if (uploadingImage || pendingImages.some((item) => item.uploading)) {
|
||||
setImageError('图片仍在上传中,请稍候再发送');
|
||||
return;
|
||||
}
|
||||
const imagesToSend = pendingImages.map((item) => item.url);
|
||||
const previewImagesToSend = pendingImages.map((item) => item.previewUrl);
|
||||
const sentImages = [...pendingImages];
|
||||
|
||||
suppressVoiceUpdateRef.current = true;
|
||||
@@ -205,7 +207,7 @@ export function ChatPanel({
|
||||
setVoiceNotice(null);
|
||||
setImageError(null);
|
||||
try {
|
||||
await onSubmit(trimmed, imagesToSend);
|
||||
await onSubmit(trimmed, imagesToSend, previewImagesToSend);
|
||||
sentImages.forEach(revokePendingImage);
|
||||
setPendingImages([]);
|
||||
} catch (err) {
|
||||
@@ -245,8 +247,9 @@ export function ChatPanel({
|
||||
|
||||
const placeholders = toUpload.map((file, index) => ({
|
||||
id: `${Date.now()}-${index}-${file.name}`,
|
||||
url: URL.createObjectURL(file),
|
||||
local: true,
|
||||
previewUrl: URL.createObjectURL(file),
|
||||
url: '',
|
||||
uploading: true,
|
||||
}));
|
||||
setPendingImages((prev) => [...prev, ...placeholders]);
|
||||
|
||||
@@ -258,8 +261,7 @@ export function ChatPanel({
|
||||
const next = prev.map((item) => {
|
||||
const index = placeholderIds.indexOf(item.id);
|
||||
if (index < 0 || !uploaded[index]) return item;
|
||||
revokePendingImage(item);
|
||||
return { ...item, url: uploaded[index], local: false };
|
||||
return { ...item, url: uploaded[index], uploading: false };
|
||||
});
|
||||
const seen = new Set<string>();
|
||||
return next.filter((item) => {
|
||||
@@ -310,8 +312,9 @@ export function ChatPanel({
|
||||
|
||||
const placeholders = toUpload.map((file, index) => ({
|
||||
id: `${Date.now()}-${index}-${file.name || 'pasted-image'}`,
|
||||
url: URL.createObjectURL(file),
|
||||
local: true,
|
||||
previewUrl: URL.createObjectURL(file),
|
||||
url: '',
|
||||
uploading: true,
|
||||
}));
|
||||
setPendingImages((prev) => [...prev, ...placeholders]);
|
||||
|
||||
@@ -323,8 +326,7 @@ export function ChatPanel({
|
||||
const next = prev.map((item) => {
|
||||
const index = placeholderIds.indexOf(item.id);
|
||||
if (index < 0 || !uploaded[index]) return item;
|
||||
revokePendingImage(item);
|
||||
return { ...item, url: uploaded[index], local: false };
|
||||
return { ...item, url: uploaded[index], uploading: false };
|
||||
});
|
||||
const seen = new Set<string>();
|
||||
return next.filter((item) => {
|
||||
@@ -419,8 +421,8 @@ export function ChatPanel({
|
||||
<div className="chat-image-attachment-grid">
|
||||
{pendingImages.map((item, index) => (
|
||||
<div key={item.id} className="chat-image-attachment-item">
|
||||
<a href={item.url} target="_blank" rel="noreferrer">
|
||||
<img src={item.url} alt={`上传图片 ${index + 1}`} className="chat-image-attachment-thumb" />
|
||||
<a href={item.url || item.previewUrl} target="_blank" rel="noreferrer">
|
||||
<img src={item.previewUrl} alt={`上传图片 ${index + 1}`} className="chat-image-attachment-thumb" />
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user