+
+
+
+ {{ generatingLabel }}
+
+
+
+
+
+
+ {{ $t('posts.create.steps.bar_error') }}
+
+
+
diff --git a/resources/js/composables/useAiGeneration.ts b/resources/js/composables/useAiGeneration.ts
new file mode 100644
index 00000000..a8233691
--- /dev/null
+++ b/resources/js/composables/useAiGeneration.ts
@@ -0,0 +1,172 @@
+import { router } from '@inertiajs/vue3';
+import { echo } from '@laravel/echo-vue';
+import { computed, ref } from 'vue';
+
+import { edit as editPostRoute } from '@/routes/app/posts';
+
+/**
+ * Global state for in-flight AI post generations.
+ *
+ * Generation runs on the backend (the `StreamPostCreation` job) and broadcasts
+ * `.ai.creation.completed` on the private `user.{id}.ai-creation.{creationId}`
+ * channel. This composable is the SOLE OWNER of that channel subscription, so
+ * the "AI is generating…" notice persists across navigation (module-level state,
+ * SPA) and survives a hard reload via `sessionStorage`.
+ */
+
+export interface AiGeneration {
+ /** creationId — identifies the generation. */
+ id: string;
+ /** Full private channel name (without the `private-` prefix). */
+ channel: string;
+ imageCount: number;
+ /** epoch ms — used by the safety timeout and by hydration. */
+ startedAt: number;
+ status: 'loading' | 'done' | 'error';
+ postId?: string;
+ error?: string;
+}
+
+const STORAGE_KEY = 'ai-generations';
+/** After this, if nothing arrived, the bar disappears on its own (avoids getting stuck). */
+const MAX_LIFETIME_MS = 6 * 60 * 1000;
+/** How long the "done"/"error" state stays visible before disappearing. */
+const DONE_TTL_MS = 12 * 1000;
+
+const generations = ref