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:
Mohamed Boudra
2026-02-22 16:22:21 +07:00
parent 2652bdda2e
commit 0c710b09f5
55 changed files with 2381 additions and 350 deletions
+55 -6
View File
@@ -1,28 +1,77 @@
import { create } from "zustand";
import type { UserMessageImageAttachment } from "@/types/stream";
export type CreateFlowLifecycleState = "active" | "abandoned" | "sent";
type PendingCreateAttempt = {
draftId: string;
serverId: string;
agentId: string | null;
messageId: string;
clientMessageId: string;
text: string;
timestamp: number;
lifecycle: CreateFlowLifecycleState;
images?: UserMessageImageAttachment[];
};
type CreateFlowState = {
pending: PendingCreateAttempt | null;
setPending: (pending: PendingCreateAttempt) => void;
setPending: (
pending: Omit<PendingCreateAttempt, "lifecycle">
) => void;
updateAgentId: (agentId: string) => void;
markLifecycle: (lifecycle: CreateFlowLifecycleState) => void;
rekeyDraft: (input: { fromDraftId: string; toDraftId: string }) => void;
clear: () => void;
};
export const useCreateFlowStore = create<CreateFlowState>((set) => ({
pending: null,
setPending: (pending) => set({ pending }),
setPending: (pending) =>
set({
pending: {
...pending,
lifecycle: "active",
},
}),
updateAgentId: (agentId) =>
set((state) =>
state.pending ? { pending: { ...state.pending, agentId } } : state
),
set((state) => {
if (!state.pending || state.pending.agentId === agentId) {
return state;
}
return {
pending: {
...state.pending,
agentId,
},
};
}),
markLifecycle: (lifecycle) =>
set((state) => {
if (!state.pending || state.pending.lifecycle === lifecycle) {
return state;
}
return {
pending: {
...state.pending,
lifecycle,
},
};
}),
rekeyDraft: ({ fromDraftId, toDraftId }) =>
set((state) => {
if (!state.pending || state.pending.draftId !== fromDraftId) {
return state;
}
if (state.pending.draftId === toDraftId) {
return state;
}
return {
pending: {
...state.pending,
draftId: toDraftId,
},
};
}),
clear: () => set({ pending: null }),
}));