-- allium: 1 -- bDS Action Patterns and Chains -- Scope: cross-cutting (all waves) -- Distilled from: PostEditor.tsx, MediaEditor.tsx, appStore.ts -- Cross-cutting patterns for AI operations, auto-translation, -- drag-and-drop chains, and confirmation dialogs. use "./post.allium" as post use "./media.allium" as media -- ─── External surfaces ────────────────────────────────────── surface UserAction { provides: AISuggestionRequested(entity_type, entity_id) provides: PostSaved(post_id) provides: PostAutoTranslateCompleted(post_id, language) } -- ─── AI operation gating ─────────────────────────────── invariant AIOperationGating { -- All AI operations route through the active endpoint for the current mode. -- See ai.allium AirplaneModeGating for endpoint selection logic. -- If airplane_mode: use airplane endpoint (local model, e.g. Ollama/LM Studio) -- If online: use online endpoint (cloud provider) -- If active endpoint not configured: show toast, abort operation -- Two model categories: -- Title model: text analysis (suggestions, translation, language detect) -- Image model: vision-capable (image analysis only) -- Model selection: per-operation default from settings, -- overrideable per-conversation in chat panel only } -- ─── AI suggestions pattern ──────────────────────────── -- Shared flow used by PostAIAnalysis and MediaAIImageAnalysis. -- See modals.allium AISuggestionsModal value type. rule AISuggestionFlow { when: AISuggestionRequested(entity_type, entity_id) -- 1. Check AIOperationGating (abort if offline and no local model) -- 2. Send request to appropriate model: -- Posts: title model, input = title + excerpt + first 2000 chars -- Media: image model, input = 448x448 AI thumbnail JPEG -- 3. Parse response into per-field suggestions -- 4. Open AISuggestionsModal: -- Each field: label, current value, suggested value, accept checkbox -- Accept checkboxes default to true -- Special case: post slug locked (no checkbox) if ever published -- 5. On Confirm: apply only accepted fields to entity -- Posts: triggers auto-save (3s timer reset) -- Media: triggers explicit save -- 6. On Cancel: discard all suggestions, no changes } -- ─── Auto-translation chain ──────────────────────────── rule AutoTranslationChain { when: PostSaved(post_id) -- Gate: AIOperationGating + post.doNotTranslate must be false -- Triggered after any post save (auto-save, manual Ctrl+S, or unmount) -- For each configured blogLanguage missing a translation for this post: -- 1. Enqueue background task: translate metadata (title, excerpt) -- via title model -- 2. Enqueue background task: translate content (full markdown) -- via title model -- 3. Create/update translation record in DB -- Tasks: sequential per language, parallel across languages -- Progress visible in Tasks panel } rule MediaMetadataTranslationCascade { when: PostAutoTranslateCompleted(post_id, language) -- After a post translation completes for a given language: -- For each media item linked to this post: -- If media has source language set -- and no translation exists for {language}: -- Enqueue background task: translate media metadata -- (title, alt, caption) via title model -- Creates translated sidecar file: {path}.{lang}.meta } -- ─── Drag-and-drop image chain ───────────────────────── -- Full chain when image file is dropped on post editor body. -- See editor_post.allium PostDragDropImage for trigger rule. -- Synchronous steps (user waits): -- 1. importMedia(file) -> new media record + file copy + base sidecar -- 2. generateThumbnails(media) -> async start (small/medium/large/ai) -- 3. linkMediaToPost(media, post) -> update sidecar linkedPostIds -- 4. insertMarkdownImage(cursor) -> insert ![](bds-media://id) at cursor -- Background steps (non-blocking, results auto-applied): -- 5. If AI available: aiImageAnalysis(media) -- Uses image model on 448x448 AI thumbnail -- Results auto-applied to media metadata (NO modal for drag-drop, -- unlike manual Quick Action which shows AISuggestionsModal) -- Triggers sidecar rewrite -- 6. If auto-translate enabled (post.doNotTranslate=false): -- For each blogLanguage: translateMediaMetadata(media, lang) -- Creates translated sidecar files -- ─── Confirmation dialog patterns ────────────────────── -- Four distinct patterns used across the application: -- Pattern 1: System confirm dialog -- Simple yes/no system dialog, no custom UI -- Used by: PostDelete, PostDiscard, TemplateDelete (when references exist) -- Pattern 2: ConfirmDeleteModal (custom modal with reference info) -- Shows entity name, reference counts, linked entity list -- Two buttons: Cancel, Delete (destructive red style) -- Used by: MediaDelete (shows linked posts), TagDelete (shows post count) -- Pattern 3: ConfirmDialog (custom modal for non-delete confirmations) -- Shows description of action and consequences -- Two buttons: Cancel, Confirm -- Used by: TagMerge ("Merge N tags into {target}? Cannot be undone.") -- Pattern 4: No confirmation (immediate execution) -- Action executes on click, no dialog -- Used by: all Rebuild operations, ScriptDelete, MenuItemDelete, -- MetadataDiff per-field sync, ImportExecute, MediaTranslationDelete, -- MediaUnlink