# ピッカーカタログ

> @nodaro/prompts を使って、Nodaro のピッカーを自分のアプリに実装します。選択をプロンプトの一節に変換し、ラベルを翻訳し、画像を表示します。API でカタログを読み取ることもできます。

Source: https://nodaro.ai/ja/docs/developers/picker-catalogs

**ピッカーカタログ**は、[**ムード**（Mood）](https://nodaro.ai/docs/nodes/creative-controls/mood)、[**レンズ**（Lens）](https://nodaro.ai/docs/nodes/creative-controls/lens)、[**フレーミング**（Framing）](https://nodaro.ai/docs/nodes/creative-controls/framing)、[**人物**（Person）](https://nodaro.ai/docs/nodes/creative-controls/person)といった、Nodaro のピッカー（クリエイティブコントロールのノード）の 1 つを支えるデータです。ピッカーの選択肢、各選択肢が追加するプロンプトのテキスト、そのカテゴリー、翻訳のキーを保持します。カタログは npm パッケージの中に単純なデータとして同梱されているため、自分のアプリに、自分のスタイルで同じピッカーを実装し、Nodaro が組み立てるのとまったく同じプロンプトを組み立てられます。

ピッカーがモデルを呼び出すことはありません。ピッカーは、自分がつながっている先のノードのプロンプトに、説明的な一節を追加するだけです。[クリエイティブコントロール](https://nodaro.ai/docs/guides/creative-controls)を参照してください。

## パッケージをインポートするか、API を呼び出す
| あなたのアプリ | 方法 |
| --- | --- |
| npm パッケージを同梱できる | `@nodaro/prompts` からカタログをインポートします。型が付いていて、オフラインで動作し、API 呼び出しは不要です。 |
| パッケージを同梱できない（MCP 経由の AI エージェントなど） | 同じデータを、ディスカバリー用のインターフェースから読み取ります。REST エンドポイントの `GET /v1/picker-catalogs`、[SDK](https://nodaro.ai/docs/developers/sdk) の `client.pickerCatalogs`、[CLI](https://nodaro.ai/docs/developers/cli/commands#pickers) の `nodaro pickers`、または MCP ツールの `get_picker_catalog` です。 |

できる場合は、パッケージを優先してください。リリースのときにしか変わらないデータのために、往復通信を省けます。

```bash
npm install @nodaro/prompts @nodaro/shared @nodaro/sdk
```

`@nodaro/prompts` にはカタログとプロンプト用の関数が、`@nodaro/shared` には翻訳が入っており、`@nodaro/sdk` が生成を実行します。

## レジストリ
```ts

```

| エクスポート | 返す内容 |
| --- | --- |
| `PICKER_CATALOGS` | すべてのピッカーカタログです。単一次元のピッカーが 28 個、複数次元のピッカーが 11 個あります。 |
| `getPickerCatalog(nodeTypeOrCatalogId)` | ノードタイプ（`"mood"` など）またはカタログ ID で指定する、1 つのカタログです。 |
| `listPickerCatalogs()` | そのすべてです。 |

カタログの型は、次のとおりです。

```ts
interface PickerOption {
id: string
label: string            // English display text; translate it with the catalogId (see below)
description?: string
category?: string        // group id, matching categoryOrder and categoryLabels
promptHint: string       // the clause this option adds ("" for a no-op option such as "auto")
term: string             // the short professional term Compact mode adds ("" for a no-op option)
icon?: string            // reserved; pictures are served separately (see "Pictures")
}

interface PickerDimension {  // multi-dimension pickers, and the secondary fields of a single-dimension picker
field: string              // for example "shotSize"
label: string
options: readonly PickerOption[]
}

interface PickerCatalog {
nodeType: string           // "mood", "framing", ...
label: string
catalogId: string          // the key of the translations
kind: "single" | "multi"
valueField?: string        // single: the field a selection writes
defaultValue?: string
categoryOrder?: readonly string[]
categoryLabels?: Readonly<Record<string, string>>
options?: readonly PickerOption[]        // single-dimension pickers
fields?: readonly string[]               // multi-dimension pickers: the dimension fields
dimensions?: readonly PickerDimension[]  // multi-dimension pickers, and secondary fields
}
```

`label` は画面に表示し、`term` または `promptHint` はモデルに送ってください。一方からもう一方を導き出さないでください。

## 単一次元のピッカーを実装する
ムードのような単一次元のピッカーは、フラットなリストから 1 つを選ぶもので、グループ分けされることもあります。`options` には、グリッドを描画するために必要なものがすべて入っています。

```tsx

const client = createClient({
baseUrl: "https://app.nodaro.ai",
auth: new StaticTokenAuth(process.env.NODARO_ACCESS_TOKEN!),
})
const mood = getPickerCatalog("mood")! // { nodeType: "mood", valueField: "mood", options, categoryOrder, categoryLabels }

// 1. Render your own tile grid, grouped by category
function MoodPicker({ value, onChange }: { value?: string; onChange: (id: string) => void }) {
return (mood.categoryOrder ?? [undefined]).map((cat) => (
<section key={cat ?? "all"}>
{cat && <h4>{mood.categoryLabels?.[cat]}</h4>}
{mood.options!
.filter((o) => !cat || o.category === cat)
.map((o) => (
<button key={o.id} aria-pressed={value === o.id} title={o.description} onClick={() => onChange(o.id)}>
{o.label}
</button>
))}
</section>
))
}

// 2. Selection, then prompt clause, then generation
const selected = "serene"
const clause = getParameterPromptHint({ type: "mood", data: { mood: selected } })
// or: mood.options!.find((o) => o.id === selected)!.promptHint

const prompt = ["a portrait of a woman", clause].filter(Boolean).join(", ")
const result = await client.nodes.runAndWait("generate-image", { prompt })
console.log(result.imageUrl)
```

### トランジション、キャラクター FX、キャラクターモーションの副次パラメーター
3 つの単一次元のピッカーには、主要な選択のほかに、追加のフィールドがあります。[**トランジション**（Transition）](https://nodaro.ai/docs/nodes/creative-controls/transition)と[**キャラクター FX**（Character FX）](https://nodaro.ai/docs/nodes/creative-controls/character-fx)には、それぞれ `position`、`duration`、`intensity` があります。[**キャラクターモーション**（Character Motion）](https://nodaro.ai/docs/nodes/creative-controls/character-motion)には `position` と `pace` があります。

これらのフィールドもカタログであり、ほかのすべての選択肢と同じ行の形を持ち、各リストの先頭には、何もしない `auto` があります。カタログはこれらを、`options` に**加えて**、`dimensions` として公開します。メインのピッカーは `options` から、ドロップダウンは `dimensions` から描画してください。ID だけを送るクライアントが、タイミングの一節を自分で書くことはありません。

```ts
const fx = getPickerCatalog("character-fx")!
fx.options     // the effects of the main picker
fx.dimensions  // [{ field: "position", ... }, { field: "duration", ... }, { field: "intensity", ... }]

// Each dimension's rows are also exported directly:
//   TRANSITION_POSITIONS / TRANSITION_DURATIONS / TRANSITION_INTENSITIES
//   CHARACTER_FX_POSITIONS / CHARACTER_FX_DURATIONS / CHARACTER_FX_INTENSITIES
//   CHARACTER_MOTION_POSITIONS / CHARACTER_MOTION_PACES
```

トランジションとキャラクター FX は、同じ ID を共有します。`start`、`middle`、`end`、`full`。`instant`、`short`、`medium`、`long`。`subtle`、`natural`、`dynamic`、`crazy` です。ただし、文言までは共有し**ません**。トランジションの文言は「起こる」「クリップ全体にわたる」、エフェクトの文言は「現れる」「持続する」のように書かれています。行は必ず、そのノード自身のカタログから読み取ってください。キャラクターモーションは position の ID を共有し、それに加えて、独自の pace の ID（`slow-motion`、`slow`、`natural`、`fast`、`explosive`）を、独自の文言とともに持ちます。

## 複数次元のピッカーを実装する
複数次元のピッカーは、いくつかの独立したフィールドを同時に設定します。たとえばフレーミングは、ショットサイズ、アングル、カバレッジ、構図、撮影方向を設定します。そのカタログには、`dimensions` の中に、フィールドごとに 1 つの `{ field, label, options }` エントリがあります。

```tsx

const framing = getPickerCatalog("framing")!
// framing.dimensions = [
//   { field: "shotSize",    label: "Shot Size",   options: [{ id: "close-up", ... }, ...] },
//   { field: "angle",       label: "Angle",       options: [{ id: "low-angle", ... }, ...] },
//   { field: "coverage",    label: "Coverage",    options: [...] },
//   { field: "composition", label: "Composition", options: [...] },
//   { field: "vantage",     label: "Vantage",     options: [...] },
// ]

function FramingPicker({ value, onChange }: {
value: Record<string, string>
onChange: (v: Record<string, string>) => void
}) {
return framing.dimensions!.map((dim) => (
<section key={dim.field}>
<h4>{dim.label}</h4>
{dim.options.map((o) => (
<button
key={o.id}
aria-pressed={value[dim.field] === o.id}
title={o.description}
onClick={() => onChange({ ...value, [dim.field]: o.id })}
>
{o.label}
</button>
))}
</section>
))
}

// Selection to clause: getParameterPromptHint composes every field that is set
const value = { shotSize: "close-up", angle: "low-angle", composition: "rule-of-thirds" }
const clause = getParameterPromptHint({ type: "framing", data: value })
```

## 選択をプロンプトに変換する
`getParameterPromptHint({ type, data })` は、単一次元でも複数次元でも、どの選択もその一節に変換します。Nodaro がサーバー上で実行しているのと同じ関数なので、あなたのプロンプトはエディターのものと一致します。カタログごとのビルダーもあります。複数次元のピッカー向けの `buildFramingHints(value)` や、1 つの選択肢向けの `getMoodPromptHint(id)` などです。

```ts
// Several pickers, one prompt, one generation
const clauses = [
getParameterPromptHint({ type: "mood",    data: { mood: "serene" } }),
getParameterPromptHint({ type: "lens",    data: { lens: "portrait-85mm" } }),
getParameterPromptHint({ type: "framing", data: { shotSize: "close-up", angle: "eye-level" } }),
]
const prompt = ["a portrait of a woman in a garden", ...clauses].filter(Boolean).join(", ")
const result = await client.nodes.runAndWait("generate-image", { prompt, provider: "nano-banana-2" })
```

ピッカーの `data` に `hintMode: "compact"` を追加すると、完全な一節の代わりに、短い `term` を取得できます。これは、エディターの**プロンプトヒント**トグルの**コンパクト**モードです。[フルとコンパクト](https://nodaro.ai/docs/nodes/creative-controls/mood#full-or-compact)を参照してください。

| 関数 | 内容 |
| --- | --- |
| `getParameterPromptHint({ type, data })` | どの選択も、組み立てられた一節に変換します。 |
| `build<Name>Hints(value)` | `buildFramingHints` など、1 つのカタログの一節を組み立てます。 |
| `get<Name>PromptHint(id)` | `getMoodPromptHint` など、1 つの選択肢の一節を返します。 |
| `PICKER_CATALOGS`、`getPickerCatalog`、`listPickerCatalogs` | レジストリです。 |

## ラベルを翻訳する
カタログのラベルは英語です。12 ロケール（`en`、`es`、`fr`、`de`、`pt-BR`、`ru`、`hi`、`ja`、`ko`、`zh-CN`、`he`、`ar`）の翻訳が、各カタログの `catalogId` をキーとして `@nodaro/shared` に同梱されています。`promptHint` と `term` の値は、モデルが読み取るため、英語のままです。

英語にはセットアップが不要です。それ以外のロケールでは、起動時に一度だけ翻訳バンドルを登録し、そのあとは同期的にラベルを取得します。フォールバックは英語です。

```ts

// 1. Once at startup, in a Vite app: wire the lazily loaded locale bundles
registerSidecarLoaders(import.meta.glob("/node_modules/@nodaro/shared/src/i18n/*.*.ts"))

// 2. Before rendering a locale, load that catalog's bundle
await ensureLocaleCatalogLoaded(mood.catalogId, "fr")

// 3. Resolve a label, synchronously; English when a translation is missing or not loaded yet
const label = resolveLabel(mood.catalogId, option.id, option.label, "fr")
```

バンドルはオンデマンドで読み込まれるため、`registerSidecarLoaders` は、Vite の `import.meta.glob` が返すもののような、ローダーのマップを受け取ります。バックエンドとテストでは、翻訳を省略して、英語の `label` を使ってかまいません。

## 画像を表示する
自分のアプリでも、これらのピッカーについて、エディターと同じ画像を表示できます。

- **写真。**[人物](https://nodaro.ai/docs/nodes/creative-controls/person)、[**スタイリング**（Styling）](https://nodaro.ai/docs/nodes/creative-controls/styling)、[**手に持つ小道具**（Held Prop）](https://nodaro.ai/docs/nodes/creative-controls/held-prop)、[**素材**（Material）](https://nodaro.ai/docs/nodes/creative-controls/material)、[**動物**（Animal）](https://nodaro.ai/docs/nodes/creative-controls/animal)に表示されます。
- **アート。**音楽と音声のピッカー、**音楽ジャンル**（Music Genre）、**音楽のムード**（Music Mood）、**楽器編成**（Instrumentation）、**声の特徴**（Voice Character）、**話し方**（Voice Delivery）に表示されます。
- **プレビュー静止画。**見た目系のピッカーに表示されますが、Nodaro Cloud だけです。対象は、**スタイル**（Style）、**カラー／ルック**（Color / Look）、**年代／時代**（Era / Period）、レンズ、ムードです。ほかに、**大気効果**（Atmosphere）、**構図エフェクト**（Composition Effect）、**カメラ／フィルム**（Camera / Film）、フレーミング、**ライティング**（Lighting）、**カメラモーション**（Camera Motion）も含まれます。

**API 経由の場合、**画像がある選択肢には、問い合わせたインストール環境上の絶対 URL である `imageUrl` が付き、画像がない選択肢には何も付きません。人物とスタイリングは `sections` も返します。これは、各トピックを順番に並べたもので、それぞれに丸い画像が付きます。URL の規則は、後述の [API でカタログを読み取る](#read-the-catalogs-over-the-api)にあります。

**ライブラリの場合、**`@nodaro/prompts` が、同じデータから同じ URL を組み立てます。

- 選択肢には `pickerOptionImageUrl(catalog, field, id, { baseUrl })`。
- 人物やスタイリングのトピックには `pickerSectionImageUrl(topicLabel, { baseUrl })`。

`baseUrl` は、ファイルを配信する Nodaro のインストール環境です。画像は npm パッケージにではなく、インストール環境に属します。`lookPreviews: true` は、Nodaro Cloud に対してだけ渡してください。それ以外のピッカーについては、`label`、`description`、`category` から、自分で見た目を作成してください。

## API でカタログを読み取る
どちらのエンドポイントも公開されていて、トークンは不要で、5 分間キャッシュされます。

| メソッド | パス | 返す内容 |
| --- | --- | --- |
| `GET` | `/v1/picker-catalogs` | すべてのピッカーの一覧です。`nodeType`、`label`、`catalogId`、`kind`、`valueField` または `fields`、`optionCount`、そして画像がある選択肢の数である `imageCount` が含まれます。 |
| `GET` | `/v1/picker-catalogs/:nodeType` | 1 つのピッカーのカタログです。不明なタイプには `404 not_found` が返ります。 |

`GET /v1/picker-catalogs/:nodeType` は、3 つのクエリパラメーターを受け取ります。不正な値には `400 validation_error` が返ります。

| パラメーター | 値 | 動作 |
| --- | --- | --- |
| `detail` | `compact`（デフォルト）または `full` | `compact` は `id`、`label`、`category`、`term`、`icon`、`imageUrl` を返します。`full` は、各選択肢の `description` と `promptHint` を追加します。 |
| `category` | カテゴリー ID | 単一次元のピッカーを、1 つのカテゴリーに絞り込みます。 |
| `field` | フィールド名 | 複数次元のピッカーの 1 つの次元、またはトランジション、キャラクター FX、キャラクターモーションの副次フィールドの 1 つを返します。 |

```bash
curl -s https://app.nodaro.ai/v1/picker-catalogs/mood | jq '.data.options[0]'
```

```json
{ "id": "happy", "label": "Happy", "category": "positive", "term": "happy expression",
"imageUrl": "https://cdn.nodaro.ai/cdn-cgi/image/width=480,format=auto,quality=80/images/710df65a-3c1e-485b-b8b7-29f7b3baf479.png" }
```

### 画像の URL
| ピッカー | 画像 |
| --- | --- |
| `person`、`styling`、`held-prop`、`material`、`animal` | 写真です。WebP、幅は最大 480 px です。 |
| `music-genre`、`music-mood`、`instrumentation`、`voice-character`、`voice-delivery` | 3D の絵文字画像（WebP、128 px）または国旗の画像（WebP、幅 120 px）です。 |
| `style`、`color-look`、`lens`、`framing`、`lighting`、`mood`、`camera-format`、`camera-motion` などの見た目系のピッカー | Nodaro Cloud では、Nodaro CDN から配信される、レンダリング済みプレビューの 480 px の静止画です。`camera-motion` の場合はクリップの 1 フレームです。セルフホスティング環境では、何も返りません。 |

- **ホスト。**インストール環境は、自分の画像を `/picker-art/` の下で配信するため、`imageUrl` はその公開アドレスを使います。Nodaro Cloud では `https://app.nodaro.ai`、セルフホスティング環境では `PUBLIC_URL` です。各 URL は、与えられたとおりに使ってください。選択肢の ID から自分で組み立てないでください。
- **キャッシュ。**ファイル名にはコンテンツのハッシュが含まれるため、画像が変わると URL も変わります。ファイルは `Cache-Control: public, max-age=31536000, immutable` と `Access-Control-Allow-Origin: *` 付きで配信されるため、どのオリジンからでも、`<img>`、`fetch`、キャンバスで読み込めます。
- **トピック。**`person` と `styling` は `sections` も返します。それぞれに `label`、まとめる `fields`、任意の丸い `imageUrl` が含まれます。

```bash
curl -s https://app.nodaro.ai/v1/picker-catalogs/person | jq '.data.sections[0], .data.dimensions[0].options[0]'
```

```json
{ "label": "Identity", "fields": ["type", "age", "ethnicity", "regionalAesthetic"],
"imageUrl": "https://app.nodaro.ai/picker-art/character/sections/identity.2d5ec1a4.webp" }
{ "id": "man", "label": "Man", "term": "man",
"imageUrl": "https://app.nodaro.ai/picker-art/character/person/man.441363db.webp" }
```

### デプロイメントのキュレーションされたカタログ
`GET /v1/catalogs` は、すべてのカタログを 1 回の呼び出しで、1 つのフラットな形として返します。デプロイメントは、選択肢を置き換え、追加、除外するベンダーパックを使って、自分のカタログをキュレーションでき、このエンドポイントはそのキュレーションを反映します。デプロイメントがパックを登録している場合、レスポンスには `curated: true` と、`data` にカタログが含まれます。登録していない場合は `curated: false` になり、同梱されているカタログは、ピッカーごとに `/v1/picker-catalogs/:nodeType` から読み取ります。SDK のメソッドは `client.catalogs.list()` です。

### 説明文からピッカーを埋める
`POST /v1/text-to-picker` は、シーンやショットの自由記述の説明文から、ピッカーの値を選びます。これは **AI Fill** 機能です。トークンが必要で、LLM の呼び出しとして課金されます。返されるのは、ピッカーのタイプ、次元、選ばれた ID の順にキーが付いた `pickerJson` と、どのカタログの選択肢にも当てはまらなかった説明内容である `gaps` です。SDK のメソッドは `client.pickerCatalogs.analyzeText()`、CLI のコマンドは `nodaro pickers analyze` です。

## キャラクターモーションのメタデータ
キャラクターモーションのカタログの選択肢は、両方の詳細レベルで `motion` メタデータを持つことがあります。このメタデータは、動きを次のように説明します。

- 必要条件、開始と終了のポーズ、そして動作後の可視性と手の状態です。
- 手が空いている必要があるか、その種類、固定のペース、対になる動きです。
- 検索用の別名、そして ID が廃止されているかどうかと、その代わりとなる ID です。

フィールドがない場合は、不明を意味します。

保存されたワークフローを読み込むときは、廃止された ID も動作するようにし、新しい選択肢には表示しないでください。この型は `CharacterMotionMetadata` で、`@nodaro/prompts` からエクスポートされています。

## Frequently asked questions

### ピッカーカタログを使うのに、Nodaro API は必要ですか？

必要ありません。カタログと、選択をプロンプトの一節に変換する関数は、@nodaro/prompts という npm パッケージに同梱されているため、自分のアプリはオフラインでピッカーを描画し、プロンプトを組み立てられます。API のエンドポイントは、パッケージを同梱できないクライアントのために存在します。

### ピッカーの選択を、プロンプトのテキストに変換するには、どうすればよいですか？

ピッカーのノードタイプと、選んだムードの ID などのデータを指定して、getParameterPromptHint を呼び出します。Nodaro もサーバー上で同じ関数を実行しているため、あなたのプロンプトは、エディターが組み立てるものと一致します。

### ピッカーのラベルは、どの言語に対応していますか？

12 のロケールです。英語、スペイン語、フランス語、ドイツ語、ブラジルポルトガル語、ロシア語、ヒンディー語、日本語、韓国語、簡体字中国語、ヘブライ語、アラビア語です。プロンプトのテキストと用語は、モデルが読み取るため、英語のままです。

### セルフホスティング環境では、見た目系のピッカーに画像がないのはなぜですか？

ムード（Mood）やライティング（Lighting）など、見た目系のピッカーのプレビューは Nodaro CDN から配信されており、返すのは Nodaro Cloud だけです。人物（Person）、スタイリング（Styling）、手に持つ小道具（Held Prop）、素材（Material）、動物（Animal）の写真と、音楽・音声のアートは、どのインストール環境でも配信されます。

### ピッカーカタログはいくつありますか？

39 個です。ムード（Mood）やレンズ（Lens）などの単一次元のピッカーが 28 個、フレーミング（Framing）、ライティング（Lighting）、人物（Person）などの複数次元のピッカーが 11 個あります。
