Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/app/lib/store/slices/backend-provision-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { GenerationStore } from "@/app/lib/store/types";
import type { StoreApi } from "zustand";
import * as api from "@/app/lib/api";

/**
* Backend provision actions — installing, updating, and checking the ACE-Step
* backend. Extracted from the model slice to keep the slice focused on model
* download/status management.
*/
export function createBackendProvisionActions(
set: StoreApi<GenerationStore>["setState"],
get: StoreApi<GenerationStore>["getState"],
) {
return {
refreshBackendProvisionStatus: async () => {
if (!api.isTauriRuntime()) return;
try {
const status = await api.getBackendProvisionStatus();
set({ backendProvisionStatus: status });
} catch (error) {
console.warn("Failed to refresh backend provision status:", error);
}
},

provisionBackend: async () => {
if (!api.isTauriRuntime()) return;
try {
const status = await api.provisionBackend();
set({ backendProvisionStatus: status });
await get().refreshBootstrapStatus();
} catch (error) {
set({
backendProvisionStatus: {
state: "failed",
installedCommit: null,
installedTag: null,
latestCommit: null,
latestTag: null,
updateAvailable: false,
downloadedBytes: 0,
error:
error instanceof Error
? {
code: "BACKEND_PROVISION_FAILED",
message: error.message,
recoverable: true,
}
: undefined,
},
});
}
},

updateBackend: async () => {
if (!api.isTauriRuntime()) return;
try {
const status = await api.updateBackend();
set({ backendProvisionStatus: status });
await get().refreshBootstrapStatus();
} catch (error) {
set({
backendProvisionStatus: {
...get().backendProvisionStatus,
state: "failed",
error:
error instanceof Error
? {
code: "BACKEND_PROVISION_FAILED",
message: error.message,
recoverable: true,
}
: undefined,
},
});
}
},
};
}
124 changes: 124 additions & 0 deletions src/app/lib/store/slices/generation-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { GenerationStore } from "@/app/lib/store/types";
import type { StoreApi } from "zustand";
import type { GenerationEvent } from "@/app/lib/types";
import { createFailedGenerationState, variationLabel } from "@/app/lib/store-helpers";
import { localizeAppError } from "@/app/lib/errors";
import { shouldMarkBootstrapFailed } from "@/app/lib/model-bootstrap";
import { tr } from "@/app/lib/i18n";

/**
* Apply a generation lifecycle event to the store. Extracted from the
* generation slice so the slice file stays focused on action orchestration
* (run, cancel, enhance) rather than event-to-state mapping.
*/
export function applyGenerationEvent(
event: GenerationEvent,
set: StoreApi<GenerationStore>["setState"],
) {
switch (event.type) {
case "backend_starting":
set({
bootstrapStatus: {
state: "downloading",
message: tr("status.preparingBackend"),
},
generationState: {
status: "running",
phase: "backend_starting",
statusMessage: `${tr("status.startingBackend")}${variationLabel(event)}`,
error: null,
variationCurrent: event.variationCurrent,
variationTotal: event.variationTotal,
},
});
break;
case "submitted":
set({
generationState: {
status: "running",
phase: "submitted",
statusMessage: `${tr("status.submittedTask", { taskId: event.taskId })}${variationLabel(event)}`,
error: null,
taskId: event.taskId,
variationCurrent: event.variationCurrent,
variationTotal: event.variationTotal,
},
});
break;
case "queued":
set({
generationState: {
status: "running",
phase: "queued",
statusMessage: `${tr("status.queued")}${variationLabel(event)}`,
error: null,
variationCurrent: event.variationCurrent,
variationTotal: event.variationTotal,
},
});
break;
case "running":
set({
generationState: {
status: "running",
phase: "running",
statusMessage: `${tr("status.running")}${variationLabel(event)}`,
error: null,
variationCurrent: event.variationCurrent,
variationTotal: event.variationTotal,
progressPercent: event.progressPercent,
},
});
break;
case "downloading":
set({
generationState: {
status: "running",
phase: "downloading",
statusMessage: `${tr("status.downloadingAudio")}${variationLabel(event)}`,
error: null,
variationCurrent: event.variationCurrent,
variationTotal: event.variationTotal,
},
});
break;
case "completed":
set({
bootstrapStatus: {
state: "ready",
message: tr("status.localStackReady"),
},
generationState: {
status: "completed",
phase: "completed",
statusMessage: tr("status.completed"),
error: null,
variationCurrent: event.variationCurrent,
variationTotal: event.variationTotal,
},
});
break;
case "cancelled":
set({
generationState: {
status: "cancelled",
phase: "cancelled",
statusMessage: tr("status.cancelled"),
error: null,
variationCurrent: event.variationCurrent,
variationTotal: event.variationTotal,
},
});
break;
case "failed": {
const error = localizeAppError(event.error);
set({
bootstrapStatus: shouldMarkBootstrapFailed(error.code)
? { state: "failed", message: error.message, error }
: { state: "ready", message: tr("status.localStackReady") },
generationState: createFailedGenerationState(tr("status.failed"), error),
});
break;
}
}
}
63 changes: 63 additions & 0 deletions src/app/lib/store/slices/generation-tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { GenerationStore } from "@/app/lib/store/types";
import type { StoreApi } from "zustand";
import * as api from "@/app/lib/api";
import { createFailedGenerationState } from "@/app/lib/store-helpers";
import { localizeAppError } from "@/app/lib/errors";
import { tr } from "@/app/lib/i18n";

/**
* Active generation task management — recovering, discarding, and refreshing
* tasks that survived a restart. Extracted from the generation slice to keep
* the slice focused on the primary generation lifecycle.
*/
export function createGenerationTaskActions(
set: StoreApi<GenerationStore>["setState"],
_get: StoreApi<GenerationStore>["getState"],
) {
return {
refreshActiveTasks: async () => {
if (!api.isTauriRuntime()) return;
const activeTasks = await api.listActiveGenerationTasks();
set({ activeTasks });
},

resumeActiveTask: async (id: string) => {
set({
generationState: {
status: "running",
phase: "recovering",
statusMessage: tr("status.recovering"),
error: null,
},
});
try {
const record = await api.resumeGenerationTask(id);
set((state) => ({
activeTasks: state.activeTasks.filter((task) => task.id !== id),
currentGeneration: record,
history: [record, ...state.history.filter((item) => item.id !== record.id)],
generationState: {
status: "completed",
phase: "completed",
statusMessage: tr("status.completed"),
error: null,
},
}));
} catch (error) {
const appError = localizeAppError(error);
set({
generationState: createFailedGenerationState(tr("status.recoveryFailed"), appError),
});
}
},

discardActiveTask: async (id: string) => {
if (api.isTauriRuntime()) {
await api.discardActiveGenerationTask(id);
}
set((state) => ({
activeTasks: state.activeTasks.filter((task) => task.id !== id),
}));
},
};
}
Loading
Loading