refactor(app): introduce attachment storage layer and draft lifecycle management
Replace inline data-URL image handling with a persistent attachment storage service, add draft lifecycle states (active/abandoned/sent) with generation tracking, and update fetchAgent to return project placement alongside agent snapshots.
This commit is contained in:
@@ -2,6 +2,10 @@ import { useState, useRef, useEffect } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import type { ImageAttachment } from "@/components/message-input";
|
||||
import { getCurrentTauriWindow, getTauri } from "@/utils/tauri";
|
||||
import {
|
||||
persistAttachmentFromBlob,
|
||||
persistAttachmentFromFileUri,
|
||||
} from "@/attachments/service";
|
||||
|
||||
interface UseFileDropZoneOptions {
|
||||
onFilesDropped: (files: ImageAttachment[]) => void;
|
||||
@@ -66,34 +70,17 @@ function isImagePath(path: string): boolean {
|
||||
return getFileExtension(path) in IMAGE_MIME_BY_EXTENSION;
|
||||
}
|
||||
|
||||
function filePathToImageAttachment(path: string): ImageAttachment {
|
||||
async function filePathToImageAttachment(path: string): Promise<ImageAttachment> {
|
||||
const extension = getFileExtension(path);
|
||||
const mimeType = IMAGE_MIME_BY_EXTENSION[extension] ?? "image/jpeg";
|
||||
const convertFileSrc = getTauri()?.core?.convertFileSrc;
|
||||
const uri =
|
||||
typeof convertFileSrc === "function" ? convertFileSrc(path) : path;
|
||||
|
||||
return {
|
||||
uri,
|
||||
mimeType,
|
||||
};
|
||||
return await persistAttachmentFromFileUri({ uri: path, mimeType });
|
||||
}
|
||||
|
||||
async function fileToImageAttachment(file: File): Promise<ImageAttachment> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
if (typeof reader.result === "string") {
|
||||
resolve({
|
||||
uri: reader.result,
|
||||
mimeType: file.type || "image/jpeg",
|
||||
});
|
||||
} else {
|
||||
reject(new Error("Failed to read file as data URL"));
|
||||
}
|
||||
};
|
||||
reader.onerror = () => reject(reader.error);
|
||||
reader.readAsDataURL(file);
|
||||
return await persistAttachmentFromBlob({
|
||||
blob: file,
|
||||
mimeType: file.type || "image/jpeg",
|
||||
fileName: file.name,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -177,8 +164,16 @@ export function useFileDropZone({
|
||||
return;
|
||||
}
|
||||
|
||||
const attachments = imagePaths.map(filePathToImageAttachment);
|
||||
onFilesDroppedRef.current(attachments);
|
||||
void Promise.all(imagePaths.map(filePathToImageAttachment))
|
||||
.then((attachments) => {
|
||||
if (attachments.length === 0) {
|
||||
return;
|
||||
}
|
||||
onFilesDroppedRef.current(attachments);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[useFileDropZone] Failed to persist dropped files:", error);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -144,11 +144,9 @@ function toAggregatedAgent(params: {
|
||||
}
|
||||
|
||||
export function useSidebarAgentsList(options?: {
|
||||
isOpen?: boolean;
|
||||
serverId?: string | null;
|
||||
selectedProjectFilterKeys?: string[];
|
||||
}): SidebarAgentsListResult {
|
||||
const isSidebarOpen = options?.isOpen ?? true;
|
||||
const { daemons } = useDaemonRegistry();
|
||||
const runtime = getHostRuntimeStore();
|
||||
const daemonLabelSignature = useMemo(
|
||||
@@ -181,7 +179,7 @@ export function useSidebarAgentsList(options?: {
|
||||
[options?.selectedProjectFilterKeys]
|
||||
);
|
||||
|
||||
const isActive = Boolean(serverId && isSidebarOpen);
|
||||
const isActive = Boolean(serverId);
|
||||
const liveAgents = useSessionStore((state) =>
|
||||
isActive && serverId ? state.sessions[serverId]?.agents ?? null : null
|
||||
);
|
||||
@@ -309,7 +307,7 @@ export function useSidebarAgentsList(options?: {
|
||||
if (!isActive || !serverId || connectionStatus !== "online") {
|
||||
return;
|
||||
}
|
||||
void runtime.refreshAgentDirectory({ serverId }).catch(() => undefined);
|
||||
void runtime.refreshAgentDirectory({ serverId, page: { limit: 50 } }).catch(() => undefined);
|
||||
}, [connectionStatus, isActive, runtime, serverId]);
|
||||
|
||||
const isDirectoryLoading =
|
||||
|
||||
Reference in New Issue
Block a user