本文へ移動
cccskills
無料GitHub で公開

embedded-image-studio

Use whenever a form needs to accept ONE image and produce permanent public URLs for one or more platform-specific sizes (OG image, social posts, avatars, logos, favicons, app icons). Replaces every legacy image-URL input, single-size uploader, and base64-data-URL flow. Triggers when you see a manual `<input type="url">` for an image, a single-size uploader, or anywhere a `dataUrl` could leak into a clipboard or a saved record.

インストール方法を見る

含まれるファイル(52)

  • SKILL.md10.9 KB
  • AI-AGENTS.md18.3 KB
  • api/python.ts17.9 KB
  • components/Base64DecoderShell.tsx17.0 KB
  • components/CropControls.tsx8.8 KB
  • components/CropPreview.tsx19.3 KB
  • components/CropPreviewWindow.tsx13.1 KB
  • components/CropStudioWindow.tsx12.6 KB
  • components/EmbeddedImageStudio.tsx44.5 KB
  • components/ExportPanel.tsx19.2 KB
  • components/ImageStudioShell.tsx45.0 KB
  • components/InitialCropPanel.tsx28.6 KB
  • components/InitialCropWindow.tsx4.0 KB
  • components/MetadataPanel.tsx13.0 KB
  • components/MiniFocalPointPicker.tsx3.5 KB
  • components/ModeImagePicker.tsx6.5 KB
  • components/PresetCatalog.tsx16.2 KB
  • components/StudioDropZone.tsx6.6 KB
  • components/StudioFileCard.tsx19.1 KB
  • components/StudioLandingHero.tsx7.7 KB
  • components/StudioVariantTile.tsx13.2 KB
  • components/useCropStudioController.ts25.5 KB
  • constants/backend-capabilities.ts528 B
  • constants/conversion-options.ts6.0 KB
  • constants/describe.ts1.3 KB
  • constants/generation-options.ts1.3 KB
  • FEATURE.md50.3 KB
  • hooks/useBase64Decoder.ts9.3 KB
  • hooks/useImageStudio.ts45.6 KB
  • lib/image-studio-write-targets.ts8.5 KB
  • modes/annotate/AnnotateModeShell.tsx10.8 KB
  • modes/avatar/AvatarModeShell.tsx9.9 KB
  • modes/edit/EditAiToolbar.tsx23.9 KB
  • modes/edit/EditModeShell.tsx44.2 KB
  • modes/edit/EditorTabHint.tsx3.5 KB
  • modes/edit/load-ai-result-blob.test.ts1.7 KB
  • modes/edit/load-ai-result-blob.ts645 B
  • modes/edit/MaskOverlay.tsx9.9 KB
  • modes/edit/use-mask-state.ts2.3 KB
  • modes/shared/save-edited-image.test.ts4.2 KB
  • modes/shared/save-edited-image.ts3.6 KB
  • modes/shared/types.ts3.2 KB
  • modes/shared/use-image-source.ts2.9 KB
  • presets.ts25.5 KB
  • types.ts8.0 KB
  • utils/build-describe-preview.ts2.4 KB
  • utils/compute-crop.ts6.9 KB
  • utils/crop-file.ts3.0 KB
  • utils/decode-base64.ts10.0 KB
  • utils/download-bundle.ts3.3 KB
  • utils/format-dimensions.ts188 B
  • utils/slugify-filename.ts764 B

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Embedded Image Studio — Skill

Drop the <EmbeddedImageStudio> component into any form that needs an image. The user drops a file → cropper opens → all caller-requested sizes generate → variants upload as public to Cloud Files → host receives permanent Cloudflare CDN URLs.

This is the only approved pattern for image inputs going forward. It is forbidden to:

  • Save base64 data: URLs in any record (chat clipboard, DB column, redux slice).
  • Use <input type="url"> to capture an image URL when an embedded studio could capture it from a real file upload.
  • Upload images with visibility: "private" for fields meant to be embedded in HTML, OG meta tags, emails, or anywhere else a paste might appear.
  • Generate variants client-side with canvas.toDataURL() and ship them as URLs.
  • Hand-roll a "drop → resize → upload" pipeline. There is one pipeline; use it.

Three intake paths — all built in

The user has three ways to give us an image. The component surfaces them up-front so we never push the user out of our system to find what they already have.

PathWhen to useWhat happens
Drop / BrowseBrand-new imageCrop dialog → Sharp generates every preset → uploads as PUBLIC → CDN URLs returned
Paste URLThe user already has a public URL elsewhereThe URL is used as-is. No re-upload, no resize. onSaved fires immediately with that URL as primary.
From libraryThe user already saved this image to Cloud FilesOpens useFilePicker → user picks a file → its permanent CDN publicUrl (or a 1h signed URL for private files) is used as-is. onSaved fires immediately.

