diff --git a/changelog/2026-05-09_01.md b/changelog/2026-05-09_01.md new file mode 100644 index 0000000..d3169e0 --- /dev/null +++ b/changelog/2026-05-09_01.md @@ -0,0 +1,155 @@ +--- +date_created: 2026-05-09 +date_modified: 2026-05-09 +title: "Drop Gate — every image asks where it should go" +lede: "Image Gin now intercepts every image you drag or paste into a note and pops a single confirmation modal: vault, ImageKit, or Imgur? Built for writers who handle private client imagery and don't want a default-on third-party uploader silently shipping screenshots to a public CDN. Default is the vault. ImageKit is for less-public material that still wants a CDN. Imgur is there when something is genuinely shareable." +publish: true +authors: + - Michael Staton +augmented_with: + - Claude Code on Claude Opus 4.7 (1M context) +files_changed: + - main.ts + - src/handlers/DropGateHandlers.ts + - src/modals/DropGateModal.ts + - src/destinations/types.ts + - src/destinations/VaultDestination.ts + - src/destinations/ImageKitDestination.ts + - src/destinations/ImgurDestination.ts + - src/utils/dropGateEvents.ts + - src/settings/settings.ts + - src/styles/drop-gate.css + - src/styles/current-file-modal.css +--- + +## Why Care? + +We use Obsidian to write about companies. The artifacts those companies share with us — growth charts, dashboard screenshots, photos of a whiteboard — are not always meant to leave a private context. The default failure mode used to be silent: drag a file in, and the bytes go where Obsidian (or some default-on third-party uploader plugin) decides without asking. That's fine for a recipe. It's a phone call we don't want to make for anything client-sensitive. + +The Drop Gate puts a deliberate decision between the drop and the destination. One modal, three destinations: + +- **Vault attachments** — Obsidian's standard private behavior. Bytes stay in the vault. +- **ImageKit** — your own private CDN. We use it when material isn't strictly secret but we don't want to host it forever ourselves. *This is our default for less-public information.* +- **Imgur** — anonymous public upload. Free, fast, durable. Use when something is genuinely shareable. + +Off by default. Toggle it on in **Settings → Image Gin → Drag-Drop / Paste Confirmation Gate**. + +## What's New? + +A new feature inside Image Gin, layered alongside the existing Recraft / Magnific / Ideogram / ImageKit pipelines. No new plugin to install — same plugin, a new section in settings. + +- **Intercepts every image drop and paste** in a markdown view via Obsidian's `editor-drop` and `editor-paste` workspace events. +- **The modal** shows the dropped file(s) (name, size, type) and a radio group of all three destinations. Configured destinations are selectable; un-configured ones are visible-but-disabled with a hint pointing at the right setting. You can see what's possible even before you've turned anything on. +- **Three destinations behind a small `DropGateDestination` interface** so a fourth (S3, Bunny, Cloudinary, an internal SharePoint of doom) is a class plus a settings panel: + - **VaultDestination** — re-dispatches a synthetic event copy into Obsidian's internal `clipboardManager` so the default attachment-handling code runs cleanly. Falls back to `Vault.createBinary` + `FileManager.generateMarkdownLink` if `clipboardManager` is unavailable. + - **ImageKitDestination** — wraps Image Gin's existing `ImageKitService`. Same multipart-upload pipeline that already powers "Convert Local Images to Remote." A drop-gate-specific folder setting (`dropGate.imageKitFolder`) overrides the main upload folder when set, so ad-hoc dropped images can land somewhere different from generated images. + - **ImgurDestination** — anonymous client-ID upload to `api.imgur.com/3/image`. Free, no account. +- **Two policy modes**: `always-confirm` (modal on every image, the default) and `external-only` (vault drops fall through silently; modal only appears if at least one external destination is enabled). +- **Per-session "remember choice"** checkbox in the modal — skips the modal for subsequent drops in the current note. Resets on `active-leaf-change`. Never persists across Obsidian restarts. +- **Conflict detection.** If the third-party `obsidian-imgur-plugin` is also enabled, both plugins handle every drop and the user gets two modals back-to-back — `preventDefault()` blocks the browser default, not other plugins. We surface a persistent Notice on plugin load telling the user to disable the other one and configure Imgur from inside Image Gin. +- **Reset command** in the palette: *"Drop Gate: Reset session-remembered destination."* + +## How it feels + +Drop a screenshot into a note. Modal opens, wide enough to read (~720px). Three destinations visible — the ones you've configured are selectable; the ones you haven't carry a one-line nudge to settings. Pick ImageKit. Modal closes. Image uploads. Markdown link lands at your cursor. Cursor moves to the next line. Editor refocuses. Console says `[image-gin/drop-gate] ImageKit upload → https://ik.imagekit.io/...`. You keep typing. + +If you'd rather not be asked every time, set the policy to *external-only* and your vault drops happen silently — only ImageKit / Imgur destinations bring up the modal. + +## The arc + +We started with the third-party `obsidian-imgur-plugin` by Kirill Gavrilov. Its drag-confirm pattern is the right idea but biased the wrong way for our workflow: it asks "should we upload to Imgur or paste locally?" The answer for client-sensitive imagery is "neither, until you tell me where." We wanted vault-first, third-party host second, and only with deliberate confirmation each time. + +We sketched a standalone plugin, then folded it back into Image Gin once it was clear that: + +- Image Gin already owns the ImageKit upload pipeline (the most useful private destination). +- The `DropGateDestination` interface is small enough that it doesn't justify its own plugin; it sits naturally next to the existing Recraft / Magnific / Ideogram services. +- One settings tab is easier than two. + +So this is a feature add inside Image Gin, not a new sibling plugin. The `ImageKitDestination` is a thin adapter over `ImageKitService.uploadFile()` — no duplicated upload logic. + +## Architecture + +``` +src/ +├── handlers/DropGateHandlers.ts editor-drop + editor-paste; re-entry guard; +│ synchronous preventDefault; cursor capture +├── modals/DropGateModal.ts deferred-promise modal; modalEl-attached +│ for proper widening; renders all destinations +│ with disabled-state for un-configured ones +├── destinations/ +│ ├── types.ts DropGateDestination + DropGateContext +│ │ (carries the captured insertPos) +│ ├── VaultDestination.ts clipboardManager re-dispatch + explicit-API fallback +│ ├── ImageKitDestination.ts wraps existing ImageKitService.uploadFile +│ └── ImgurDestination.ts anonymous client-ID upload +├── utils/dropGateEvents.ts DragEventCopy, PasteEventCopy (re-entry guards), +│ allFilesAreImages, fileNameFor +├── settings/settings.ts +dropGate +imgur blocks; settings-tab UI +└── styles/drop-gate.css modal styles, scoped to .image-gin-drop-gate-modal +``` + +`main.ts` grew by ~40 lines: instantiate three destinations, wire two `workspace.on(...)` calls, register a reset command, run the conflict check. + +## Load-bearing details + +Three implementation details we expect to forget and want findable when we do: + +```ts +// 1. Re-entry guard. Without this, our own VaultDestination's +// re-dispatch loops back through the handler. Infinite loop. +if (e instanceof DragEventCopy) return + +// 2. Synchronous preventDefault — must come BEFORE any await, +// otherwise the browser already gave up on cancelling the default. +e.preventDefault() + +// 3. Capture cursor synchronously, before awaiting the modal. +// By the time the user picks a destination, editor.getCursor() +// is unreliable — focus may have drifted. We pass insertPos +// through DropGateContext and use editor.replaceRange(md, pos) +// instead of editor.replaceSelection(md). +const insertPos = editor.getCursor() +``` + +The vault fall-through uses an undocumented internal: `view.currentMode.clipboardManager.handleDrop(DragEventCopy.create(e, files))`. It's not in `obsidian.d.ts` but stable in practice; we cast through `unknown` and fall back to `Vault.createBinary` + `FileManager.generateMarkdownLink` if the surface ever disappears. + +The `editor-drop` callback signature in `obsidian.d.ts` is `(evt: DragEvent, editor: Editor, info: MarkdownView | MarkdownFileInfo) => any`. `MarkdownFileInfo` is the canvas / non-markdown variant; we narrow with `info instanceof MarkdownView` and bail on canvas drops. Canvas support is a follow-up. + +## Settings surface + +Two new sections in the Image Gin settings tab, both default-off: + +| Section | Setting | Default | +|------------------|------------------------------------------|------------------------| +| Drop Gate | Enable drop gate | Off | +| Drop Gate | Policy mode | Always confirm | +| Drop Gate | Default destination | Vault attachments | +| Drop Gate | Show "remember for session" checkbox | On | +| Drop Gate | ImageKit folder for drop-gate uploads | empty (inherits main) | +| Imgur | Enable Imgur destination | Off | +| Imgur | Imgur client ID | empty | + +ImageKit's main settings (private key, endpoint, main upload folder) live in their existing section above — they pre-date this feature. The drop-gate uses them whenever `imageKit.enabled` is on. + +## What to test + +1. Toggle Drop Gate on. Drop a PNG into a markdown note → modal appears with all three destinations. Vault is pre-selected. Click Insert → image lands in vault attachments, markdown link inserted at cursor. +2. Configure ImageKit and drop again → ImageKit row becomes selectable. Pick it → image uploads, markdown link with the ImageKit URL inserted. +3. Configure Imgur (anonymous client ID) and drop again → Imgur row becomes selectable. +4. Tick "Remember for this session," drop a second image in the same note → no modal, goes straight to your remembered destination. +5. Switch to a different note → modal returns on next drop. +6. Drop a non-image file → falls through to Obsidian's default behavior; modal does not appear. +7. With the `obsidian-imgur-plugin` community plugin also enabled → you'll see a persistent Notice on Image Gin's load telling you to disable it. Disable it, reload, drops go through Image Gin only. + +## Reference + +- Plan: `content-farm/context-v/plans/Image-Drop-Confirmation-Gate.md` — design rationale, threat model, phasing. +- Inspiration / interception pattern: [`gavvvr/obsidian-imgur-plugin`](https://github.com/gavvvr/obsidian-imgur-plugin) (MIT). Disable it if you enable Image Gin's Drop Gate; both register `editor-drop` and you'll see two modals otherwise. +- Modal widening mechanic: `perplexed/context-v/issues/Widen-Modals-in-Obsidian-using-CSS.md` — `modalEl` vs. `contentEl`. The 720px width and 88vh height only take effect because the class is on `modalEl`. +- ImageKit upload pipeline: existing `src/services/imagekitService.ts`, reused via the `ImageKitDestination` adapter. + +## Next + +- Canvas support — `MarkdownFileInfo` rather than `MarkdownView`; needs a parallel handler. +- Optional `imagery_policy:` frontmatter gate ("always confirm for this folder/file"), once we see whether always-on is friction-light enough not to need it. +- Filename / preview thumbnail in the modal — currently shows name + size + type only. diff --git a/log.json b/log.json index 345a834..a0cb653 100644 --- a/log.json +++ b/log.json @@ -670,5 +670,5048 @@ "timestamp": "2026-05-04T03:42:16.975Z", "level": "info", "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/uploads/lossless/2025-dec/MemoPop_AI_content_1777866136156_bOoHTyHzX.webp" + }, + { + "timestamp": "2026-05-05T08:01:41.240Z", + "level": "info", + "message": "[Ideogram] toggle banner -> true; selected:", + "details": [ + "banner" + ] + }, + { + "timestamp": "2026-05-05T08:01:41.907Z", + "level": "info", + "message": "[Ideogram] toggle portrait -> true; selected:", + "details": [ + "banner", + "portrait" + ] + }, + { + "timestamp": "2026-05-05T08:01:42.525Z", + "level": "info", + "message": "[Ideogram] master toggle -> true; selected:", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:01:42.525Z", + "level": "info", + "message": "[Ideogram] toggle square -> true; selected:", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:01:45.296Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:01:45.296Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:01:45.297Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:01:45.297Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T08:01:45.297Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:01:45.297Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:01:45.297Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:01:45.297Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:01:45.297Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:01:52.944Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:01:53.796Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1027145 + }, + { + "timestamp": "2026-05-05T08:01:53.810Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:01:53.811Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T08:01:53.811Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:01:53.811Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:01:53.811Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:01:53.811Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:01:53.811Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:02:00.950Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:02:01.820Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1150363 + }, + { + "timestamp": "2026-05-05T08:02:01.830Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:02:01.830Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T08:02:01.830Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:02:01.830Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:02:01.830Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:02:01.830Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:02:01.830Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:02:09.349Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:02:10.161Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1096965 + }, + { + "timestamp": "2026-05-05T08:17:08.198Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "assets/ImageGin/ideogram-image_2048x1024_1777968113796.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "assets/ImageGin/ideogram-image_1024x1820_1777968121820.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "assets/ImageGin/ideogram-image_1024x1024_1777968130161.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:17:08.198Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:17:17.336Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777969037336.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://ik.imagekit.io/xvpgfijuw/", + "contentLength": 1027671 + } + }, + { + "timestamp": "2026-05-05T08:17:17.819Z", + "level": "error", + "message": "Error uploading to ImageKit:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:17:17.820Z", + "level": "error", + "message": "Error converting banner_image:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:17:17.823Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777969037822.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://ik.imagekit.io/xvpgfijuw/", + "contentLength": 1150893 + } + }, + { + "timestamp": "2026-05-05T08:17:17.991Z", + "level": "error", + "message": "Error uploading to ImageKit:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:17:17.991Z", + "level": "error", + "message": "Error converting portrait_image:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:17:17.994Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777969037994.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://ik.imagekit.io/xvpgfijuw/", + "contentLength": 1097491 + } + }, + { + "timestamp": "2026-05-05T08:17:18.189Z", + "level": "error", + "message": "Error uploading to ImageKit:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:17:18.190Z", + "level": "error", + "message": "Error converting square_image:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:18:33.852Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "assets/ImageGin/ideogram-image_2048x1024_1777968113796.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "assets/ImageGin/ideogram-image_1024x1820_1777968121820.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "assets/ImageGin/ideogram-image_1024x1024_1777968130161.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:18:33.852Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:18:37.257Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777969117257.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://ik.imagekit.io/xvpgfijuw/", + "contentLength": 1027671 + } + }, + { + "timestamp": "2026-05-05T08:18:37.396Z", + "level": "error", + "message": "Error uploading to ImageKit:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:18:37.396Z", + "level": "error", + "message": "Error converting banner_image:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:18:37.397Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777969117397.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://ik.imagekit.io/xvpgfijuw/", + "contentLength": 1150893 + } + }, + { + "timestamp": "2026-05-05T08:18:37.469Z", + "level": "error", + "message": "Error uploading to ImageKit:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:18:37.469Z", + "level": "error", + "message": "Error converting portrait_image:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:18:37.470Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777969117470.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://ik.imagekit.io/xvpgfijuw/", + "contentLength": 1097491 + } + }, + { + "timestamp": "2026-05-05T08:18:37.524Z", + "level": "error", + "message": "Error uploading to ImageKit:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:18:37.524Z", + "level": "error", + "message": "Error converting square_image:", + "details": { + "message": "Request failed, status 403", + "name": "Error", + "stack": "Error: Request failed, status 403\n at new t (app://obsidian.md/app.js:1:1066656)\n at ly (app://obsidian.md/app.js:1:1066848)\n at app://obsidian.md/app.js:1:1067527\n at app://obsidian.md/app.js:1:258511\n at Object.next (app://obsidian.md/app.js:1:258616)\n at a (app://obsidian.md/app.js:1:257334)" + } + }, + { + "timestamp": "2026-05-05T08:22:31.199Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "assets/ImageGin/ideogram-image_2048x1024_1777968113796.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "assets/ImageGin/ideogram-image_1024x1820_1777968121820.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "assets/ImageGin/ideogram-image_1024x1024_1777968130161.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:22:31.199Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:22:34.950Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777969354950.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1027671 + } + }, + { + "timestamp": "2026-05-05T08:22:35.678Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777969354950_8hVle1ahT.webp" + }, + { + "timestamp": "2026-05-05T08:22:35.682Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777969354950_8hVle1ahT.webp" + }, + { + "timestamp": "2026-05-05T08:22:35.683Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777969355683.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1150893 + } + }, + { + "timestamp": "2026-05-05T08:22:36.058Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777969355683_c9-2tSKjVY.webp" + }, + { + "timestamp": "2026-05-05T08:22:36.063Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777969355683_c9-2tSKjVY.webp" + }, + { + "timestamp": "2026-05-05T08:22:36.065Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777969356065.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1097491 + } + }, + { + "timestamp": "2026-05-05T08:22:36.430Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777969356065_0Efa7Ggtv.webp" + }, + { + "timestamp": "2026-05-05T08:22:36.436Z", + "level": "info", + "message": "Successfully converted square_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777969356065_0Efa7Ggtv.webp" + }, + { + "timestamp": "2026-05-05T08:25:40.050Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:25:40.050Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:25:40.050Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:25:40.050Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T08:25:40.050Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:25:40.050Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:25:40.051Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:25:40.051Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:25:40.051Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:25:48.508Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:25:49.338Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1140828 + }, + { + "timestamp": "2026-05-05T08:25:49.352Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:25:49.352Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T08:25:49.352Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:25:49.352Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:25:49.352Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:25:49.352Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:25:49.352Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:25:56.696Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:25:57.561Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1215327 + }, + { + "timestamp": "2026-05-05T08:25:57.573Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:25:57.573Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T08:25:57.573Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:25:57.573Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:25:57.573Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:25:57.573Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:25:57.573Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:26:06.359Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:26:07.208Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1360505 + }, + { + "timestamp": "2026-05-05T08:26:25.327Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "assets/ImageGin/ideogram-image_2048x1024_1777969549339.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "assets/ImageGin/ideogram-image_1024x1820_1777969557561.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "assets/ImageGin/ideogram-image_1024x1024_1777969567209.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:26:25.328Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:26:36.699Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777969596699.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1141354 + } + }, + { + "timestamp": "2026-05-05T08:26:37.392Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777969596699__Z6sj9VRY.webp" + }, + { + "timestamp": "2026-05-05T08:26:37.398Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777969596699__Z6sj9VRY.webp" + }, + { + "timestamp": "2026-05-05T08:26:37.399Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777969597398.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1215857 + } + }, + { + "timestamp": "2026-05-05T08:26:37.749Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777969597398_eQaupiP68.webp" + }, + { + "timestamp": "2026-05-05T08:26:37.754Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777969597398_eQaupiP68.webp" + }, + { + "timestamp": "2026-05-05T08:26:37.756Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777969597756.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1361031 + } + }, + { + "timestamp": "2026-05-05T08:26:38.062Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777969597756__beqjEMJa.webp" + }, + { + "timestamp": "2026-05-05T08:26:38.065Z", + "level": "info", + "message": "Successfully converted square_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777969597756__beqjEMJa.webp" + }, + { + "timestamp": "2026-05-05T08:28:23.344Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:28:23.344Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:28:23.345Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:28:23.345Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T08:28:23.345Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:28:23.345Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:28:23.345Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:28:23.345Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:28:23.345Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:28:30.732Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:28:31.569Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1128441 + }, + { + "timestamp": "2026-05-05T08:28:31.580Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:28:31.580Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T08:28:31.580Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:28:31.580Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:28:31.580Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:28:31.580Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:28:31.580Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:28:40.118Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:28:41.038Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1178223 + }, + { + "timestamp": "2026-05-05T08:28:41.046Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:28:41.047Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T08:28:41.047Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:28:41.047Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:28:41.047Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:28:41.047Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:28:41.047Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:28:48.288Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:28:49.036Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1208851 + }, + { + "timestamp": "2026-05-05T08:32:37.308Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "assets/ImageGin/ideogram-image_2048x1024_1777969711569.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "assets/ImageGin/ideogram-image_1024x1820_1777969721038.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "assets/ImageGin/ideogram-image_1024x1024_1777969729036.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:32:37.308Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:32:41.832Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777969961832.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1128967 + } + }, + { + "timestamp": "2026-05-05T08:32:43.087Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777969961832_usECwv-Q_t.webp" + }, + { + "timestamp": "2026-05-05T08:32:43.090Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777969961832_usECwv-Q_t.webp" + }, + { + "timestamp": "2026-05-05T08:32:43.091Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777969963091.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1178749 + } + }, + { + "timestamp": "2026-05-05T08:32:43.541Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777969963091_-66gReMM4.webp" + }, + { + "timestamp": "2026-05-05T08:32:43.546Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777969963091_-66gReMM4.webp" + }, + { + "timestamp": "2026-05-05T08:32:43.548Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777969963547.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1209373 + } + }, + { + "timestamp": "2026-05-05T08:32:43.884Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777969963547_HO6qmCGoe.webp" + }, + { + "timestamp": "2026-05-05T08:32:43.887Z", + "level": "info", + "message": "Successfully converted square_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777969963547_HO6qmCGoe.webp" + }, + { + "timestamp": "2026-05-05T08:34:06.170Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:34:06.170Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:34:06.171Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:34:06.171Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T08:34:06.171Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:34:06.171Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:34:06.171Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:34:06.171Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:34:06.171Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:34:13.391Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:34:14.216Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1378068 + }, + { + "timestamp": "2026-05-05T08:34:14.226Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:34:14.227Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T08:34:14.227Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:34:14.227Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:34:14.227Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:34:14.227Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:34:14.227Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:34:21.348Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:34:22.103Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1422080 + }, + { + "timestamp": "2026-05-05T08:34:22.112Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:34:22.112Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T08:34:22.112Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:34:22.112Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:34:22.112Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:34:22.112Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:34:22.112Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:34:29.332Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:34:30.299Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1376395 + }, + { + "timestamp": "2026-05-05T08:34:34.579Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "assets/ImageGin/ideogram-image_2048x1024_1777970054216.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "assets/ImageGin/ideogram-image_1024x1820_1777970062104.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "assets/ImageGin/ideogram-image_1024x1024_1777970070299.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:34:34.579Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:34:38.096Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777970078096.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1378590 + } + }, + { + "timestamp": "2026-05-05T08:34:38.915Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777970078096_0PUd1Fgyu.webp" + }, + { + "timestamp": "2026-05-05T08:34:38.922Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777970078096_0PUd1Fgyu.webp" + }, + { + "timestamp": "2026-05-05T08:34:38.923Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777970078922.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1422610 + } + }, + { + "timestamp": "2026-05-05T08:34:39.340Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777970078922_IYuSWfJLs.webp" + }, + { + "timestamp": "2026-05-05T08:34:39.346Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777970078922_IYuSWfJLs.webp" + }, + { + "timestamp": "2026-05-05T08:34:39.347Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777970079346.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1376921 + } + }, + { + "timestamp": "2026-05-05T08:34:39.844Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777970079346_ZW6vvATTB.webp" + }, + { + "timestamp": "2026-05-05T08:36:26.218Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:36:26.219Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:36:26.219Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:36:26.219Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T08:36:26.219Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:36:26.219Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:36:26.219Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:36:26.219Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:36:26.219Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:36:33.514Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:36:34.440Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1201382 + }, + { + "timestamp": "2026-05-05T08:36:34.450Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:36:34.450Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T08:36:34.450Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:36:34.450Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:36:34.450Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:36:34.450Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:36:34.450Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:36:41.779Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:36:42.789Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1200207 + }, + { + "timestamp": "2026-05-05T08:36:42.800Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:36:42.800Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T08:36:42.800Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:36:42.800Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:36:42.800Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:36:42.800Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:36:42.800Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:36:50.301Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:36:51.126Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1345160 + }, + { + "timestamp": "2026-05-05T08:37:40.935Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "assets/ImageGin/ideogram-image_2048x1024_1777970194440.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "assets/ImageGin/ideogram-image_1024x1820_1777970202789.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "assets/ImageGin/ideogram-image_1024x1024_1777970211127.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:37:40.935Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:37:45.649Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777970265649.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1201908 + } + }, + { + "timestamp": "2026-05-05T08:37:46.680Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777970265649_t45lAqGFk.webp" + }, + { + "timestamp": "2026-05-05T08:37:46.685Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777970265649_t45lAqGFk.webp" + }, + { + "timestamp": "2026-05-05T08:37:46.687Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777970266686.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1200733 + } + }, + { + "timestamp": "2026-05-05T08:37:47.058Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777970266686_M_5jk8LQRW.webp" + }, + { + "timestamp": "2026-05-05T08:37:47.064Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777970266686_M_5jk8LQRW.webp" + }, + { + "timestamp": "2026-05-05T08:37:47.066Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777970267065.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1345686 + } + }, + { + "timestamp": "2026-05-05T08:37:48.593Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777970267065_k0ANvmndr0.webp" + }, + { + "timestamp": "2026-05-05T08:37:48.598Z", + "level": "info", + "message": "Successfully converted square_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777970267065_k0ANvmndr0.webp" + }, + { + "timestamp": "2026-05-05T08:39:39.394Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:39:39.394Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:39:39.394Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:39:39.394Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T08:39:39.394Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:39:39.395Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:39:39.395Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:39:39.395Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:39:39.395Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:39:47.158Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:39:48.065Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1320990 + }, + { + "timestamp": "2026-05-05T08:39:48.077Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:39:48.077Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T08:39:48.077Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:39:48.077Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:39:48.077Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:39:48.077Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:39:48.077Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:39:55.286Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:39:56.037Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1418352 + }, + { + "timestamp": "2026-05-05T08:39:56.047Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:39:56.047Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T08:39:56.047Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:39:56.047Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:39:56.047Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:39:56.047Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:39:56.047Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:40:03.518Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:40:04.285Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1696197 + }, + { + "timestamp": "2026-05-05T08:40:08.062Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "assets/ImageGin/ideogram-image_2048x1024_1777970388065.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "assets/ImageGin/ideogram-image_1024x1820_1777970396037.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "assets/ImageGin/ideogram-image_1024x1024_1777970404285.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:40:08.062Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:40:09.759Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777970409758.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1321516 + } + }, + { + "timestamp": "2026-05-05T08:40:10.562Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777970409758_9W0Tv78oy.webp" + }, + { + "timestamp": "2026-05-05T08:40:10.568Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777970409758_9W0Tv78oy.webp" + }, + { + "timestamp": "2026-05-05T08:40:10.569Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777970410569.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1418882 + } + }, + { + "timestamp": "2026-05-05T08:40:11.101Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777970410569_Ga3AJDRePf.webp" + }, + { + "timestamp": "2026-05-05T08:40:11.106Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777970410569_Ga3AJDRePf.webp" + }, + { + "timestamp": "2026-05-05T08:40:11.107Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777970411107.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1696719 + } + }, + { + "timestamp": "2026-05-05T08:40:11.827Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777970411107_0nl_9V2Ic.webp" + }, + { + "timestamp": "2026-05-05T08:40:11.831Z", + "level": "info", + "message": "Successfully converted square_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777970411107_0nl_9V2Ic.webp" + }, + { + "timestamp": "2026-05-05T08:41:09.269Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:41:09.270Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:41:09.270Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:41:09.270Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T08:41:09.270Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:41:09.270Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:41:09.270Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:41:09.270Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:41:09.270Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:41:18.010Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:41:19.017Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1235710 + }, + { + "timestamp": "2026-05-05T08:41:19.028Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:41:19.028Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T08:41:19.028Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:41:19.028Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:41:19.028Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:41:19.028Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:41:19.028Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:41:26.164Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:41:26.891Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1457008 + }, + { + "timestamp": "2026-05-05T08:41:26.901Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:41:26.902Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T08:41:26.902Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:41:26.902Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:41:26.902Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:41:26.902Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:41:26.902Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:41:34.159Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:41:34.980Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1224814 + }, + { + "timestamp": "2026-05-05T08:41:38.345Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "assets/ImageGin/ideogram-image_2048x1024_1777970479018.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "assets/ImageGin/ideogram-image_1024x1820_1777970486892.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "assets/ImageGin/ideogram-image_1024x1024_1777970494980.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:41:38.345Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:41:39.774Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777970499774.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1236236 + } + }, + { + "timestamp": "2026-05-05T08:41:40.418Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777970499774_IJBz5LJGS.webp" + }, + { + "timestamp": "2026-05-05T08:41:40.424Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777970499774_IJBz5LJGS.webp" + }, + { + "timestamp": "2026-05-05T08:41:40.425Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777970500425.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1457538 + } + }, + { + "timestamp": "2026-05-05T08:41:40.782Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777970500425_JwrBsHlq7.webp" + }, + { + "timestamp": "2026-05-05T08:41:40.788Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777970500425_JwrBsHlq7.webp" + }, + { + "timestamp": "2026-05-05T08:41:40.789Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777970500788.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1225340 + } + }, + { + "timestamp": "2026-05-05T08:41:41.104Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777970500788_Fm-02j3eo.webp" + }, + { + "timestamp": "2026-05-05T08:41:41.110Z", + "level": "info", + "message": "Successfully converted square_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777970500788_Fm-02j3eo.webp" + }, + { + "timestamp": "2026-05-05T08:42:33.528Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:42:33.528Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:42:33.528Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:42:33.528Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T08:42:33.528Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:42:33.528Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:42:33.529Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:42:33.529Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:42:33.529Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:42:41.101Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:42:41.995Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1274579 + }, + { + "timestamp": "2026-05-05T08:42:42.006Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:42:42.006Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T08:42:42.006Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:42:42.006Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:42:42.006Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:42:42.006Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:42:42.006Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:42:49.551Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:42:50.875Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1290378 + }, + { + "timestamp": "2026-05-05T08:42:50.886Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:42:50.886Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T08:42:50.886Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:42:50.886Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:42:50.886Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:42:50.886Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:42:50.887Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:42:58.341Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:42:59.255Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1274007 + }, + { + "timestamp": "2026-05-05T08:44:29.642Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "assets/ImageGin/ideogram-image_2048x1024_1777970561996.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "assets/ImageGin/ideogram-image_1024x1820_1777970570875.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "assets/ImageGin/ideogram-image_1024x1024_1777970579256.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:44:29.642Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:44:30.916Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777970670915.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1275101 + } + }, + { + "timestamp": "2026-05-05T08:44:31.819Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777970670915_cmLBqYRNJ.webp" + }, + { + "timestamp": "2026-05-05T08:44:31.825Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777970670915_cmLBqYRNJ.webp" + }, + { + "timestamp": "2026-05-05T08:44:31.826Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777970671826.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1290908 + } + }, + { + "timestamp": "2026-05-05T08:44:33.023Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777970671826_FHQXKzWCE.webp" + }, + { + "timestamp": "2026-05-05T08:44:33.028Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777970671826_FHQXKzWCE.webp" + }, + { + "timestamp": "2026-05-05T08:44:33.030Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777970673029.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1274529 + } + }, + { + "timestamp": "2026-05-05T08:44:33.408Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777970673029_Yq_ZGUKdm.webp" + }, + { + "timestamp": "2026-05-05T08:44:33.413Z", + "level": "info", + "message": "Successfully converted square_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777970673029_Yq_ZGUKdm.webp" + }, + { + "timestamp": "2026-05-05T08:45:26.335Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:45:26.336Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:45:26.336Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:45:26.336Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T08:45:26.336Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:45:26.336Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:45:26.336Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:45:26.336Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:45:26.336Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:45:34.061Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:45:34.859Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1206066 + }, + { + "timestamp": "2026-05-05T08:45:34.865Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:45:34.865Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T08:45:34.865Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:45:34.865Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:45:34.866Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:45:34.866Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:45:34.866Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:45:42.297Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:45:43.115Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1399264 + }, + { + "timestamp": "2026-05-05T08:45:43.127Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:45:43.128Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T08:45:43.128Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:45:43.128Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:45:43.128Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:45:43.128Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:45:43.128Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:45:50.987Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:45:51.906Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1370599 + }, + { + "timestamp": "2026-05-05T08:49:15.707Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:49:15.707Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T08:49:15.708Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:49:15.708Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T08:49:15.708Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:49:15.708Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:49:15.708Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:49:15.708Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:49:15.708Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:49:23.176Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:49:24.269Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1282052 + }, + { + "timestamp": "2026-05-05T08:49:24.282Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:49:24.282Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T08:49:24.282Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:49:24.282Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:49:24.282Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:49:24.282Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:49:24.282Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:49:31.598Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:49:32.414Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1251100 + }, + { + "timestamp": "2026-05-05T08:49:32.422Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T08:49:32.422Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T08:49:32.422Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T08:49:32.422Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T08:49:32.422Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T08:49:32.422Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T08:49:32.422Z", + "level": "info", + "message": "Prompt preview:", + "details": " Minimal isometric editorial illustration. Background: deep midnight-indigo g..." + }, + { + "timestamp": "2026-05-05T08:49:39.798Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T08:49:40.682Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1152407 + }, + { + "timestamp": "2026-05-05T08:50:38.331Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "Visuals/ImageGin/ideogram-image_2048x1024_1777970964269.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "Visuals/ImageGin/ideogram-image_1024x1820_1777970972414.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "Visuals/ImageGin/ideogram-image_1024x1024_1777970980682.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T08:50:38.331Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T08:50:39.672Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_banner_image_1777971039672.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1282574 + } + }, + { + "timestamp": "2026-05-05T08:50:40.765Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777971039672_tutuZgxHX.webp" + }, + { + "timestamp": "2026-05-05T08:50:40.772Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__banner_image_1777971039672_tutuZgxHX.webp" + }, + { + "timestamp": "2026-05-05T08:50:40.773Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_portrait_image_1777971040773.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1251626 + } + }, + { + "timestamp": "2026-05-05T08:50:41.120Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777971040773_eJ-f-6z8uI.webp" + }, + { + "timestamp": "2026-05-05T08:50:41.126Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__portrait_image_1777971040773_eJ-f-6z8uI.webp" + }, + { + "timestamp": "2026-05-05T08:50:41.127Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Content-Farm-(Obsidian-Community-Plugin)_square_image_1777971041127.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1152933 + } + }, + { + "timestamp": "2026-05-05T08:50:41.519Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Content-Farm-_Obsidian-Community-Plugin__square_image_1777971041127_DC1Dtn6z3.webp" + }, + { + "timestamp": "2026-05-05T20:43:21.219Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T20:43:21.220Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-05T20:43:21.221Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T20:43:21.221Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-05T20:43:21.221Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T20:43:21.221Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T20:43:21.221Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T20:43:21.221Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T20:43:21.221Z", + "level": "info", + "message": "Prompt preview:", + "details": "Editorial poster.\nConfident, asymmetrically-balanced composition with deliber..." + }, + { + "timestamp": "2026-05-05T20:43:28.490Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T20:43:29.068Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 680367 + }, + { + "timestamp": "2026-05-05T20:43:29.079Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T20:43:29.079Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-05T20:43:29.079Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T20:43:29.079Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T20:43:29.079Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T20:43:29.079Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T20:43:29.079Z", + "level": "info", + "message": "Prompt preview:", + "details": "Editorial poster.\nConfident, asymmetrically-balanced composition with deliber..." + }, + { + "timestamp": "2026-05-05T20:43:36.222Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T20:43:36.856Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 841896 + }, + { + "timestamp": "2026-05-05T20:43:36.867Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-05T20:43:36.867Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-05T20:43:36.867Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-05T20:43:36.867Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-05T20:43:36.867Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-05T20:43:36.867Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-05T20:43:36.867Z", + "level": "info", + "message": "Prompt preview:", + "details": "Editorial poster.\nConfident, asymmetrically-balanced composition with deliber..." + }, + { + "timestamp": "2026-05-05T20:43:44.028Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-05T20:43:44.647Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1007940 + }, + { + "timestamp": "2026-05-05T20:45:02.313Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "Visuals/ImageGin/ideogram-image_2048x1024_1778013809068.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "Visuals/ImageGin/ideogram-image_1024x1820_1778013816856.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "Visuals/ImageGin/ideogram-image_1024x1024_1778013824648.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-05T20:45:02.313Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-05T20:45:03.566Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Agent Development Kit_banner_image_1778013903566.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 680855 + } + }, + { + "timestamp": "2026-05-05T20:45:04.531Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Agent_Development_Kit_banner_image_1778013903566_nGqvMx_SX.webp" + }, + { + "timestamp": "2026-05-05T20:45:04.535Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Agent_Development_Kit_banner_image_1778013903566_nGqvMx_SX.webp" + }, + { + "timestamp": "2026-05-05T20:45:04.537Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Agent Development Kit_portrait_image_1778013904536.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 842388 + } + }, + { + "timestamp": "2026-05-05T20:45:04.823Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Agent_Development_Kit_portrait_image_1778013904536_GYatYiHaq.webp" + }, + { + "timestamp": "2026-05-05T20:45:04.828Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Agent_Development_Kit_portrait_image_1778013904536_GYatYiHaq.webp" + }, + { + "timestamp": "2026-05-05T20:45:04.829Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Agent Development Kit_square_image_1778013904829.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1008428 + } + }, + { + "timestamp": "2026-05-05T20:45:05.125Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Agent_Development_Kit_square_image_1778013904829_0Y_T9fanGF.webp" + }, + { + "timestamp": "2026-05-06T09:16:17.344Z", + "level": "info", + "message": "Found image properties:", + "details": [] + }, + { + "timestamp": "2026-05-06T09:16:17.344Z", + "level": "info", + "message": "Found markdown images:", + "details": [ + { + "path": "Visuals/Screenshot 2026-05-06 at 4.16.01 AM.png", + "match": "![[Visuals/Screenshot 2026-05-06 at 4.16.01 AM.png]]" + } + ] + }, + { + "timestamp": "2026-05-06T09:16:19.592Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Agent2Agent Protocol_content_1778058979591.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Agent2Agent Protocol,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 279006 + } + }, + { + "timestamp": "2026-05-06T09:16:20.235Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Agent2Agent_Protocol_content_1778058979591_TKcaO2Q9y.webp" + }, + { + "timestamp": "2026-05-06T09:16:20.235Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Agent2Agent_Protocol_content_1778058979591_TKcaO2Q9y.webp" + }, + { + "timestamp": "2026-05-06T10:14:25.272Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-06T10:14:25.272Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square" + ] + }, + { + "timestamp": "2026-05-06T10:14:25.272Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-06T10:14:25.272Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-06T10:14:25.272Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-06T10:14:25.272Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-06T10:14:25.272Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-06T10:14:25.272Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-06T10:14:25.272Z", + "level": "info", + "message": "Prompt preview:", + "details": "Editorial technical illustration in the lineage of a precision blueprint\ncuta..." + }, + { + "timestamp": "2026-05-06T10:14:32.905Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-06T10:14:34.242Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1235570 + }, + { + "timestamp": "2026-05-06T10:14:34.253Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-06T10:14:34.253Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-06T10:14:34.253Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-06T10:14:34.253Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-06T10:14:34.253Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-06T10:14:34.253Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-06T10:14:34.253Z", + "level": "info", + "message": "Prompt preview:", + "details": "Editorial technical illustration in the lineage of a precision blueprint\ncuta..." + }, + { + "timestamp": "2026-05-06T10:14:41.586Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-06T10:14:42.992Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1538381 + }, + { + "timestamp": "2026-05-06T10:14:43.004Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-06T10:14:43.004Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-06T10:14:43.004Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-06T10:14:43.004Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-06T10:14:43.004Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-06T10:14:43.004Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-06T10:14:43.004Z", + "level": "info", + "message": "Prompt preview:", + "details": "Editorial technical illustration in the lineage of a precision blueprint\ncuta..." + }, + { + "timestamp": "2026-05-06T10:14:51.730Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-06T10:14:52.352Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 955021 + }, + { + "timestamp": "2026-05-06T10:15:07.505Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "Visuals/ImageGin/ideogram-image_2048x1024_1778062474242.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "Visuals/ImageGin/ideogram-image_1024x1820_1778062482992.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "Visuals/ImageGin/ideogram-image_1024x1024_1778062492352.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-06T10:15:07.505Z", + "level": "info", + "message": "Found markdown images:", + "details": [] + }, + { + "timestamp": "2026-05-06T10:15:09.552Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "MemoPop AI_banner_image_1778062509552.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1236032 + } + }, + { + "timestamp": "2026-05-06T10:15:10.774Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MemoPop_AI_banner_image_1778062509552_xx6m5xkS4.webp" + }, + { + "timestamp": "2026-05-06T10:15:10.780Z", + "level": "info", + "message": "Successfully converted banner_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MemoPop_AI_banner_image_1778062509552_xx6m5xkS4.webp" + }, + { + "timestamp": "2026-05-06T10:15:10.781Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "MemoPop AI_portrait_image_1778062510781.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1538851 + } + }, + { + "timestamp": "2026-05-06T10:15:11.275Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MemoPop_AI_portrait_image_1778062510781_U9YgfZq12.webp" + }, + { + "timestamp": "2026-05-06T10:15:11.280Z", + "level": "info", + "message": "Successfully converted portrait_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MemoPop_AI_portrait_image_1778062510781_U9YgfZq12.webp" + }, + { + "timestamp": "2026-05-06T10:15:11.281Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "MemoPop AI_square_image_1778062511281.webp", + "folder": "/Image-Gin/2026-05", + "tags": "", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 955483 + } + }, + { + "timestamp": "2026-05-06T10:15:11.669Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MemoPop_AI_square_image_1778062511281_HsQ6nGdqD.webp" + }, + { + "timestamp": "2026-05-06T10:15:11.675Z", + "level": "info", + "message": "Successfully converted square_image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MemoPop_AI_square_image_1778062511281_HsQ6nGdqD.webp" + }, + { + "timestamp": "2026-05-06T10:23:58.124Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "og_image", + "value": "https://getviktor.com/images/viktor-og-image-5.png", + "isLocalFile": false + } + ] + }, + { + "timestamp": "2026-05-06T10:23:58.124Z", + "level": "info", + "message": "Found markdown images:", + "details": [ + { + "path": "Visuals/2026-05-06_Viktor_Screenshot_5.22.54 AM.png", + "match": "![[Visuals/2026-05-06_Viktor_Screenshot_5.22.54 AM.png]]" + } + ] + }, + { + "timestamp": "2026-05-06T10:24:07.291Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Viktor_content_1778063047291.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Viktor,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 2539509 + } + }, + { + "timestamp": "2026-05-06T10:24:08.645Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Viktor_content_1778063047291_gSPeQ6imG.webp" + }, + { + "timestamp": "2026-05-06T10:24:08.645Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Viktor_content_1778063047291_gSPeQ6imG.webp" + }, + { + "timestamp": "2026-05-07T01:28:47.393Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "og_image", + "value": "https://cdn.prod.website-files.com/6585dbcefaa9bc6771139c22/6616f0f548a8453eb39dbddd_640f988cbb946e8bffb0afc8_Aalo-OG.png", + "isLocalFile": false + } + ] + }, + { + "timestamp": "2026-05-07T01:28:47.393Z", + "level": "info", + "message": "Found markdown images:", + "details": [ + { + "path": "Visuals/Pasted image 20260506202834.png", + "match": "![[Visuals/Pasted image 20260506202834.png]]" + } + ] + }, + { + "timestamp": "2026-05-07T01:31:34.550Z", + "level": "info", + "message": "Found image properties:", + "details": [] + }, + { + "timestamp": "2026-05-07T01:31:34.550Z", + "level": "info", + "message": "Found markdown images:", + "details": [ + { + "path": "Visuals/20251212_AARRR_Pirate-Metrics-for-Startups.png", + "match": "![[Visuals/20251212_AARRR_Pirate-Metrics-for-Startups.png]]" + } + ] + }, + { + "timestamp": "2026-05-07T01:31:36.622Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Key Performance Indicators_content_1778117496622.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Key Performance Indicators,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 88201 + } + }, + { + "timestamp": "2026-05-07T01:31:37.232Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Key_Performance_Indicators_content_1778117496622_wzCAo1fzV.webp" + }, + { + "timestamp": "2026-05-07T01:31:37.232Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Key_Performance_Indicators_content_1778117496622_wzCAo1fzV.webp" + }, + { + "timestamp": "2026-05-07T06:06:43.944Z", + "level": "info", + "message": "Found image properties:", + "details": [] + }, + { + "timestamp": "2026-05-07T06:06:43.944Z", + "level": "info", + "message": "Found markdown images:", + "details": [ + { + "path": "Visuals/2026-05-07_Table-of-LLM-Agent-Orchestration-Frameworks_1.05.38 AM.png", + "match": "![[Visuals/2026-05-07_Table-of-LLM-Agent-Orchestration-Frameworks_1.05.38 AM.png]]" + } + ] + }, + { + "timestamp": "2026-05-07T06:06:45.386Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "An Empirical Study of Agent Developer Practices in AI Agent Frameworks_content_1778134005386.webp", + "folder": "/Image-Gin/2026-05", + "tags": "An Empirical Study of Agent Developer Practices in AI Agent Frameworks,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 210723 + } + }, + { + "timestamp": "2026-05-07T06:06:45.962Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/An_Empirical_Study_of_Agent_Developer_Practices_in_AI_Agent_Frameworks_content_1778134005386_VO-0M0VEB.webp" + }, + { + "timestamp": "2026-05-07T06:06:45.962Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/An_Empirical_Study_of_Agent_Developer_Practices_in_AI_Agent_Frameworks_content_1778134005386_VO-0M0VEB.webp" + }, + { + "timestamp": "2026-05-07T22:26:54.170Z", + "level": "info", + "message": "[Ideogram] toggle custom-1778192770886 -> true; selected:", + "details": [ + "banner", + "portrait", + "square", + "custom-1778192770886" + ] + }, + { + "timestamp": "2026-05-07T22:26:54.170Z", + "level": "info", + "message": "[Ideogram] master toggle -> true; selected:", + "details": [ + "banner", + "portrait", + "square", + "custom-1778192770886" + ] + }, + { + "timestamp": "2026-05-07T22:27:02.872Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square", + "custom-1778192770886" + ] + }, + { + "timestamp": "2026-05-07T22:27:02.872Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square", + "custom-1778192770886" + ] + }, + { + "timestamp": "2026-05-07T22:27:02.872Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-07T22:27:02.872Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-07T22:27:02.872Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-07T22:27:02.872Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-07T22:27:02.872Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-07T22:27:02.872Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-07T22:27:02.872Z", + "level": "info", + "message": "Prompt preview:", + "details": "A wide editorial illustration on a warm cream-paper background (#f8f3e8) with..." + }, + { + "timestamp": "2026-05-07T22:27:10.283Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-07T22:27:13.065Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1381500 + }, + { + "timestamp": "2026-05-07T22:27:13.079Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-07T22:27:13.079Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-07T22:27:13.079Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-07T22:27:13.079Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-07T22:27:13.079Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-07T22:27:13.079Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-07T22:27:13.079Z", + "level": "info", + "message": "Prompt preview:", + "details": "A wide editorial illustration on a warm cream-paper background (#f8f3e8) with..." + }, + { + "timestamp": "2026-05-07T22:27:21.110Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-07T22:27:22.445Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1448864 + }, + { + "timestamp": "2026-05-07T22:27:22.457Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-07T22:27:22.457Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-07T22:27:22.457Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-07T22:27:22.457Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-07T22:27:22.457Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-07T22:27:22.457Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-07T22:27:22.457Z", + "level": "info", + "message": "Prompt preview:", + "details": "A wide editorial illustration on a warm cream-paper background (#f8f3e8) with..." + }, + { + "timestamp": "2026-05-07T22:27:30.310Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-07T22:27:31.578Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1526447 + }, + { + "timestamp": "2026-05-07T22:27:31.591Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-07T22:27:31.591Z", + "level": "info", + "message": "Aspect ratio:", + "details": "4x3" + }, + { + "timestamp": "2026-05-07T22:27:31.591Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-07T22:27:31.591Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-07T22:27:31.591Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-07T22:27:31.591Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-07T22:27:31.591Z", + "level": "info", + "message": "Prompt preview:", + "details": "A wide editorial illustration on a warm cream-paper background (#f8f3e8) with..." + }, + { + "timestamp": "2026-05-07T22:27:39.042Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-07T22:27:40.312Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1435475 + }, + { + "timestamp": "2026-05-07T22:34:28.458Z", + "level": "info", + "message": "Found image properties:", + "details": [] + }, + { + "timestamp": "2026-05-07T22:34:28.458Z", + "level": "info", + "message": "Found markdown images:", + "details": [ + { + "path": "Visuals/ImageGin/Lossless-Flavored-Markdown_2048x1536.png", + "match": "![[Visuals/ImageGin/Lossless-Flavored-Markdown_2048x1536.png]]" + }, + { + "path": "Visuals/ImageGin/Lossless-Flavored-Markdown_1024x1024.png", + "match": "![[Visuals/ImageGin/Lossless-Flavored-Markdown_1024x1024.png]]" + }, + { + "path": "Visuals/ImageGin/Lossless-Flavored-Markdown_1024x1820.png", + "match": "![[Visuals/ImageGin/Lossless-Flavored-Markdown_1024x1820.png]]" + }, + { + "path": "Visuals/ImageGin/Lossless-Flavored-Markdown_2048x1024.png", + "match": "![[Visuals/ImageGin/Lossless-Flavored-Markdown_2048x1024.png]]" + } + ] + }, + { + "timestamp": "2026-05-07T22:34:30.621Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "MarkEdit_content_1778193270620.webp", + "folder": "/Image-Gin/2026-05", + "tags": "MarkEdit,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1436027 + } + }, + { + "timestamp": "2026-05-07T22:34:31.405Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MarkEdit_content_1778193270620_fcifqzmRQ.webp" + }, + { + "timestamp": "2026-05-07T22:34:31.405Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MarkEdit_content_1778193270620_fcifqzmRQ.webp" + }, + { + "timestamp": "2026-05-07T22:34:31.407Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "MarkEdit_content_1778193271406.webp", + "folder": "/Image-Gin/2026-05", + "tags": "MarkEdit,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1527004 + } + }, + { + "timestamp": "2026-05-07T22:34:31.776Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MarkEdit_content_1778193271406_w4BRdDH0U.webp" + }, + { + "timestamp": "2026-05-07T22:34:31.777Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MarkEdit_content_1778193271406_w4BRdDH0U.webp" + }, + { + "timestamp": "2026-05-07T22:34:31.778Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "MarkEdit_content_1778193271777.webp", + "folder": "/Image-Gin/2026-05", + "tags": "MarkEdit,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1449421 + } + }, + { + "timestamp": "2026-05-07T22:34:32.218Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MarkEdit_content_1778193271777_xYg9QdmkC.webp" + }, + { + "timestamp": "2026-05-07T22:34:32.218Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MarkEdit_content_1778193271777_xYg9QdmkC.webp" + }, + { + "timestamp": "2026-05-07T22:34:32.220Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "MarkEdit_content_1778193272219.webp", + "folder": "/Image-Gin/2026-05", + "tags": "MarkEdit,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1382062 + } + }, + { + "timestamp": "2026-05-07T22:34:32.982Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MarkEdit_content_1778193272219_FKDFzMbtq.webp" + }, + { + "timestamp": "2026-05-07T22:34:32.983Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/MarkEdit_content_1778193272219_FKDFzMbtq.webp" + }, + { + "timestamp": "2026-05-08T08:17:32.011Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square", + "custom-1778192770886" + ] + }, + { + "timestamp": "2026-05-08T08:17:32.011Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square", + "custom-1778192770886" + ] + }, + { + "timestamp": "2026-05-08T08:17:32.011Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:17:32.011Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-08T08:17:32.011Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:17:32.011Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:17:32.011Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:17:32.011Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:17:32.011Z", + "level": "info", + "message": "Prompt preview:", + "details": "A wide editorial-illustration style scene on a near-black ink background (#0a..." + }, + { + "timestamp": "2026-05-08T08:17:39.694Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:17:40.454Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1388392 + }, + { + "timestamp": "2026-05-08T08:17:40.465Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:17:40.465Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-08T08:17:40.465Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:17:40.465Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:17:40.465Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:17:40.465Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:17:40.465Z", + "level": "info", + "message": "Prompt preview:", + "details": "A wide editorial-illustration style scene on a near-black ink background (#0a..." + }, + { + "timestamp": "2026-05-08T08:17:47.920Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:17:48.825Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1419432 + }, + { + "timestamp": "2026-05-08T08:17:48.835Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:17:48.835Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-08T08:17:48.835Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:17:48.835Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:17:48.835Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:17:48.835Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:17:48.835Z", + "level": "info", + "message": "Prompt preview:", + "details": "A wide editorial-illustration style scene on a near-black ink background (#0a..." + }, + { + "timestamp": "2026-05-08T08:17:56.171Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:17:56.768Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1455628 + }, + { + "timestamp": "2026-05-08T08:17:56.779Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:17:56.779Z", + "level": "info", + "message": "Aspect ratio:", + "details": "4x3" + }, + { + "timestamp": "2026-05-08T08:17:56.779Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:17:56.779Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:17:56.779Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:17:56.779Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:17:56.779Z", + "level": "info", + "message": "Prompt preview:", + "details": "A wide editorial-illustration style scene on a near-black ink background (#0a..." + }, + { + "timestamp": "2026-05-08T08:18:05.690Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:18:06.565Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1426354 + }, + { + "timestamp": "2026-05-08T08:23:49.417Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "Visuals/ImageGin/ideogram-image_2048x1024_1778228260454.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "Visuals/ImageGin/ideogram-image_1024x1820_1778228268825.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "Visuals/ImageGin/ideogram-image_1024x1024_1778228276768.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-08T08:23:49.417Z", + "level": "info", + "message": "Found markdown images:", + "details": [ + { + "path": "Visuals/ogimage_Context-Vigilance_2048x1536.png", + "match": "![[Visuals/ogimage_Context-Vigilance_2048x1536.png]]" + }, + { + "path": "Visuals/ogimage_Context-Vigilance_1024x1024.png", + "match": "![[Visuals/ogimage_Context-Vigilance_1024x1024.png]]" + }, + { + "path": "Visuals/ogimage_Context-Vigilance_1024x1820.png", + "match": "![[Visuals/ogimage_Context-Vigilance_1024x1820.png]]" + }, + { + "path": "Visuals/ogimage_Context-Vigilance_2048x1024.png", + "match": "![[Visuals/ogimage_Context-Vigilance_2048x1024.png]]" + } + ] + }, + { + "timestamp": "2026-05-08T08:23:58.490Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Context-Vigilance_content_1778228638490.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Context-Vigilance,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1426933 + } + }, + { + "timestamp": "2026-05-08T08:23:59.394Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778228638490_kLfasPzC6.webp" + }, + { + "timestamp": "2026-05-08T08:23:59.395Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778228638490_kLfasPzC6.webp" + }, + { + "timestamp": "2026-05-08T08:23:59.396Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Context-Vigilance_content_1778228639396.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Context-Vigilance,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1456212 + } + }, + { + "timestamp": "2026-05-08T08:23:59.705Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778228639396_WetbQXBAD.webp" + }, + { + "timestamp": "2026-05-08T08:23:59.705Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778228639396_WetbQXBAD.webp" + }, + { + "timestamp": "2026-05-08T08:23:59.707Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Context-Vigilance_content_1778228639707.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Context-Vigilance,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1420011 + } + }, + { + "timestamp": "2026-05-08T08:24:00.032Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778228639707_ZpucZMAFC.webp" + }, + { + "timestamp": "2026-05-08T08:24:00.032Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778228639707_ZpucZMAFC.webp" + }, + { + "timestamp": "2026-05-08T08:24:00.034Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Context-Vigilance_content_1778228640033.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Context-Vigilance,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1388971 + } + }, + { + "timestamp": "2026-05-08T08:24:00.418Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778228640033_hkXIJ6TkP.webp" + }, + { + "timestamp": "2026-05-08T08:24:00.418Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778228640033_hkXIJ6TkP.webp" + }, + { + "timestamp": "2026-05-08T08:30:13.207Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square", + "custom-1778192770886" + ] + }, + { + "timestamp": "2026-05-08T08:30:13.207Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square", + "custom-1778192770886" + ] + }, + { + "timestamp": "2026-05-08T08:30:13.208Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:30:13.208Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-08T08:30:13.208Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:30:13.208Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:30:13.208Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:30:13.208Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:30:13.208Z", + "level": "info", + "message": "Prompt preview:", + "details": "Mood: late-night operator, retro-mechanical, embodied — a scene, not a patter..." + }, + { + "timestamp": "2026-05-08T08:30:20.627Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:30:21.226Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 824496 + }, + { + "timestamp": "2026-05-08T08:30:21.241Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:30:21.241Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-08T08:30:21.241Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:30:21.241Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:30:21.241Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:30:21.241Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:30:21.241Z", + "level": "info", + "message": "Prompt preview:", + "details": "Mood: late-night operator, retro-mechanical, embodied — a scene, not a patter..." + }, + { + "timestamp": "2026-05-08T08:30:29.166Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:30:29.770Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 919335 + }, + { + "timestamp": "2026-05-08T08:30:29.780Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:30:29.780Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-08T08:30:29.780Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:30:29.780Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:30:29.780Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:30:29.780Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:30:29.780Z", + "level": "info", + "message": "Prompt preview:", + "details": "Mood: late-night operator, retro-mechanical, embodied — a scene, not a patter..." + }, + { + "timestamp": "2026-05-08T08:30:38.897Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:30:39.629Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 1034386 + }, + { + "timestamp": "2026-05-08T08:30:39.638Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:30:39.638Z", + "level": "info", + "message": "Aspect ratio:", + "details": "4x3" + }, + { + "timestamp": "2026-05-08T08:30:39.638Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:30:39.638Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:30:39.638Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:30:39.638Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:30:39.638Z", + "level": "info", + "message": "Prompt preview:", + "details": "Mood: late-night operator, retro-mechanical, embodied — a scene, not a patter..." + }, + { + "timestamp": "2026-05-08T08:30:46.772Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:30:47.342Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 910874 + }, + { + "timestamp": "2026-05-08T08:35:58.505Z", + "level": "info", + "message": "Found image properties:", + "details": [ + { + "key": "banner_image", + "value": "Visuals/ImageGin/ideogram-image_2048x1024_1778229021227.png", + "isLocalFile": true + }, + { + "key": "portrait_image", + "value": "Visuals/ImageGin/ideogram-image_1024x1820_1778229029770.png", + "isLocalFile": true + }, + { + "key": "square_image", + "value": "Visuals/ImageGin/ideogram-image_1024x1024_1778229039629.png", + "isLocalFile": true + } + ] + }, + { + "timestamp": "2026-05-08T08:35:58.505Z", + "level": "info", + "message": "Found markdown images:", + "details": [ + { + "path": "Visuals/ImageGin/ideogram-image_1024x1024_1778229039629.png", + "match": "![[Visuals/ImageGin/ideogram-image_1024x1024_1778229039629.png]]" + }, + { + "path": "Visuals/ImageGin/ideogram-image_1024x1820_1778229029770.png", + "match": "![[Visuals/ImageGin/ideogram-image_1024x1820_1778229029770.png]]" + }, + { + "path": "Visuals/ImageGin/ideogram-image_2048x1024_1778229021227.png", + "match": "![[Visuals/ImageGin/ideogram-image_2048x1024_1778229021227.png]]" + }, + { + "path": "Visuals/ImageGin/ideogram-image_2048x1536_1778229047342.png", + "match": "![[Visuals/ImageGin/ideogram-image_2048x1536_1778229047342.png]]" + } + ] + }, + { + "timestamp": "2026-05-08T08:36:02.219Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Context-Vigilance_content_1778229362219.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Context-Vigilance,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1034975 + } + }, + { + "timestamp": "2026-05-08T08:36:03.099Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778229362219_DgcwjGOjp.webp" + }, + { + "timestamp": "2026-05-08T08:36:03.099Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778229362219_DgcwjGOjp.webp" + }, + { + "timestamp": "2026-05-08T08:36:03.100Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Context-Vigilance_content_1778229363099.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Context-Vigilance,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 919919 + } + }, + { + "timestamp": "2026-05-08T08:36:03.422Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778229363099_RzjkEokYw.webp" + }, + { + "timestamp": "2026-05-08T08:36:03.422Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778229363099_RzjkEokYw.webp" + }, + { + "timestamp": "2026-05-08T08:36:03.423Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Context-Vigilance_content_1778229363423.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Context-Vigilance,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 825075 + } + }, + { + "timestamp": "2026-05-08T08:36:03.700Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778229363423_ySoI0Y6iHQ.webp" + }, + { + "timestamp": "2026-05-08T08:36:03.700Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778229363423_ySoI0Y6iHQ.webp" + }, + { + "timestamp": "2026-05-08T08:36:03.701Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "Context-Vigilance_content_1778229363701.webp", + "folder": "/Image-Gin/2026-05", + "tags": "Context-Vigilance,markdown", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 911458 + } + }, + { + "timestamp": "2026-05-08T08:36:03.939Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778229363701_i0SN9bqAD.webp" + }, + { + "timestamp": "2026-05-08T08:36:03.939Z", + "level": "info", + "message": "Successfully converted markdown image: https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/Context-Vigilance_content_1778229363701_i0SN9bqAD.webp" + }, + { + "timestamp": "2026-05-08T08:37:58.876Z", + "level": "info", + "message": "[Ideogram] handleGenerate: selectedSizes =", + "details": [ + "banner", + "portrait", + "square", + "custom-1778192770886" + ] + }, + { + "timestamp": "2026-05-08T08:37:58.876Z", + "level": "info", + "message": "[Ideogram] handleGenerate: sizes to generate =", + "details": [ + "banner", + "portrait", + "square", + "custom-1778192770886" + ] + }, + { + "timestamp": "2026-05-08T08:37:58.876Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:37:58.876Z", + "level": "info", + "message": "Aspect ratio:", + "details": "2x1" + }, + { + "timestamp": "2026-05-08T08:37:58.876Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:37:58.876Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:37:58.876Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:37:58.876Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:37:58.876Z", + "level": "info", + "message": "Prompt preview:", + "details": "Right half of the canvas empty — uniform background, nothing on it. All subje..." + }, + { + "timestamp": "2026-05-08T08:38:07.143Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:38:07.653Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 629509 + }, + { + "timestamp": "2026-05-08T08:38:07.667Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:38:07.667Z", + "level": "info", + "message": "Aspect ratio:", + "details": "9x16" + }, + { + "timestamp": "2026-05-08T08:38:07.667Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:38:07.667Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:38:07.667Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:38:07.667Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:38:07.667Z", + "level": "info", + "message": "Prompt preview:", + "details": "Right half of the canvas empty — uniform background, nothing on it. All subje..." + }, + { + "timestamp": "2026-05-08T08:38:14.662Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:38:15.072Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 969003 + }, + { + "timestamp": "2026-05-08T08:38:15.080Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:38:15.080Z", + "level": "info", + "message": "Aspect ratio:", + "details": "1x1" + }, + { + "timestamp": "2026-05-08T08:38:15.080Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:38:15.080Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:38:15.080Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:38:15.080Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:38:15.080Z", + "level": "info", + "message": "Prompt preview:", + "details": "Right half of the canvas empty — uniform background, nothing on it. All subje..." + }, + { + "timestamp": "2026-05-08T08:38:22.088Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:38:22.480Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 611422 + }, + { + "timestamp": "2026-05-08T08:38:22.492Z", + "level": "info", + "message": "=== Ideogram Generate Request ===" + }, + { + "timestamp": "2026-05-08T08:38:22.492Z", + "level": "info", + "message": "Aspect ratio:", + "details": "4x3" + }, + { + "timestamp": "2026-05-08T08:38:22.492Z", + "level": "info", + "message": "Rendering speed:", + "details": "DEFAULT" + }, + { + "timestamp": "2026-05-08T08:38:22.492Z", + "level": "info", + "message": "Style type:", + "details": "GENERAL" + }, + { + "timestamp": "2026-05-08T08:38:22.492Z", + "level": "info", + "message": "Magic prompt:", + "details": "AUTO" + }, + { + "timestamp": "2026-05-08T08:38:22.492Z", + "level": "info", + "message": "Has negative prompt:", + "details": true + }, + { + "timestamp": "2026-05-08T08:38:22.492Z", + "level": "info", + "message": "Prompt preview:", + "details": "Right half of the canvas empty — uniform background, nothing on it. All subje..." + }, + { + "timestamp": "2026-05-08T08:38:29.283Z", + "level": "info", + "message": "Downloading Ideogram image from ephemeral URL" + }, + { + "timestamp": "2026-05-08T08:38:29.684Z", + "level": "info", + "message": "Downloaded image bytes:", + "details": 599190 + }, + { + "timestamp": "2026-05-09T06:33:57.674Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "2026-05-09_Cite-Wide-Inspect-Page-Citations-Modal_12.46.05 AM.webp", + "folder": "/Image-Gin/2026-05", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1479232 + } + }, + { + "timestamp": "2026-05-09T06:33:58.563Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/2026-05-09_Cite-Wide-Inspect-Page-Citations-Modal_12.46.05_AM_Vs9ge3e_W.webp" + }, + { + "timestamp": "2026-05-09T06:44:13.050Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "2026-05-08_Vercel_Code-Reviews_10.53.01 PM.webp", + "folder": "/Image-Gin/2026-05", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1294512 + } + }, + { + "timestamp": "2026-05-09T06:44:14.558Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/2026-05-08_Vercel_Code-Reviews_10.53.01_PM_hqsEJfEyO.webp" + }, + { + "timestamp": "2026-05-09T06:46:34.485Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "2026-05-08_Vercel-Agent_10.52.16 PM.webp", + "folder": "/Image-Gin/2026-05", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1564306 + } + }, + { + "timestamp": "2026-05-09T06:46:35.384Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/2026-05-08_Vercel-Agent_10.52.16_PM_qMinjnHCp.webp" + }, + { + "timestamp": "2026-05-09T06:48:10.425Z", + "level": "info", + "message": "Uploading to ImageKit:", + "details": { + "fileName": "2026-05-08_Login-to-OpenPanel_8.07.13 PM.webp", + "folder": "/Image-Gin/2026-05", + "endpoint": "https://upload.imagekit.io/api/v1/files/upload", + "contentLength": 1159154 + } + }, + { + "timestamp": "2026-05-09T06:48:11.283Z", + "level": "info", + "message": "ImageKit upload successful:", + "details": "https://ik.imagekit.io/xvpgfijuw/Image-Gin/2026-05/2026-05-08_Login-to-OpenPanel_8.07.13_PM_BxygpTZEQ.webp" } ] \ No newline at end of file diff --git a/main.ts b/main.ts index 0da0c67..3fb48d6 100644 --- a/main.ts +++ b/main.ts @@ -8,6 +8,11 @@ import { IdeogramModal } from './src/modals/IdeogramModal'; import type { ImageGinSettings} from './src/settings/settings'; import { ImageGinSettingTab, DEFAULT_SETTINGS } from './src/settings/settings'; import { logger } from './src/utils/logger'; +import { DropGateHandlers } from './src/handlers/DropGateHandlers'; +import type { DropGateDestination } from './src/destinations/types'; +import { VaultDestination } from './src/destinations/VaultDestination'; +import { ImageKitDestination } from './src/destinations/ImageKitDestination'; +import { ImgurDestination } from './src/destinations/ImgurDestination'; // Deep-merge `loaded` over `base` for plain objects only. Arrays are // replaced wholesale (so user-edited `imageSizes` overrides defaults @@ -37,6 +42,7 @@ function deepMergeSettings(base: T, loaded: unknown): T { export default class ImageGinPlugin extends Plugin { settings: ImageGinSettings = { ...DEFAULT_SETTINGS }; + private dropGateHandlers: DropGateHandlers | null = null; async loadSettings(): Promise { const loadedSettings = (await this.loadData()) ?? {}; @@ -112,5 +118,64 @@ export default class ImageGinPlugin extends Plugin { } } }); + + // ─── Drop / paste confirmation gate ──────────────────────── + // Intercepts every image dropped or pasted into a markdown view and + // pops a destination-picker modal before anything reaches disk or + // the network. See src/handlers/DropGateHandlers.ts. + const destinations: DropGateDestination[] = [ + new VaultDestination(this.app), + new ImageKitDestination(() => this.settings), + new ImgurDestination(() => this.settings), + ]; + this.dropGateHandlers = new DropGateHandlers(this, destinations); + + this.registerEvent(this.app.workspace.on('editor-drop', this.dropGateHandlers.onDrop)); + this.registerEvent(this.app.workspace.on('editor-paste', this.dropGateHandlers.onPaste)); + this.registerEvent( + this.app.workspace.on('active-leaf-change', () => this.dropGateHandlers?.resetSession()) + ); + + // Detect a conflicting third-party imgur plugin. Both plugins register + // editor-drop handlers and Obsidian fires every registered handler in + // turn — preventDefault() only blocks the browser default, not other + // plugins. So if obsidian-imgur-plugin is enabled, the user gets two + // modals: ours, then theirs. Warn once. + if (this.settings.dropGate.enabled) { + this.warnAboutConflictingImgurPlugin(); + } + + this.addCommand({ + id: 'image-gin-reset-drop-gate-session', + name: 'Drop Gate: Reset session-remembered destination', + callback: () => { + this.dropGateHandlers?.resetSession(); + new Notice('Image Gin: drop-gate session reset.'); + } + }); + } + + /** + * If obsidian-imgur-plugin is enabled, both plugins will fire on every + * image drop and the user gets two modals back-to-back. Surface a one- + * time, persistent Notice telling them to disable the other. + */ + private warnAboutConflictingImgurPlugin(): void { + const pluginsApi = (this.app as unknown as { + plugins?: { enabledPlugins?: Set; manifests?: Record }; + }).plugins; + + const enabled = pluginsApi?.enabledPlugins; + if (!enabled) return; + if (!enabled.has('obsidian-imgur-plugin')) return; + + new Notice( + 'Image Gin Drop Gate: the "Imgur" community plugin is also enabled. ' + + 'Both plugins handle image drops, so you will see two modals. ' + + 'Disable the Imgur community plugin in Settings → Community Plugins, ' + + 'then enable Image Gin\'s Imgur destination if you want Imgur uploads from inside the gate.', + 0 + ); + console.warn('[image-gin/drop-gate] obsidian-imgur-plugin is enabled — drop events will fire twice.'); } } \ No newline at end of file diff --git a/manifest.json b/manifest.json index 51faff2..263dfee 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "image-gin", "name": "Image Gin", - "version": "0.1.1", + "version": "0.2.0", "minAppVersion": "1.8.10", "description": "Generate AI images, search stock images, and upload to ImageKit CDN.", "author": "The Lossless Group", diff --git a/package.json b/package.json index feb6b1e..e755de9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "image-gin", - "version": "0.1.1", + "version": "0.2.0", "description": "Generate AI images, search stock images, and upload to ImageKit CDN.", "main": "main.js", "scripts": { diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3334c0e..05e49c5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1 +1,4 @@ packages: [] + +onlyBuiltDependencies: + - esbuild diff --git a/src/destinations/ImageKitDestination.ts b/src/destinations/ImageKitDestination.ts new file mode 100644 index 0000000..f97eead --- /dev/null +++ b/src/destinations/ImageKitDestination.ts @@ -0,0 +1,72 @@ +import { Notice } from 'obsidian'; +import type { DropGateContext, DropGateDestination } from './types'; +import type { ImageGinSettings } from '../settings/settings'; +import { ImageKitService } from '../services/imagekitService'; +import { fileNameFor } from '../utils/dropGateEvents'; + +/** + * Wraps the existing ImageKitService — the same multipart-upload pipeline + * image-gin already uses for its "Convert Local Images to Remote" command. + * + * Folder resolution: drop-gate-specific folder override + * (settings.dropGate.imageKitFolder) takes precedence; falls back to the + * main settings.imageKit.uploadFolder when blank. Both come straight from + * the user's settings — never hardcoded. + */ +export class ImageKitDestination implements DropGateDestination { + readonly id = 'imagekit' as const; + readonly label = 'ImageKit (private CDN)'; + + constructor(private readonly getSettings: () => ImageGinSettings) {} + + get description(): string { + const folder = this.resolveFolder(); + return folder + ? `Upload to ImageKit folder "${folder}". Private host.` + : 'Upload to ImageKit. Private host.'; + } + + isAvailable(): boolean { + const s = this.getSettings().imageKit; + return s.enabled && s.privateKey.length > 0 && s.uploadEndpoint.length > 0; + } + + async insert(files: readonly File[], ctx: DropGateContext): Promise { + const settings = this.getSettings(); + const service = new ImageKitService(settings); + const folder = this.resolveFolder(); + + let pos = ctx.insertPos; + for (const file of files) { + const buffer = await file.arrayBuffer(); + const result = await service.uploadFile(buffer, fileNameFor(file), folder); + + if (!result.url) { + console.error('[image-gin/drop-gate] ImageKit upload returned no url', result); + throw new Error('ImageKit returned an empty URL — check console for the raw response.'); + } + console.log(`[image-gin/drop-gate] ImageKit upload → ${result.url}`); + + const alt = file.name || 'image'; + const md = `![${alt}](${result.url})\n`; + ctx.editor.replaceRange(md, pos); + // Advance position past what we just inserted so multi-file + // batches stack instead of overwriting each other. + pos = { line: pos.line + 1, ch: 0 }; + } + // Move cursor to after the last inserted line and focus the editor. + ctx.editor.setCursor(pos); + ctx.editor.focus(); + + new Notice( + `Image Gin: uploaded ${files.length} image${files.length === 1 ? '' : 's'} to ImageKit${folder ? ` (${folder})` : ''}.` + ); + } + + private resolveFolder(): string { + const s = this.getSettings(); + const override = s.dropGate.imageKitFolder.trim(); + if (override) return override; + return s.imageKit.uploadFolder.trim(); + } +} diff --git a/src/destinations/ImgurDestination.ts b/src/destinations/ImgurDestination.ts new file mode 100644 index 0000000..dee0919 --- /dev/null +++ b/src/destinations/ImgurDestination.ts @@ -0,0 +1,87 @@ +import { Notice, requestUrl } from 'obsidian'; +import type { DropGateContext, DropGateDestination } from './types'; +import type { ImageGinSettings } from '../settings/settings'; + +const IMGUR_ENDPOINT = 'https://api.imgur.com/3/image'; + +interface ImgurResponse { + data?: { link?: string; deletehash?: string }; + success?: boolean; +} + +/** + * Anonymous Imgur uploader. Use for non-sensitive imagery where a free + * public CDN is fine and we don't need an account. + */ +export class ImgurDestination implements DropGateDestination { + readonly id = 'imgur' as const; + readonly label = 'Imgur (public CDN)'; + readonly description = 'Anonymous upload to imgur.com. Public. Use for non-sensitive imagery.'; + + constructor(private readonly getSettings: () => ImageGinSettings) {} + + isAvailable(): boolean { + const s = this.getSettings().imgur; + return s.enabled && s.clientId.length > 0; + } + + async insert(files: readonly File[], ctx: DropGateContext): Promise { + const { clientId } = this.getSettings().imgur; + + let pos = ctx.insertPos; + for (const file of files) { + const link = await this.upload(file, clientId); + console.log(`[image-gin/drop-gate] Imgur upload → ${link}`); + const alt = file.name || 'image'; + const md = `![${alt}](${link})\n`; + ctx.editor.replaceRange(md, pos); + pos = { line: pos.line + 1, ch: 0 }; + } + ctx.editor.setCursor(pos); + ctx.editor.focus(); + new Notice(`Image Gin: uploaded ${files.length} image${files.length === 1 ? '' : 's'} to Imgur.`); + } + + private async upload(file: File, clientId: string): Promise { + const boundary = '----image-gin-imgur-' + Math.random().toString(36).slice(2); + const lines: string[] = []; + lines.push(`--${boundary}`); + lines.push(`Content-Disposition: form-data; name="image"; filename="${file.name || 'image'}"`); + lines.push(`Content-Type: ${file.type || 'application/octet-stream'}`); + lines.push(''); + + const header = lines.join('\r\n') + '\r\n'; + const footer = `\r\n--${boundary}--\r\n`; + + const headerBytes = new TextEncoder().encode(header); + const footerBytes = new TextEncoder().encode(footer); + const fileBytes = new Uint8Array(await file.arrayBuffer()); + const totalLen = headerBytes.length + fileBytes.length + footerBytes.length; + const body = new Uint8Array(totalLen); + body.set(headerBytes, 0); + body.set(fileBytes, headerBytes.length); + body.set(footerBytes, headerBytes.length + fileBytes.length); + + const response = await requestUrl({ + url: IMGUR_ENDPOINT, + method: 'POST', + headers: { + Authorization: `Client-ID ${clientId}`, + 'Content-Type': `multipart/form-data; boundary=${boundary}`, + }, + body: body.buffer, + throw: false, + }); + + if (response.status !== 200) { + throw new Error(`Imgur upload failed (${response.status}): ${response.text}`); + } + + const data: ImgurResponse = (typeof response.json === 'function' ? await response.json() : response.json) as ImgurResponse; + const link = data.data?.link; + if (!link) { + throw new Error('Imgur response missing link.'); + } + return link; + } +} diff --git a/src/destinations/VaultDestination.ts b/src/destinations/VaultDestination.ts new file mode 100644 index 0000000..7c1fbab --- /dev/null +++ b/src/destinations/VaultDestination.ts @@ -0,0 +1,110 @@ +import type { App, TFile } from 'obsidian'; +import { Notice } from 'obsidian'; +import type { DropGateContext, DropGateDestination } from './types'; +import { DragEventCopy, PasteEventCopy, fileNameFor } from '../utils/dropGateEvents'; + +/** + * Internal-clipboard-manager surface — undocumented in obsidian.d.ts but + * stable in practice. If `clipboardManager` is missing at runtime we fall + * back to the explicit Vault.createBinary + FileManager.generateMarkdownLink + * path. + */ +interface InternalClipboardManager { + handleDrop(e: DragEvent): void; + handlePaste(e: ClipboardEvent): void; +} + +interface ViewModeWithClipboard { + clipboardManager?: InternalClipboardManager; +} + +export class VaultDestination implements DropGateDestination { + readonly id = 'vault' as const; + readonly label = 'Vault attachments'; + readonly description = 'Save into the vault. Default Obsidian behavior. Private.'; + + constructor(private readonly app: App) {} + + isAvailable(): boolean { + return true; + } + + async insert(files: readonly File[], ctx: DropGateContext): Promise { + const view = ctx.view; + const mode = view.currentMode as unknown as ViewModeWithClipboard; + const clip = mode.clipboardManager; + + if (clip) { + try { + if (ctx.originalEvent instanceof DragEvent) { + clip.handleDrop(DragEventCopy.create(ctx.originalEvent, files)); + } else { + clip.handlePaste(PasteEventCopy.create(ctx.originalEvent, files)); + } + return; + } catch (err) { + console.error('[image-gin/drop-gate] clipboardManager re-dispatch failed; falling back', err); + // fall through to explicit-API path + } + } + + await this.fallbackInsert(files, ctx); + } + + /** + * Explicit-API fallback if Obsidian's internal clipboardManager surface + * disappears. Slower (no built-in attachment-folder polish) but type-safe. + */ + private async fallbackInsert(files: readonly File[], ctx: DropGateContext): Promise { + const activeFile = this.app.workspace.getActiveFile(); + if (!activeFile) { + new Notice('Image Gin: no active file for vault drop.'); + return; + } + + const attachmentDir = this.attachmentDirFor(activeFile); + await this.ensureDir(attachmentDir); + + let pos = ctx.insertPos; + for (const file of files) { + const safeName = await this.uniqueFileName(attachmentDir, fileNameFor(file)); + const buffer = await file.arrayBuffer(); + const path = attachmentDir ? `${attachmentDir}/${safeName}` : safeName; + const written = await this.app.vault.createBinary(path, buffer); + const link = this.app.fileManager.generateMarkdownLink(written, activeFile.path); + const md = (link.startsWith('!') ? link : `!${link}`) + '\n'; + ctx.editor.replaceRange(md, pos); + pos = { line: pos.line + 1, ch: 0 }; + } + ctx.editor.setCursor(pos); + ctx.editor.focus(); + } + + private attachmentDirFor(_file: TFile): string { + const cfgRecord = (this.app.vault as unknown as { config?: { attachmentFolderPath?: string } }).config; + const cfg = cfgRecord?.attachmentFolderPath; + if (cfg && cfg !== '/' && cfg !== '') return cfg.replace(/^\/+|\/+$/g, ''); + return ''; + } + + private async ensureDir(dir: string): Promise { + if (!dir) return; + const exists = this.app.vault.getAbstractFileByPath(dir); + if (exists) return; + await this.app.vault.createFolder(dir); + } + + private async uniqueFileName(dir: string, name: string): Promise { + const dot = name.lastIndexOf('.'); + const base = dot > 0 ? name.substring(0, dot) : name; + const ext = dot > 0 ? name.substring(dot) : ''; + let candidate = name; + let counter = 1; + const prefix = dir ? `${dir}/` : ''; + while (this.app.vault.getAbstractFileByPath(`${prefix}${candidate}`)) { + candidate = `${base}-${counter}${ext}`; + counter++; + } + return candidate; + } +} diff --git a/src/destinations/types.ts b/src/destinations/types.ts new file mode 100644 index 0000000..8a72f3c --- /dev/null +++ b/src/destinations/types.ts @@ -0,0 +1,38 @@ +import type { Editor, EditorPosition, MarkdownView } from 'obsidian'; + +export type DropGateDestinationId = 'vault' | 'imagekit' | 'imgur'; + +export interface DropGateContext { + editor: Editor; + view: MarkdownView; + /** + * The original event. Vault destination uses this to re-dispatch a + * synthetic copy into Obsidian's internal clipboardManager. Hosted + * destinations don't need it. + */ + originalEvent: DragEvent | ClipboardEvent; + /** + * Cursor position captured synchronously when the drop/paste fired. + * Hosted destinations insert their markdown link at this position via + * editor.replaceRange — using replaceSelection instead would insert at + * whatever the cursor became while the modal was open, which is rarely + * where the user dropped the image. + */ + insertPos: EditorPosition; +} + +export interface DropGateDestination { + id: DropGateDestinationId; + label: string; + description: string; + /** + * True if this destination is configured and usable. Disabled destinations + * are hidden from the modal. + */ + isAvailable(): boolean; + /** + * Process and insert the supplied images into the active document. Throw + * on failure; the handler will surface a Notice. + */ + insert(files: readonly File[], ctx: DropGateContext): Promise; +} diff --git a/src/handlers/DropGateHandlers.ts b/src/handlers/DropGateHandlers.ts new file mode 100644 index 0000000..3332f03 --- /dev/null +++ b/src/handlers/DropGateHandlers.ts @@ -0,0 +1,185 @@ +import type { Editor, EditorPosition, MarkdownFileInfo, MarkdownView } from 'obsidian'; +import { MarkdownView as MarkdownViewClass, Notice } from 'obsidian'; +import type { DropGateDestination, DropGateDestinationId } from '../destinations/types'; +import { DropGateModal } from '../modals/DropGateModal'; +import { + DragEventCopy, + PasteEventCopy, + allFilesAreImages, + imagesIn, +} from '../utils/dropGateEvents'; +import type ImageGinPlugin from '../../main'; + +function asMarkdownView(info: MarkdownView | MarkdownFileInfo): MarkdownView | null { + return info instanceof MarkdownViewClass ? info : null; +} + +interface SessionState { + rememberedDestination: DropGateDestinationId | null; +} + +export class DropGateHandlers { + private session: SessionState = { rememberedDestination: null }; + + constructor( + private readonly plugin: ImageGinPlugin, + private readonly destinations: readonly DropGateDestination[], + ) {} + + /** Reset the per-session "remember choice" — wired to active-leaf-change. */ + resetSession(): void { + this.session.rememberedDestination = null; + } + + onDrop = (e: DragEvent, editor: Editor, info: MarkdownView | MarkdownFileInfo): void => { + // Re-entry guard. The synthetic copy our own VaultDestination + // dispatches would otherwise loop back through here. + if (e instanceof DragEventCopy) return; + + const view = asMarkdownView(info); + if (!view) return; // Canvas etc.: out of scope. + + const settings = this.plugin.settings.dropGate; + if (!settings.enabled) return; + + const files = Array.from(e.dataTransfer?.files ?? []); + if (!allFilesAreImages(files)) return; + + // Synchronous preventDefault — must come before any await. + e.preventDefault(); + + // Capture cursor NOW. After awaiting the modal, the cursor reported + // by editor.getCursor() may be stale or somewhere else entirely + // (the user could have clicked, the editor could have lost focus). + const insertPos = editor.getCursor(); + + if (settings.policyMode === 'external-only' && !this.anyExternalAvailable()) { + this.dispatchVault(e, files, editor, view, insertPos); + return; + } + + // Floating promise on purpose — the handler must return synchronously. + void this.gate(e, files, editor, view, insertPos); + }; + + onPaste = (e: ClipboardEvent, editor: Editor, info: MarkdownView | MarkdownFileInfo): void => { + if (e instanceof PasteEventCopy) return; + + const view = asMarkdownView(info); + if (!view) return; + + const settings = this.plugin.settings.dropGate; + if (!settings.enabled) return; + + const files = Array.from(e.clipboardData?.files ?? []); + if (!allFilesAreImages(files)) return; + + e.preventDefault(); + + const insertPos = editor.getCursor(); + + if (settings.policyMode === 'external-only' && !this.anyExternalAvailable()) { + this.dispatchVault(e, files, editor, view, insertPos); + return; + } + + void this.gate(e, files, editor, view, insertPos); + }; + + private async gate( + originalEvent: DragEvent | ClipboardEvent, + files: File[], + editor: Editor, + view: MarkdownView, + insertPos: EditorPosition, + ): Promise { + const images = imagesIn(files); + const settings = this.plugin.settings.dropGate; + + const remembered = this.session.rememberedDestination; + if (remembered) { + const dest = this.destinations.find((d) => d.id === remembered); + if (dest && dest.isAvailable()) { + await this.runDestination(dest, images, originalEvent, editor, view, insertPos); + return; + } + this.session.rememberedDestination = null; + } + + const available = this.destinations.filter((d) => d.isAvailable()); + const defaultId = available.find((d) => d.id === settings.defaultDestination) + ? settings.defaultDestination + : (available[0]?.id ?? 'vault'); + + // Show every destination in the modal — disabled ones included — so + // users see what's possible and don't get a separate plugin's modal + // for an option that "should have been here." Per-destination hint + // tells them why a row is greyed out. + const imgurEnabled = this.plugin.settings.imgur.enabled; + const imageKitEnabled = this.plugin.settings.imageKit.enabled; + const unavailableHints = { + imagekit: !imageKitEnabled + ? 'Enable ImageKit and add a private key in settings.' + : 'Missing private key or upload endpoint in settings.', + imgur: !imgurEnabled + ? 'Enable Imgur and add a client ID in settings.' + : 'Missing Imgur client ID in settings.', + }; + + const modal = new DropGateModal(this.plugin.app, { + files: images, + destinations: this.destinations, + unavailableHints, + defaultDestinationId: defaultId, + showRememberToggle: settings.rememberSessionChoice, + }); + + const choice = await modal.ask(); + if (choice.kind === 'cancel') return; + if (!choice.destinationId) return; + + const dest = this.destinations.find((d) => d.id === choice.destinationId); + if (!dest || !dest.isAvailable()) return; + + if (choice.rememberForSession) { + this.session.rememberedDestination = dest.id; + } + + await this.runDestination(dest, images, originalEvent, editor, view, insertPos); + } + + private async runDestination( + dest: DropGateDestination, + files: File[], + originalEvent: DragEvent | ClipboardEvent, + editor: Editor, + view: MarkdownView, + insertPos: EditorPosition, + ): Promise { + try { + await dest.insert(files, { editor, view, originalEvent, insertPos }); + } catch (err) { + console.error(`[image-gin/drop-gate] ${dest.id} destination failed`, err); + const msg = err instanceof Error ? err.message : String(err); + new Notice(`Image Gin (${dest.label}): ${msg}`); + // Do NOT silently fall back to vault — the user picked this + // destination deliberately. Surface the error and stop. + } + } + + private dispatchVault( + originalEvent: DragEvent | ClipboardEvent, + files: File[], + editor: Editor, + view: MarkdownView, + insertPos: EditorPosition, + ): void { + const vault = this.destinations.find((d) => d.id === 'vault'); + if (!vault) return; + void vault.insert(files, { editor, view, originalEvent, insertPos }); + } + + private anyExternalAvailable(): boolean { + return this.destinations.some((d) => d.id !== 'vault' && d.isAvailable()); + } +} diff --git a/src/modals/ConvertLocalImagesForCurrentFile.ts b/src/modals/ConvertLocalImagesForCurrentFile.ts index 907f211..f8b37a2 100644 --- a/src/modals/ConvertLocalImagesForCurrentFile.ts +++ b/src/modals/ConvertLocalImagesForCurrentFile.ts @@ -1,6 +1,7 @@ import { logger } from '../utils/logger'; import type { App, TFile } from 'obsidian'; import { Modal, Setting, Notice, FileSystemAdapter } from 'obsidian'; +import type { ToggleComponent } from 'obsidian'; import type ImageGinPlugin from '../../main'; import { ImageKitService } from '../services/imagekitService'; import { readFileSync } from 'fs'; @@ -22,6 +23,14 @@ export class ConvertLocalImagesForCurrentFile extends Modal { private selectedProperties: Set = new Set(); private selectedMarkdownImages: Set = new Set(); + // Refs for header "All" master toggles and the per-row toggles they + // command, mirroring the IdeogramModal pattern so master and rows + // stay in sync when either is clicked. + private fmMasterToggle: ToggleComponent | null = null; + private fmRowToggles: Map = new Map(); + private mdMasterToggle: ToggleComponent | null = null; + private mdRowToggles: Map = new Map(); + // Common image properties to check private readonly IMAGE_PROPERTIES = [ 'banner_image', @@ -112,6 +121,19 @@ export class ConvertLocalImagesForCurrentFile extends Modal { } } + // Pre-select every eligible row. The common flow is "convert + // everything in this file" (banner + portrait + square, plus + // any inline markdown images), so default-on removes the + // three-clicks-every-time friction the user hit. Already-remote + // frontmatter URLs stay deselected because their toggle is + // disabled regardless. + for (const property of this.imageProperties) { + if (property.isLocalFile) this.selectedProperties.add(property.key); + } + for (const md of this.markdownImagePaths) { + this.selectedMarkdownImages.add(md.path); + } + logger.info('Found image properties:', this.imageProperties); logger.info('Found markdown images:', this.markdownImagePaths); } catch (error) { @@ -155,19 +177,11 @@ export class ConvertLocalImagesForCurrentFile extends Modal { // Frontmatter image properties list if (this.imageProperties.length > 0) { - contentEl.createEl('h3', { - text: 'Frontmatter Images', - cls: 'image-gin-section-header' - }); this.renderImagePropertiesList(contentEl); } // Markdown content images list if (this.markdownImagePaths.length > 0) { - contentEl.createEl('h3', { - text: 'Markdown Content Images', - cls: 'image-gin-section-header' - }); this.renderMarkdownImagesList(contentEl); } @@ -178,12 +192,80 @@ export class ConvertLocalImagesForCurrentFile extends Modal { this.renderConvertButton(contentEl); } + /** + * Build a section header (label + right-aligned "All" master toggle) + * matching the IdeogramModal Image Sizes section. Returns the toggle + * component so the caller can wire it up to its row toggles. + */ + private renderSectionHeaderWithMasterToggle( + section: HTMLElement, + title: string, + initialAllSelected: boolean, + onMasterChange: (value: boolean) => void + ): ToggleComponent | null { + const header = section.createDiv('image-gin-section-header'); + header.style.display = 'flex'; + header.style.justifyContent = 'space-between'; + header.style.alignItems = 'center'; + header.createEl('span', { text: title }); + + const masterWrap = header.createDiv(); + masterWrap.style.display = 'flex'; + masterWrap.style.alignItems = 'center'; + masterWrap.style.gap = '0.5rem'; + masterWrap.createEl('span', { + text: 'All', + attr: { style: 'font-size: 0.85em; opacity: 0.75;' }, + }); + + let captured: ToggleComponent | null = null; + new Setting(masterWrap) + .setClass('image-gin-master-toggle') + .addToggle(toggle => { + captured = toggle; + toggle.setValue(initialAllSelected); + toggle.setTooltip(`Toggle all ${title.toLowerCase()} on/off`); + toggle.onChange(onMasterChange); + }); + // Strip the Setting component's left-info column so the toggle + // hugs the header's right edge (matches IdeogramModal). + masterWrap.querySelectorAll('.setting-item-info').forEach(el => el.remove()); + + return captured; + } + private renderImagePropertiesList(containerEl: HTMLElement): void { - const listContainer = containerEl.createDiv('image-gin-properties-list'); + const section = containerEl.createDiv('image-gin-section'); + + // Eligible = local files; already-ImageKit URLs are excluded from + // the master toggle's universe so flipping master-on never tries + // to "select" an already-converted entry. + const eligible = this.imageProperties.filter(p => p.isLocalFile); + + this.fmMasterToggle = this.renderSectionHeaderWithMasterToggle( + section, + 'Frontmatter Images', + this.areAllFmEligibleSelected(), + (value) => { + if (value) { + for (const p of eligible) this.selectedProperties.add(p.key); + } else { + for (const p of eligible) this.selectedProperties.delete(p.key); + } + for (const p of eligible) { + const t = this.fmRowToggles.get(p.key); + if (t) t.setValue(this.selectedProperties.has(p.key)); + } + this.updateConvertButtonState(); + } + ); + + const content = section.createDiv('image-gin-section-content'); + const toggleGroup = content.createDiv('image-gin-toggle-group'); this.imageProperties.forEach((prop) => { - const itemEl = listContainer.createDiv('image-gin-property-item'); - + const itemEl = toggleGroup.createDiv('image-gin-toggle-item'); + const isAlreadyImageKit = !prop.isLocalFile; const statusClass = isAlreadyImageKit ? 'already-imagekit' : 'local-file'; itemEl.addClass(statusClass); @@ -192,6 +274,7 @@ export class ConvertLocalImagesForCurrentFile extends Modal { .setName(prop.key) .setDesc(`${prop.value} ${isAlreadyImageKit ? '(Already ImageKit URL)' : '(Local file)'}`) .addToggle(toggle => { + if (!isAlreadyImageKit) this.fmRowToggles.set(prop.key, toggle); toggle.setValue(prop.isLocalFile && this.selectedProperties.has(prop.key)); toggle.setDisabled(isAlreadyImageKit); toggle.onChange((value) => { @@ -200,12 +283,26 @@ export class ConvertLocalImagesForCurrentFile extends Modal { } else { this.selectedProperties.delete(prop.key); } + if (this.fmMasterToggle) { + this.fmMasterToggle.setValue(this.areAllFmEligibleSelected()); + } this.updateConvertButtonState(); }); }); }); } + private areAllFmEligibleSelected(): boolean { + const eligible = this.imageProperties.filter(p => p.isLocalFile); + if (eligible.length === 0) return false; + return eligible.every(p => this.selectedProperties.has(p.key)); + } + + private areAllMdSelected(): boolean { + if (this.markdownImagePaths.length === 0) return false; + return this.markdownImagePaths.every(m => this.selectedMarkdownImages.has(m.path)); + } + private renderProgressSection(containerEl: HTMLElement): void { this.progressEl = containerEl.createDiv('image-gin-progress'); this.progressEl.style.display = 'none'; @@ -217,15 +314,36 @@ export class ConvertLocalImagesForCurrentFile extends Modal { } private renderMarkdownImagesList(containerEl: HTMLElement): void { - const listContainer = containerEl.createDiv('image-gin-markdown-images-list'); + const section = containerEl.createDiv('image-gin-section'); + + this.mdMasterToggle = this.renderSectionHeaderWithMasterToggle( + section, + 'Markdown Content Images', + this.areAllMdSelected(), + (value) => { + if (value) { + for (const m of this.markdownImagePaths) this.selectedMarkdownImages.add(m.path); + } else { + this.selectedMarkdownImages.clear(); + } + for (const [path, t] of this.mdRowToggles) { + t.setValue(this.selectedMarkdownImages.has(path)); + } + this.updateConvertButtonState(); + } + ); + + const content = section.createDiv('image-gin-section-content'); + const toggleGroup = content.createDiv('image-gin-toggle-group'); this.markdownImagePaths.forEach((image, index) => { - const itemEl = listContainer.createDiv('image-gin-markdown-item'); - + const itemEl = toggleGroup.createDiv('image-gin-toggle-item'); + new Setting(itemEl) .setName(`Image ${index + 1}: ${image.path}`) .setDesc(`Found in markdown content`) .addToggle(toggle => { + this.mdRowToggles.set(image.path, toggle); toggle.setValue(this.selectedMarkdownImages.has(image.path)); toggle.onChange((value) => { if (value) { @@ -233,6 +351,9 @@ export class ConvertLocalImagesForCurrentFile extends Modal { } else { this.selectedMarkdownImages.delete(image.path); } + if (this.mdMasterToggle) { + this.mdMasterToggle.setValue(this.areAllMdSelected()); + } this.updateConvertButtonState(); }); }); diff --git a/src/modals/DropGateModal.ts b/src/modals/DropGateModal.ts new file mode 100644 index 0000000..dfa3008 --- /dev/null +++ b/src/modals/DropGateModal.ts @@ -0,0 +1,132 @@ +import type { App } from 'obsidian'; +import { Modal } from 'obsidian'; +import type { DropGateDestination, DropGateDestinationId } from '../destinations/types'; +import { formatFileSize } from '../utils/dropGateEvents'; + +export interface DropGateChoice { + kind: 'destination' | 'cancel'; + destinationId?: DropGateDestinationId; + rememberForSession: boolean; +} + +interface DialogOptions { + files: readonly File[]; + /** All destinations in display order. Each carries its own isAvailable(). */ + destinations: readonly DropGateDestination[]; + /** Per-destination message shown under the label when isAvailable() is false. */ + unavailableHints: Partial>; + defaultDestinationId: DropGateDestinationId; + showRememberToggle: boolean; +} + +export class DropGateModal extends Modal { + private readonly opts: DialogOptions; + private resolveChoice: ((c: DropGateChoice) => void) | null = null; + private selectedId: DropGateDestinationId; + private rememberForSession = false; + + constructor(app: App, opts: DialogOptions) { + super(app); + this.opts = opts; + this.selectedId = opts.defaultDestinationId; + } + + ask(): Promise { + return new Promise((resolve) => { + this.resolveChoice = resolve; + this.open(); + }); + } + + onOpen(): void { + const { contentEl, modalEl } = this; + // Attach the styling class to modalEl so width rules apply to the + // OUTER popup. See perplexed widening reminder. + modalEl.addClass('image-gin-drop-gate-modal'); + contentEl.empty(); + + const header = contentEl.createDiv('idg-header'); + const title = this.opts.files.length === 1 + ? 'Image dropped' + : `${this.opts.files.length} images dropped`; + header.createEl('h2', { text: title, cls: 'idg-title' }); + header.createEl('p', { text: 'Where should this go?', cls: 'idg-subtitle' }); + + const fileList = contentEl.createDiv('idg-file-list'); + for (const f of this.opts.files) { + const row = fileList.createDiv('idg-file-row'); + row.createEl('span', { text: f.name || 'image', cls: 'idg-file-name' }); + const meta: string[] = []; + if (f.size > 0) meta.push(formatFileSize(f.size)); + const t = f.type.replace(/^image\//, ''); + if (t) meta.push(t); + row.createEl('span', { text: meta.join(' · '), cls: 'idg-file-meta' }); + } + + const destList = contentEl.createDiv('idg-destinations'); + for (const dest of this.opts.destinations) { + const available = dest.isAvailable(); + const row = destList.createEl('label', { + cls: `idg-destination${available ? '' : ' idg-destination-disabled'}`, + }); + const radio = row.createEl('input', { + type: 'radio', + attr: { name: 'idg-destination', value: dest.id }, + }); + radio.checked = available && dest.id === this.selectedId; + radio.disabled = !available; + radio.addEventListener('change', () => { + if (radio.checked) this.selectedId = dest.id; + }); + + const text = row.createDiv('idg-destination-text'); + text.createEl('div', { text: dest.label, cls: 'idg-destination-label' }); + text.createEl('div', { text: dest.description, cls: 'idg-destination-desc' }); + if (!available) { + const hint = this.opts.unavailableHints[dest.id] + ?? 'Not configured. Enable and configure in image-gin settings.'; + text.createEl('div', { text: hint, cls: 'idg-destination-hint' }); + } + } + + if (this.opts.showRememberToggle) { + const rememberRow = contentEl.createEl('label', { cls: 'idg-remember' }); + const cb = rememberRow.createEl('input', { type: 'checkbox' }); + cb.addEventListener('change', () => { + this.rememberForSession = cb.checked; + }); + rememberRow.createEl('span', { text: 'Remember choice for this session' }); + } + + const footer = contentEl.createDiv('idg-footer'); + const cancelBtn = footer.createEl('button', { text: 'Cancel', cls: 'idg-cancel' }); + cancelBtn.addEventListener('click', () => this.finish({ kind: 'cancel', rememberForSession: false })); + + const insertBtn = footer.createEl('button', { text: 'Insert', cls: 'mod-cta idg-insert' }); + insertBtn.addEventListener('click', () => this.finish({ + kind: 'destination', + destinationId: this.selectedId, + rememberForSession: this.rememberForSession, + })); + + setTimeout(() => insertBtn.focus(), 0); + } + + onClose(): void { + const { contentEl, modalEl } = this; + modalEl.removeClass('image-gin-drop-gate-modal'); + contentEl.empty(); + if (this.resolveChoice) { + const r = this.resolveChoice; + this.resolveChoice = null; + r({ kind: 'cancel', rememberForSession: false }); + } + } + + private finish(choice: DropGateChoice): void { + const r = this.resolveChoice; + this.resolveChoice = null; + this.close(); + if (r) r(choice); + } +} diff --git a/src/settings/settings.ts b/src/settings/settings.ts index f11ef20..78ef6ac 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -148,6 +148,26 @@ export interface ImageCacheSettings { cleanupDays: number; } +export interface ImgurSettings { + enabled: boolean; + clientId: string; +} + +export type DropGatePolicyMode = 'always-confirm' | 'external-only'; + +export interface DropGateSettings { + enabled: boolean; + policyMode: DropGatePolicyMode; + defaultDestination: 'vault' | 'imagekit' | 'imgur'; + rememberSessionChoice: boolean; + /** + * Override folder for drop-gate uploads to ImageKit. When empty, falls + * back to the main imageKit.uploadFolder. Lets the user route ad-hoc + * dropped images somewhere different from generated images. + */ + imageKitFolder: string; +} + export interface ImageGinSettings { recraftApiKey: string; recraftBaseUrl: string; @@ -162,9 +182,11 @@ export interface ImageGinSettings { imageStylesJSON: string; imageOutputFolder: string; imageKit: ImageKitSettings; + imgur: ImgurSettings; magnific: MagnificSettings; ideogram: IdeogramSettings; imageCache: ImageCacheSettings; + dropGate: DropGateSettings; recraftLastSession: RecraftSessionState; } @@ -248,6 +270,17 @@ export const DEFAULT_SETTINGS: ImageGinSettings = { autoCleanup: true, cleanupDays: 30, }, + imgur: { + enabled: false, + clientId: '', + }, + dropGate: { + enabled: false, + policyMode: 'always-confirm', + defaultDestination: 'vault', + rememberSessionChoice: true, + imageKitFolder: '', + }, recraftLastSession: { selectedSizes: [], writeToFrontmatter: true, @@ -880,6 +913,104 @@ export class ImageGinSettingTab extends PluginSettingTab { // Load and display cache stats void this.loadCacheStats(statsDiv); } + + // ─── Drop Gate ────────────────────────────────────────────── + containerEl.createEl('h3', { text: 'Drag-Drop / Paste Confirmation Gate' }); + containerEl.createEl('p', { + text: 'When enabled, every image dropped or pasted into a note opens a confirmation modal asking where it should go: vault attachments, ImageKit, or Imgur. Built for writers who handle private client imagery and want every image destination to be a deliberate decision.', + cls: 'image-gin-settings-blurb', + }); + + new Setting(containerEl) + .setName('Enable drop gate') + .setDesc('Intercept image drops and pastes; show the confirmation modal.') + .addToggle((t) => + t.setValue(this.plugin.settings.dropGate.enabled).onChange(async (v) => { + this.plugin.settings.dropGate.enabled = v; + await this.plugin.saveSettings(); + this.display(); + }) + ); + + if (this.plugin.settings.dropGate.enabled) { + new Setting(containerEl) + .setName('Policy mode') + .setDesc('When should the gate intercept?') + .addDropdown((dd) => { + dd.addOption('always-confirm', 'Always confirm'); + dd.addOption('external-only', 'Confirm only if an external destination is enabled'); + dd.setValue(this.plugin.settings.dropGate.policyMode); + dd.onChange(async (v) => { + this.plugin.settings.dropGate.policyMode = v as DropGatePolicyMode; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('Default destination') + .setDesc('Pre-selected when the modal opens.') + .addDropdown((dd) => { + dd.addOption('vault', 'Vault attachments'); + dd.addOption('imagekit', 'ImageKit (private CDN)'); + dd.addOption('imgur', 'Imgur (public CDN)'); + dd.setValue(this.plugin.settings.dropGate.defaultDestination); + dd.onChange(async (v) => { + this.plugin.settings.dropGate.defaultDestination = + v as DropGateSettings['defaultDestination']; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName('Show "remember for session" checkbox') + .setDesc('Lets the user skip the modal for the current note. Never persists across Obsidian restarts.') + .addToggle((t) => + t.setValue(this.plugin.settings.dropGate.rememberSessionChoice).onChange(async (v) => { + this.plugin.settings.dropGate.rememberSessionChoice = v; + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName('ImageKit folder for drop-gate uploads') + .setDesc( + `Folder path on ImageKit where dropped/pasted images go. Leave blank to use the main ImageKit upload folder ("${this.plugin.settings.imageKit.uploadFolder || '(unset)'}").` + ) + .addText((t) => { + t.setPlaceholder('/uploads/lossless/drops'); + t.setValue(this.plugin.settings.dropGate.imageKitFolder).onChange(async (v) => { + this.plugin.settings.dropGate.imageKitFolder = v; + await this.plugin.saveSettings(); + }); + }); + } + + // ─── Imgur (public CDN) ───────────────────────────────────── + containerEl.createEl('h3', { text: 'Imgur (public CDN)' }); + + new Setting(containerEl) + .setName('Enable Imgur destination') + .setDesc('Anonymous upload via a client ID. Public — use for non-sensitive imagery only.') + .addToggle((t) => + t.setValue(this.plugin.settings.imgur.enabled).onChange(async (v) => { + this.plugin.settings.imgur.enabled = v; + await this.plugin.saveSettings(); + this.display(); + }) + ); + + if (this.plugin.settings.imgur.enabled) { + new Setting(containerEl) + .setName('Imgur client ID') + .setDesc('Anonymous client ID from imgur.com/account → Applications. Not the secret.') + .addText((t) => { + t.inputEl.type = 'password'; + t.setValue(this.plugin.settings.imgur.clientId).onChange(async (v) => { + this.plugin.settings.imgur.clientId = v; + await this.plugin.saveSettings(); + }); + }); + } } private async loadCacheStats(container: HTMLElement) { diff --git a/src/styles/current-file-modal.css b/src/styles/current-file-modal.css index 367edcd..300d468 100644 --- a/src/styles/current-file-modal.css +++ b/src/styles/current-file-modal.css @@ -1,6 +1,7 @@ /* image-gin/src/styles/current-file-modal.css */ @import './magnific.css'; +@import './drop-gate.css'; /* ===== Base Modal Styles ===== Scoped to our modal classes only — DO NOT bare-target `.modal`. diff --git a/src/styles/drop-gate.css b/src/styles/drop-gate.css new file mode 100644 index 0000000..a1817ce --- /dev/null +++ b/src/styles/drop-gate.css @@ -0,0 +1,175 @@ +/* image-gin/src/styles/drop-gate.css + * + * Drop-gate confirmation modal. Class attaches to modalEl (the OUTER popup), + * not contentEl, so width rules actually take effect. See + * perplexed/context-v/issues/Widen-Modals-in-Obsidian-using-CSS.md. + */ + +.image-gin-drop-gate-modal { + width: 90vw; + max-width: 720px; + max-height: 88vh; +} + +.image-gin-drop-gate-modal .modal-content { + padding: 0; + display: flex; + flex-direction: column; + max-height: calc(88vh - 60px); +} + +.image-gin-drop-gate-modal .idg-destinations { + flex: 1 1 auto; + overflow-y: auto; +} + +.image-gin-drop-gate-modal .idg-header { + padding: 1.25rem 1.5rem 0.75rem; + border-bottom: 1px solid var(--background-modifier-border); +} + +.image-gin-drop-gate-modal .idg-title { + margin: 0 0 0.35rem; + font-size: 1.25rem; + font-weight: 600; +} + +.image-gin-drop-gate-modal .idg-subtitle { + margin: 0; + color: var(--text-muted); + font-size: 0.95rem; +} + +.image-gin-drop-gate-modal .idg-file-list { + display: flex; + flex-direction: column; + gap: 0.35rem; + padding: 0.85rem 1.5rem; + background: var(--background-secondary); + border-bottom: 1px solid var(--background-modifier-border); + max-height: 8rem; + overflow-y: auto; + flex-shrink: 0; +} + +.image-gin-drop-gate-modal .idg-file-row { + display: flex; + justify-content: space-between; + gap: 1rem; + font-size: 0.85rem; +} + +.image-gin-drop-gate-modal .idg-file-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--text-normal); + font-family: var(--font-monospace); +} + +.image-gin-drop-gate-modal .idg-file-meta { + color: var(--text-muted); + flex-shrink: 0; +} + +.image-gin-drop-gate-modal .idg-destinations { + display: flex; + flex-direction: column; + gap: 0.4rem; + padding: 1rem 1.5rem; +} + +.image-gin-drop-gate-modal .idg-destination { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.85rem 1rem; + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + cursor: pointer; + transition: border-color 0.12s ease, background 0.12s ease; +} + +.image-gin-drop-gate-modal .idg-destination:hover:not(.idg-destination-disabled) { + border-color: var(--interactive-accent); + background: var(--background-modifier-hover); +} + +.image-gin-drop-gate-modal .idg-destination-disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.image-gin-drop-gate-modal .idg-destination-disabled .idg-destination-hint { + color: var(--text-error, var(--text-muted)); + font-style: italic; + font-size: 0.78rem; + margin-top: 0.2rem; +} + +.image-gin-drop-gate-modal .idg-destination input[type='radio'] { + margin-top: 0.2rem; + accent-color: var(--interactive-accent); +} + +.image-gin-drop-gate-modal .idg-destination-text { + display: flex; + flex-direction: column; + gap: 0.15rem; + min-width: 0; +} + +.image-gin-drop-gate-modal .idg-destination-label { + font-weight: 600; + color: var(--text-normal); +} + +.image-gin-drop-gate-modal .idg-destination-desc { + font-size: 0.8rem; + color: var(--text-muted); + line-height: 1.35; +} + +.image-gin-drop-gate-modal .idg-remember { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0 1.5rem 0.75rem; + color: var(--text-muted); + font-size: 0.85rem; + cursor: pointer; +} + +.image-gin-drop-gate-modal .idg-footer { + display: flex; + justify-content: flex-end; + gap: 0.5rem; + padding: 0.85rem 1.5rem; + border-top: 1px solid var(--background-modifier-border); + background: var(--background-secondary); +} + +.image-gin-drop-gate-modal .idg-cancel, +.image-gin-drop-gate-modal .idg-insert { + padding: 0.4rem 1rem; + border-radius: 4px; + font-size: 0.9rem; + cursor: pointer; + border: 1px solid var(--background-modifier-border); + background: var(--background-primary); + color: var(--text-normal); +} + +.image-gin-drop-gate-modal .idg-cancel:hover { + background: var(--background-modifier-hover); +} + +.image-gin-drop-gate-modal .idg-insert.mod-cta { + background: var(--interactive-accent); + color: var(--text-on-accent); + border-color: var(--interactive-accent); +} + +.image-gin-drop-gate-modal .idg-insert.mod-cta:hover { + background: var(--interactive-accent-hover); +} diff --git a/src/utils/dropGateEvents.ts b/src/utils/dropGateEvents.ts new file mode 100644 index 0000000..e1fa110 --- /dev/null +++ b/src/utils/dropGateEvents.ts @@ -0,0 +1,71 @@ +/** + * Re-entry guards and helpers for the drag-drop / paste interception flow. + * + * The two synthetic event subclasses are how we re-dispatch a drop or paste + * into Obsidian's internal clipboardManager without our own handler picking + * it up again — `instanceof` check at the top of each handler bails out + * early, breaking the would-be loop. + */ + +export class DragEventCopy extends DragEvent { + static create(original: DragEvent, files: readonly File[]): DragEventCopy { + const dt = new DataTransfer(); + files.forEach((f) => dt.items.add(f)); + return new DragEventCopy(original.type, { + dataTransfer: dt, + clientX: original.clientX, + clientY: original.clientY, + bubbles: true, + cancelable: true, + }); + } +} + +export class PasteEventCopy extends ClipboardEvent { + static create(original: ClipboardEvent, files: readonly File[]): PasteEventCopy { + const dt = new DataTransfer(); + files.forEach((f) => dt.items.add(f)); + return new PasteEventCopy(original.type, { + clipboardData: dt, + bubbles: true, + cancelable: true, + }); + } +} + +export function allFilesAreImages(files: readonly File[]): boolean { + if (files.length === 0) return false; + return files.every((f) => f.type.startsWith('image/')); +} + +export function imagesIn(files: readonly File[]): File[] { + return files.filter((f) => f.type.startsWith('image/')); +} + +export function formatFileSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / 1024 / 1024).toFixed(2)} MB`; +} + +const MIME_TO_EXT: Record = { + 'image/png': 'png', + 'image/jpeg': 'jpg', + 'image/jpg': 'jpg', + 'image/gif': 'gif', + 'image/webp': 'webp', + 'image/svg+xml': 'svg', + 'image/bmp': 'bmp', + 'image/tiff': 'tiff', + 'image/heic': 'heic', + 'image/avif': 'avif', +}; + +export function fileNameFor(file: File, fallbackBase = 'image'): string { + if (file.name && file.name.trim() !== '' && file.name !== 'image.png') { + return file.name; + } + const ext = MIME_TO_EXT[file.type.toLowerCase()] ?? 'png'; + const ts = new Date().toISOString().replace(/[:.]/g, '-'); + return `${fallbackBase}-${ts}.${ext}`; +} diff --git a/styles.css b/styles.css index 6e78d5a..6cfab88 100644 --- a/styles.css +++ b/styles.css @@ -1 +1 @@ -.magnific-modal{width:90vw;max-width:1100px;box-sizing:border-box}.magnific-modal .modal-content{padding:1.5rem 1.75rem}.magnific-search-container{display:flex;gap:10px;margin-bottom:20px}.magnific-search-container input[type=text]{flex:1;padding:8px 12px;border:1px solid var(--background-modifier-border);border-radius:4px;background-color:var(--background-primary);color:var(--text-normal)}.magnific-search-container button{padding:8px 16px;background-color:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:4px;cursor:pointer}.magnific-search-container button:hover{background-color:var(--interactive-accent-hover)}.magnific-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:18px;margin-top:20px}.magnific-image-container{display:flex;flex-direction:column;gap:10px;border:1px solid var(--background-modifier-border);border-radius:8px;padding:10px;background-color:var(--background-primary);transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease;cursor:pointer;overflow:hidden}.magnific-image-container:hover{transform:translateY(-2px);box-shadow:0 6px 16px #0000002e;border-color:var(--interactive-accent)}.magnific-thumbnail{width:100%;aspect-ratio:1 / 1;object-fit:cover;border-radius:6px;background-color:var(--background-secondary);display:block}.magnific-title{margin:0;font-size:.9em;line-height:1.3;color:var(--text-normal);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:2.4em}.magnific-select-button{margin-top:auto;padding:6px 12px;background-color:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:4px;cursor:pointer;font-size:.85em;align-self:flex-start}.magnific-select-button:hover{background-color:var(--interactive-accent-hover)}.magnific-error{color:var(--text-error);padding:10px;background-color:var(--background-modifier-error);border-radius:4px;border-left:3px solid var(--text-error)}.magnific-status{color:var(--text-muted);font-style:italic;text-align:center;padding:20px}.magnific-no-results{color:var(--text-muted);text-align:center;padding:20px;font-style:italic}.magnific-error-message{color:var(--text-error);padding:10px;background-color:var(--background-modifier-error);border-radius:4px;border-left:3px solid var(--text-error);margin:10px 0}.image-gin-modal,.batch-directory-modal,.magnific-modal{min-height:60vh}.image-gin-modal h2,.batch-directory-modal h2,.magnific-modal h2{margin-top:0;padding-bottom:.75rem;border-bottom:1px solid var(--background-modifier-border);color:var(--text-normal);font-size:1.5rem;font-weight:600}.image-gin-modal{--modal-padding: 1.5rem;--border-color: var(--background-modifier-border);--hover-bg: var(--background-modifier-hover);--active-bg: var(--background-modifier-active);width:90vw;max-width:880px;box-sizing:border-box}.image-gin-modal .modal-content{padding:var(--modal-padding)}.image-gin-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem;padding-bottom:.75rem;border-bottom:1px solid var(--border-color)}.image-gin-title{margin:0;color:var(--text-normal);font-size:1.5rem;font-weight:600}.image-gin-section{margin-bottom:1.5rem;border:1px solid var(--border-color);border-radius:6px;overflow:hidden}.image-gin-section-header{padding:.75rem 1rem;background-color:var(--background-secondary);border-bottom:1px solid var(--border-color);font-weight:600}.image-gin-section-content{padding:1rem}.image-gin-textarea{width:100%;min-height:100px;resize:vertical;padding:.5rem;border:1px solid var(--border-color);border-radius:4px;background:var(--background-primary);color:var(--text-normal)}.image-gin-textarea:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent);outline:none}.image-gin-toggle-group{display:flex;flex-direction:column;gap:.5rem}.image-gin-toggle-item{display:flex;align-items:center;padding:.5rem;border-radius:4px;transition:background-color .2s ease}.image-gin-toggle-item:hover{background-color:var(--hover-bg)}.image-gin-toggle-item .toggle-container{margin-right:.75rem}.image-gin-progress{margin:1rem 0;padding:1rem;background-color:var(--hover-bg);border-radius:4px;text-align:center}.image-gin-progress-text{color:var(--text-normal);font-weight:500;margin:0}.image-gin-button{width:100%;padding:.75rem 1.5rem;background:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:4px;font-size:1rem;font-weight:500;cursor:pointer;transition:background-color .2s ease,transform .1s ease}.image-gin-button:disabled{opacity:.6;cursor:not-allowed}.image-gin-button:not(:disabled):hover{background-color:var(--interactive-accent-hover);transform:translateY(-1px)}.image-gin-button:not(:disabled):active{transform:translateY(0)}.modal-section{margin-bottom:2rem;border:1px solid var(--background-modifier-border);border-radius:8px;overflow:hidden;background-color:var(--background-primary)}.modal-section h3{margin:0;padding:1rem;background-color:var(--background-secondary);color:var(--text-normal);font-size:1.1rem;font-weight:600;border-bottom:1px solid var(--background-modifier-border)}.modal-section .setting-item{padding:1rem;border-bottom:1px solid var(--background-modifier-border-hover);background-color:var(--background-primary);transition:background-color .2s ease}.modal-section .setting-item:last-child{border-bottom:none}.modal-section .setting-item:hover{background-color:var(--background-primary-alt)}.batch-directory-modal{width:90vw;max-width:1000px;box-sizing:border-box}.batch-directory-modal .modal-content{padding:1.5rem}.batch-directory-modal .modal-section{margin-bottom:2rem}.batch-directory-modal .directory-input{background-color:var(--background-primary-alt);border:2px solid var(--interactive-accent);border-radius:6px;padding:.75rem;margin-bottom:1rem}.batch-directory-modal .directory-input input[type=text]{width:100%;font-family:var(--font-monospace);font-size:.95rem;font-weight:500}.batch-directory-modal .batch-operation{padding:1rem;border-left:3px solid var(--interactive-accent);margin-bottom:1rem;background-color:var(--background-secondary-alt)}.batch-directory-modal .batch-operation-title{font-weight:600;color:var(--text-normal);margin-bottom:.5rem}.batch-directory-modal .batch-operation-description{color:var(--text-muted);font-size:.9rem;margin-bottom:.75rem;line-height:1.4}.batch-directory-modal .batch-inputs{display:flex;gap:.75rem;margin-bottom:.75rem;flex-wrap:wrap}.batch-directory-modal .batch-inputs input[type=text]{flex:1;min-width:150px;padding:.5rem;border:1px solid var(--background-modifier-border);border-radius:4px;background-color:var(--background-primary);color:var(--text-normal)}.batch-directory-modal .batch-button{padding:.5rem 1.5rem;border:1px solid var(--interactive-accent);border-radius:4px;background-color:var(--interactive-accent);color:var(--text-on-accent);cursor:pointer;transition:all .2s ease}.batch-directory-modal .batch-button:hover{background-color:var(--interactive-accent-hover);transform:translateY(-1px)}.batch-directory-modal .batch-button.secondary{background-color:var(--background-secondary);border-color:var(--background-modifier-border);color:var(--text-normal)}.batch-directory-modal .batch-button.secondary:hover{background-color:var(--background-modifier-hover)}.batch-directory-modal .batch-button:active{transform:translateY(0)}.batch-directory-modal .analysis-section{background-color:var(--background-secondary-alt)}.batch-directory-modal .analysis-section h3{background-color:var(--background-modifier-success);color:var(--text-normal)}.batch-directory-modal .stats-display{padding:1rem;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:4px;font-family:var(--font-monospace);font-size:.9rem;line-height:1.6;margin-top:.75rem}.flex-row{display:flex;gap:.75rem;align-items:center;flex-wrap:wrap}.flex-column{display:flex;flex-direction:column;gap:.5rem}.text-muted{color:var(--text-muted);font-size:.9rem}.text-accent{color:var(--interactive-accent);font-weight:500}.margin-bottom-small{margin-bottom:.5rem}.margin-bottom-medium{margin-bottom:1rem}.margin-bottom-large{margin-bottom:1.5rem}.progress-indicator{display:inline-block;width:16px;height:16px;border:2px solid var(--interactive-accent);border-top:2px solid transparent;border-radius:50%;animation:spin 1s linear infinite;margin-right:.5rem}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.status-badge{display:inline-block;padding:.2rem .5rem;border-radius:12px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.status-badge.success{background-color:var(--background-modifier-success);color:var(--text-on-accent)}.status-badge.warning{background-color:var(--background-modifier-error);color:var(--text-on-accent)}.status-badge.info{background-color:var(--interactive-accent);color:var(--text-on-accent)}@media(max-width:768px){.modal{padding:1rem;max-width:100%}.batch-directory-modal .batch-inputs{flex-direction:column}.batch-directory-modal .batch-inputs input[type=text]{min-width:100%}.current-file-modal .text-case-buttons{flex-direction:column}.current-file-modal .text-case-buttons button{min-width:100%}.current-file-modal .setting-item-control{flex-direction:column;align-items:stretch}}.theme-dark .modal-section{border-color:var(--background-modifier-border)}.theme-dark .modal-section h3{background-color:var(--background-secondary)}.theme-dark .batch-directory-modal .batch-operation{background-color:var(--background-secondary);border-left-color:var(--interactive-accent)}.theme-light .modal-section{box-shadow:0 1px 3px #0000001a}.theme-light .batch-directory-modal .batch-operation{background-color:var(--background-primary-alt)}@media(max-width:600px){.image-gin-modal{padding:1rem}.image-gin-title{font-size:1.25rem}.image-gin-section-content{padding:.75rem}} +.magnific-modal{width:90vw;max-width:1100px;box-sizing:border-box}.magnific-modal .modal-content{padding:1.5rem 1.75rem}.magnific-search-container{display:flex;gap:10px;margin-bottom:20px}.magnific-search-container input[type=text]{flex:1;padding:8px 12px;border:1px solid var(--background-modifier-border);border-radius:4px;background-color:var(--background-primary);color:var(--text-normal)}.magnific-search-container button{padding:8px 16px;background-color:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:4px;cursor:pointer}.magnific-search-container button:hover{background-color:var(--interactive-accent-hover)}.magnific-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:18px;margin-top:20px}.magnific-image-container{display:flex;flex-direction:column;gap:10px;border:1px solid var(--background-modifier-border);border-radius:8px;padding:10px;background-color:var(--background-primary);transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease;cursor:pointer;overflow:hidden}.magnific-image-container:hover{transform:translateY(-2px);box-shadow:0 6px 16px #0000002e;border-color:var(--interactive-accent)}.magnific-thumbnail{width:100%;aspect-ratio:1 / 1;object-fit:cover;border-radius:6px;background-color:var(--background-secondary);display:block}.magnific-title{margin:0;font-size:.9em;line-height:1.3;color:var(--text-normal);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:2.4em}.magnific-select-button{margin-top:auto;padding:6px 12px;background-color:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:4px;cursor:pointer;font-size:.85em;align-self:flex-start}.magnific-select-button:hover{background-color:var(--interactive-accent-hover)}.magnific-error{color:var(--text-error);padding:10px;background-color:var(--background-modifier-error);border-radius:4px;border-left:3px solid var(--text-error)}.magnific-status{color:var(--text-muted);font-style:italic;text-align:center;padding:20px}.magnific-no-results{color:var(--text-muted);text-align:center;padding:20px;font-style:italic}.magnific-error-message{color:var(--text-error);padding:10px;background-color:var(--background-modifier-error);border-radius:4px;border-left:3px solid var(--text-error);margin:10px 0}.image-gin-drop-gate-modal{width:90vw;max-width:720px;max-height:88vh}.image-gin-drop-gate-modal .modal-content{padding:0;display:flex;flex-direction:column;max-height:calc(88vh - 60px)}.image-gin-drop-gate-modal .idg-destinations{flex:1 1 auto;overflow-y:auto}.image-gin-drop-gate-modal .idg-header{padding:1.25rem 1.5rem .75rem;border-bottom:1px solid var(--background-modifier-border)}.image-gin-drop-gate-modal .idg-title{margin:0 0 .35rem;font-size:1.25rem;font-weight:600}.image-gin-drop-gate-modal .idg-subtitle{margin:0;color:var(--text-muted);font-size:.95rem}.image-gin-drop-gate-modal .idg-file-list{display:flex;flex-direction:column;gap:.35rem;padding:.85rem 1.5rem;background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);max-height:8rem;overflow-y:auto;flex-shrink:0}.image-gin-drop-gate-modal .idg-file-row{display:flex;justify-content:space-between;gap:1rem;font-size:.85rem}.image-gin-drop-gate-modal .idg-file-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-normal);font-family:var(--font-monospace)}.image-gin-drop-gate-modal .idg-file-meta{color:var(--text-muted);flex-shrink:0}.image-gin-drop-gate-modal .idg-destinations{display:flex;flex-direction:column;gap:.4rem;padding:1rem 1.5rem}.image-gin-drop-gate-modal .idg-destination{display:flex;align-items:flex-start;gap:.75rem;padding:.85rem 1rem;border:1px solid var(--background-modifier-border);border-radius:6px;cursor:pointer;transition:border-color .12s ease,background .12s ease}.image-gin-drop-gate-modal .idg-destination:hover:not(.idg-destination-disabled){border-color:var(--interactive-accent);background:var(--background-modifier-hover)}.image-gin-drop-gate-modal .idg-destination-disabled{opacity:.5;cursor:not-allowed}.image-gin-drop-gate-modal .idg-destination-disabled .idg-destination-hint{color:var(--text-error, var(--text-muted));font-style:italic;font-size:.78rem;margin-top:.2rem}.image-gin-drop-gate-modal .idg-destination input[type=radio]{margin-top:.2rem;accent-color:var(--interactive-accent)}.image-gin-drop-gate-modal .idg-destination-text{display:flex;flex-direction:column;gap:.15rem;min-width:0}.image-gin-drop-gate-modal .idg-destination-label{font-weight:600;color:var(--text-normal)}.image-gin-drop-gate-modal .idg-destination-desc{font-size:.8rem;color:var(--text-muted);line-height:1.35}.image-gin-drop-gate-modal .idg-remember{display:flex;align-items:center;gap:.5rem;padding:0 1.5rem .75rem;color:var(--text-muted);font-size:.85rem;cursor:pointer}.image-gin-drop-gate-modal .idg-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:.85rem 1.5rem;border-top:1px solid var(--background-modifier-border);background:var(--background-secondary)}.image-gin-drop-gate-modal .idg-cancel,.image-gin-drop-gate-modal .idg-insert{padding:.4rem 1rem;border-radius:4px;font-size:.9rem;cursor:pointer;border:1px solid var(--background-modifier-border);background:var(--background-primary);color:var(--text-normal)}.image-gin-drop-gate-modal .idg-cancel:hover{background:var(--background-modifier-hover)}.image-gin-drop-gate-modal .idg-insert.mod-cta{background:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.image-gin-drop-gate-modal .idg-insert.mod-cta:hover{background:var(--interactive-accent-hover)}.image-gin-modal,.batch-directory-modal,.magnific-modal{min-height:60vh}.image-gin-modal h2,.batch-directory-modal h2,.magnific-modal h2{margin-top:0;padding-bottom:.75rem;border-bottom:1px solid var(--background-modifier-border);color:var(--text-normal);font-size:1.5rem;font-weight:600}.image-gin-modal{--modal-padding: 1.5rem;--border-color: var(--background-modifier-border);--hover-bg: var(--background-modifier-hover);--active-bg: var(--background-modifier-active);width:90vw;max-width:880px;box-sizing:border-box}.image-gin-modal .modal-content{padding:var(--modal-padding)}.image-gin-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem;padding-bottom:.75rem;border-bottom:1px solid var(--border-color)}.image-gin-title{margin:0;color:var(--text-normal);font-size:1.5rem;font-weight:600}.image-gin-section{margin-bottom:1.5rem;border:1px solid var(--border-color);border-radius:6px;overflow:hidden}.image-gin-section-header{padding:.75rem 1rem;background-color:var(--background-secondary);border-bottom:1px solid var(--border-color);font-weight:600}.image-gin-section-content{padding:1rem}.image-gin-textarea{width:100%;min-height:100px;resize:vertical;padding:.5rem;border:1px solid var(--border-color);border-radius:4px;background:var(--background-primary);color:var(--text-normal)}.image-gin-textarea:focus{border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent);outline:none}.image-gin-toggle-group{display:flex;flex-direction:column;gap:.5rem}.image-gin-toggle-item{display:flex;align-items:center;padding:.5rem;border-radius:4px;transition:background-color .2s ease}.image-gin-toggle-item:hover{background-color:var(--hover-bg)}.image-gin-toggle-item .toggle-container{margin-right:.75rem}.image-gin-progress{margin:1rem 0;padding:1rem;background-color:var(--hover-bg);border-radius:4px;text-align:center}.image-gin-progress-text{color:var(--text-normal);font-weight:500;margin:0}.image-gin-button{width:100%;padding:.75rem 1.5rem;background:var(--interactive-accent);color:var(--text-on-accent);border:none;border-radius:4px;font-size:1rem;font-weight:500;cursor:pointer;transition:background-color .2s ease,transform .1s ease}.image-gin-button:disabled{opacity:.6;cursor:not-allowed}.image-gin-button:not(:disabled):hover{background-color:var(--interactive-accent-hover);transform:translateY(-1px)}.image-gin-button:not(:disabled):active{transform:translateY(0)}.modal-section{margin-bottom:2rem;border:1px solid var(--background-modifier-border);border-radius:8px;overflow:hidden;background-color:var(--background-primary)}.modal-section h3{margin:0;padding:1rem;background-color:var(--background-secondary);color:var(--text-normal);font-size:1.1rem;font-weight:600;border-bottom:1px solid var(--background-modifier-border)}.modal-section .setting-item{padding:1rem;border-bottom:1px solid var(--background-modifier-border-hover);background-color:var(--background-primary);transition:background-color .2s ease}.modal-section .setting-item:last-child{border-bottom:none}.modal-section .setting-item:hover{background-color:var(--background-primary-alt)}.batch-directory-modal{width:90vw;max-width:1000px;box-sizing:border-box}.batch-directory-modal .modal-content{padding:1.5rem}.batch-directory-modal .modal-section{margin-bottom:2rem}.batch-directory-modal .directory-input{background-color:var(--background-primary-alt);border:2px solid var(--interactive-accent);border-radius:6px;padding:.75rem;margin-bottom:1rem}.batch-directory-modal .directory-input input[type=text]{width:100%;font-family:var(--font-monospace);font-size:.95rem;font-weight:500}.batch-directory-modal .batch-operation{padding:1rem;border-left:3px solid var(--interactive-accent);margin-bottom:1rem;background-color:var(--background-secondary-alt)}.batch-directory-modal .batch-operation-title{font-weight:600;color:var(--text-normal);margin-bottom:.5rem}.batch-directory-modal .batch-operation-description{color:var(--text-muted);font-size:.9rem;margin-bottom:.75rem;line-height:1.4}.batch-directory-modal .batch-inputs{display:flex;gap:.75rem;margin-bottom:.75rem;flex-wrap:wrap}.batch-directory-modal .batch-inputs input[type=text]{flex:1;min-width:150px;padding:.5rem;border:1px solid var(--background-modifier-border);border-radius:4px;background-color:var(--background-primary);color:var(--text-normal)}.batch-directory-modal .batch-button{padding:.5rem 1.5rem;border:1px solid var(--interactive-accent);border-radius:4px;background-color:var(--interactive-accent);color:var(--text-on-accent);cursor:pointer;transition:all .2s ease}.batch-directory-modal .batch-button:hover{background-color:var(--interactive-accent-hover);transform:translateY(-1px)}.batch-directory-modal .batch-button.secondary{background-color:var(--background-secondary);border-color:var(--background-modifier-border);color:var(--text-normal)}.batch-directory-modal .batch-button.secondary:hover{background-color:var(--background-modifier-hover)}.batch-directory-modal .batch-button:active{transform:translateY(0)}.batch-directory-modal .analysis-section{background-color:var(--background-secondary-alt)}.batch-directory-modal .analysis-section h3{background-color:var(--background-modifier-success);color:var(--text-normal)}.batch-directory-modal .stats-display{padding:1rem;background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:4px;font-family:var(--font-monospace);font-size:.9rem;line-height:1.6;margin-top:.75rem}.flex-row{display:flex;gap:.75rem;align-items:center;flex-wrap:wrap}.flex-column{display:flex;flex-direction:column;gap:.5rem}.text-muted{color:var(--text-muted);font-size:.9rem}.text-accent{color:var(--interactive-accent);font-weight:500}.margin-bottom-small{margin-bottom:.5rem}.margin-bottom-medium{margin-bottom:1rem}.margin-bottom-large{margin-bottom:1.5rem}.progress-indicator{display:inline-block;width:16px;height:16px;border:2px solid var(--interactive-accent);border-top:2px solid transparent;border-radius:50%;animation:spin 1s linear infinite;margin-right:.5rem}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.status-badge{display:inline-block;padding:.2rem .5rem;border-radius:12px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.status-badge.success{background-color:var(--background-modifier-success);color:var(--text-on-accent)}.status-badge.warning{background-color:var(--background-modifier-error);color:var(--text-on-accent)}.status-badge.info{background-color:var(--interactive-accent);color:var(--text-on-accent)}@media(max-width:768px){.modal{padding:1rem;max-width:100%}.batch-directory-modal .batch-inputs{flex-direction:column}.batch-directory-modal .batch-inputs input[type=text]{min-width:100%}.current-file-modal .text-case-buttons{flex-direction:column}.current-file-modal .text-case-buttons button{min-width:100%}.current-file-modal .setting-item-control{flex-direction:column;align-items:stretch}}.theme-dark .modal-section{border-color:var(--background-modifier-border)}.theme-dark .modal-section h3{background-color:var(--background-secondary)}.theme-dark .batch-directory-modal .batch-operation{background-color:var(--background-secondary);border-left-color:var(--interactive-accent)}.theme-light .modal-section{box-shadow:0 1px 3px #0000001a}.theme-light .batch-directory-modal .batch-operation{background-color:var(--background-primary-alt)}@media(max-width:600px){.image-gin-modal{padding:1rem}.image-gin-title{font-size:1.25rem}.image-gin-section-content{padding:.75rem}} diff --git a/versions.json b/versions.json index c8795c7..c674931 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,6 @@ { "0.0.9": "1.8.10", "0.1.0": "1.8.10", - "0.1.1": "1.8.10" + "0.1.1": "1.8.10", + "0.2.0": "1.8.10" }