Rewrite workspace tab management to use stable tab IDs (agent_, terminal_, draft_, file_) with dedicated /tab/[tabId] routes, replace separate file-tabs store with unified workspace-tabs store, support multiple concurrent draft agent tabs, and detect text files by content instead of extension allowlist. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
97 lines
2.6 KiB
TypeScript
97 lines
2.6 KiB
TypeScript
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;
|
|
clientMessageId: string;
|
|
text: string;
|
|
timestamp: number;
|
|
lifecycle: CreateFlowLifecycleState;
|
|
images?: UserMessageImageAttachment[];
|
|
};
|
|
|
|
type CreateFlowState = {
|
|
pendingByDraftId: Record<string, PendingCreateAttempt>;
|
|
setPending: (
|
|
pending: Omit<PendingCreateAttempt, "lifecycle">
|
|
) => void;
|
|
updateAgentId: (input: { draftId: string; agentId: string }) => void;
|
|
markLifecycle: (input: {
|
|
draftId: string;
|
|
lifecycle: CreateFlowLifecycleState;
|
|
}) => void;
|
|
rekeyDraft: (input: { fromDraftId: string; toDraftId: string }) => void;
|
|
clear: (input: { draftId: string }) => void;
|
|
clearAll: () => void;
|
|
};
|
|
|
|
export const useCreateFlowStore = create<CreateFlowState>((set) => ({
|
|
pendingByDraftId: {},
|
|
setPending: (pending) =>
|
|
set((state) => ({
|
|
pendingByDraftId: {
|
|
...state.pendingByDraftId,
|
|
[pending.draftId]: {
|
|
...pending,
|
|
lifecycle: "active",
|
|
},
|
|
},
|
|
})),
|
|
updateAgentId: ({ draftId, agentId }) =>
|
|
set((state) => {
|
|
const current = state.pendingByDraftId[draftId];
|
|
if (!current || current.agentId === agentId) {
|
|
return state;
|
|
}
|
|
return {
|
|
pendingByDraftId: {
|
|
...state.pendingByDraftId,
|
|
[draftId]: { ...current, agentId },
|
|
},
|
|
};
|
|
}),
|
|
markLifecycle: ({ draftId, lifecycle }) =>
|
|
set((state) => {
|
|
const current = state.pendingByDraftId[draftId];
|
|
if (!current || current.lifecycle === lifecycle) {
|
|
return state;
|
|
}
|
|
return {
|
|
pendingByDraftId: {
|
|
...state.pendingByDraftId,
|
|
[draftId]: { ...current, lifecycle },
|
|
},
|
|
};
|
|
}),
|
|
rekeyDraft: ({ fromDraftId, toDraftId }) =>
|
|
set((state) => {
|
|
const current = state.pendingByDraftId[fromDraftId];
|
|
if (!current) {
|
|
return state;
|
|
}
|
|
if (fromDraftId === toDraftId) {
|
|
return state;
|
|
}
|
|
const { [fromDraftId]: _removed, ...rest } = state.pendingByDraftId;
|
|
return {
|
|
pendingByDraftId: {
|
|
...rest,
|
|
[toDraftId]: { ...current, draftId: toDraftId },
|
|
},
|
|
};
|
|
}),
|
|
clear: ({ draftId }) =>
|
|
set((state) => {
|
|
if (!state.pendingByDraftId[draftId]) {
|
|
return state;
|
|
}
|
|
const { [draftId]: _removed, ...rest } = state.pendingByDraftId;
|
|
return { pendingByDraftId: rest };
|
|
}),
|
|
clearAll: () => set({ pendingByDraftId: {} }),
|
|
}));
|