Drop is the "I want all the sizes" path. The other two are "I already have what I need" paths — they emit result.primary.publicUrl straight away so the host form gets populated with one click. Never push a user out of the system to find an image they already have.


The pattern at a glance

import { EmbeddedImageStudio } from "@/features/image-studio/components/EmbeddedImageStudio";

<EmbeddedImageStudio
  // Which preset sizes to auto-generate. From features/image-studio/presets.ts.
  presetIds={["og-image", "fb-post", "tw-card-large", "ig-square"]}
  // Which one's URL the host actually cares about — the result's `primary` URL.
  primaryPresetId="og-image"
  // Folder segment under Images/Generated/. One per host feature.
  rootFolderSegment="html-pages"
  // Recommended: pass a meaningful default (page title, app name, person's name).
  defaultFilenameBase={page.title || "page-social-image"}
  // Show the existing public URL with a Replace CTA when the field is already set.
  initialUrl={page.ogImage || null}
  disabled={!user}
  label="Social Share Image"
  onSaved={(result) => {
    // result.primary.publicUrl is a permanent Cloudflare CDN URL.
    if (result.primary?.publicUrl) {
      setOgImage(result.primary.publicUrl);
    }
    // result.byPreset has every preset's CDN URL — store them too if useful.
  }}
  onCleared={() => setOgImage("")}
/>

What the user sees

  1. Drop zone (or "Replace" button if initialUrl was set).
  2. InitialCropWindow opens automatically (a floating WindowPanel) with aspect-ratio chips, panning, and resize handles.
  3. Source preview + filename input — the user can rename the slug; the slug becomes the per-source subfolder AND every variant's filename.
  4. "Generate & save" button — one click runs Sharp + cloud upload.
  5. Variant grid — every requested preset shows preview, dimensions, file size, compression %, and a permanent CDN URL with a Copy button.
  6. onSaved fires with result.primary.publicUrl ready to drop into the host's state.

Where the bytes go

Cloud Files
└── Images/
    └── Generated/
        └── <rootFolderSegment>/                 ← e.g. html-pages
            └── <filenameBase>/                  ← e.g. how-to-bake-bread
                ├── how-to-bake-bread-og-image.webp
                ├── how-to-bake-bread-fb-post.webp
                ├── how-to-bake-bread-tw-card-large.webp
                └── how-to-bake-bread-ig-square.webp

Every file is visibility: "public" → API returns public_url → the result is a https://cdn.aidream.com/.../...?v=<checksum> URL that:

  • Never expires.
  • Cache-busts automatically when content changes (the checksum suffix flips).
  • Is served from Cloudflare's CDN, not a presigned S3 URL.

Picking the right presetIds

Read presets.ts for the full catalog (60+ across 10 categories). The right set depends on the host feature.

Host featureSuggested presetIdsprimaryPresetId
HTML pages / blog SEO["og-image", "fb-post", "tw-card-large", "ig-square"]"og-image"
Applet cover / preview["applet-cover", "fb-post", "ig-square"]"applet-cover"
Avatar / profile picture["avatar-xl", "avatar-lg", "avatar-md", "avatar-sm", "avatar-xs"]"avatar-md"
Org logo["logo-xl", "logo-md", "logo-favicon"]"logo-md"
Favicon set["favicon-32", "favicon-192", "apple-touch-icon", "android-chrome-192", "android-chrome-512"]"favicon-192"
Podcast cover art["podcast-cover", "ig-square", "fb-post"]"podcast-cover"
Single OG-only field["og-image"]"og-image"

If a preset you need doesn't exist, add it to PRESET_CATEGORIES in presets.ts — don't pass arbitrary dimensions inline.


When the host has a richer surface

If the host feature wants different filenames per record (e.g. per-page title), pass defaultFilenameBase from that record. It's used as the per-source subfolder AND as the prefix for every variant filename.

// Bad — every page's variants land in /image-studio/social-image/
defaultFilenameBase="social-image"

// Good — every page gets its own folder, slugged from the title
defaultFilenameBase={page.title}

When the user later AI-describes the image (or types a new name), the variants are regenerated under the new slug.


Replacing existing image inputs

Step-by-step migration recipe

  1. Identify the field. Search for <input type="url"> near the word "image", or useState<string> whose default is an image URL.
  2. Identify the presets. Match the field's purpose to the table above.
  3. Identify a meaningful filename source. Title, name, slug, agent name, etc.
  4. Replace the input with <EmbeddedImageStudio> exactly as shown above.
  5. Remove any dataUrl/base64 plumbing. The component never returns one.
  6. Verify by uploading an image: the URL the host stores should start with https://cdn. and contain ?v=.

Anti-patterns to delete on sight

// ❌ Plain URL input
<input type="url" value={ogImage} onChange={(e) => setOgImage(e.target.value)} />

// ❌ Single-size uploader that returns a dataUrl
<ImageAssetUploader onComplete={(r) => setOgImage(r.dataUrl)} />

// ❌ Saving a data: URL into Redux/DB
dispatch(updateMetadata({ ogImage: canvas.toDataURL() }));

// ❌ Manual `uploadFile` + `getSignedUrl` chains for a public image
const f = await uploadFile(...); const s = await getSignedUrl(f.id);
setUrl(s); // ← signed URL expires, breaks shares

What the component handles for you

  • Drop / paste / browse — multi-source intake (file picker, drag-drop, paste-from-clipboard).
  • EXIF rotation — Sharp auto-rotates so portrait phone shots aren't sideways.
  • Crop dialog — full freeform + aspect-ratio chips, with rule-of-thirds guides.
  • Per-source subfolder — variants for one image stay grouped, never jumbled.
  • Filename gate — auto-named files surface a banner before mass generation.
  • Public-by-default — visibility: "public" so the API returns a CDN URL.
  • Per-variant copy buttons — every result has a "Copy CDN" action. We never copy a data URL once the variant is saved.
  • Replace flow — clicking Replace clears state and re-opens the drop zone.
  • onSaved callback — host receives result.primary.publicUrl to fold back into its state.

Forbidden in callers

  • Don't read variant.dataUrl for anything user-facing once savedAt is set.
  • Don't bypass onSaved to manually fish URLs out of the cloud-files slice.
  • Don't override visibility: "private" unless the field is genuinely never going to be embedded anywhere (rare — usually the answer is public).
  • Don't render the <EmbeddedImageStudio> inside a tight grid cell. It needs ~400px of width.
  • Don't reuse the same rootFolderSegment across unrelated features. Use the host feature's slug.

Reference implementation

SavePageTab.tsx — the canonical example. Copy the pattern:

<EmbeddedImageStudio
  presetIds={["og-image", "fb-post", "tw-card-large", "ig-square"]}
  primaryPresetId="og-image"
  rootFolderSegment="html-pages"
  defaultFilenameBase={state.metadata.title?.trim() || "page-social-image"}
  initialUrl={state.metadata.ogImage || null}
  disabled={!user}
  label="Social Share Image"
  onSaved={(result) => {
    if (result.primary?.publicUrl) {
      actions.setMetadataField("ogImage", result.primary.publicUrl);
    }
  }}
  onCleared={() => actions.setMetadataField("ogImage", "")}
/>

Files of record

Change log

  • 2026-05-04 — Three-way intake (Drop / Paste URL / From library). URL paste and library pick emit onSaved immediately with the chosen URL as primary — no forced pipeline. Stops pushing users out of the system when they already have what they need. Also fixed a closure-staleness bug in saveAll where variants saved right after generate() had no publicUrl because the function read from a frozen files reference; now reads through filesRef.current.
  • 2026-05-03 — Initial skill. Defines EmbeddedImageStudio as the canonical image-input component for every form. SavePageTab is the reference implementation.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Admin debug system wiring for the floating AdminIndicator's live debug data and Copy Full Context. Use when adding debug visibility to a route or feature, wiring useDebugContext or the debug panel, capturing console errors, or enabling the copy-context workflow for a page.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月12日 更新

Compact two-icon Copy / Copy-for-AI controls (components/agent-copy). Use when adding copy buttons to a row, card, list, or record; merging duplicate Copy/JSON/AI controls; continuing the copy rollout; or writing a Copy-for-AI payload. NOT for markdown content actions (use rich-document-actions).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月12日 更新

Disclosing a surface's existing fixed AI jobs in the shell's top Agents menu. Use when a page, panel, overlay, or window already runs a mandate behind a button, assist, automatic action, or mode; when the agent-disclosure guard names a file; or during a surface check.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月12日 更新

The watch-fix-rerun method for making an existing platform agent or automated process efficient and correct: baseline its ledger, run one unit yourself, fix the class behind every wasted call, rerun, record. Use when asked to improve, optimize, watch, or 'make efficient' an agent, a sandbox session, a sync job, or any recurring automated process.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月12日 更新

Redux state for agent execution and firing agent shortcuts. Use when editing features/agents/redux/, building agent UI, creating a conversation, touching assembleRequest or NDJSON stream state, wiring agent-state selectors, adding a per-conversation capability, or triggering a shortcut from a button, menu, or code (useShortcutTrigger, launchAgentExecution).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月12日 更新

The provision for a call site: the exhaustive menu of values that place in the code can realistically produce. Use before creating or fixing a mandate or agent that reasons about more than its own input, when deciding whether an agent can answer at all with what it is sent, or when its output 'looks right' but nobody checked it could be. NOT for building the agent (use create-agent).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月12日 更新

armanisadeghi のスキルをすべて見る

このスキルの問題を報告する