APIリファレンス
サンドボックスが公開する すべて の完全リスト — グローバル、コンポーネント、
useYumina()の全フィールドとメソッド、型定義、ブロックされたブラウザAPIの代替。
これは リファレンスドキュメント であり、チュートリアルではありません。まず カスタムUIガイド を読んで全体像を掴み、特定のシグネチャを調べる時にここに来てください。
このページの内容はすべて packages/app/sandbox/ の実装から派生しており、エディタに同梱されるサンドボックスバージョンと一致します。
サンドボックスのグローバル
これらの名前は import文なしでルートコンポーネントツリーのどこでも利用可能 です。
| 名前 | 種類 | 内容 |
|---|---|---|
React | module | フルReact(useState、useEffect、useRef、useMemo、useCallback、useLayoutEffect、Fragment、…) |
useYumina | hook | プラットフォームSDK — useYumina() SDK を参照 |
useAssetFont | hook | アセットライブラリからカスタムフォントを読み込む — useAssetFont() を参照 |
Icons | object | 1400以上のLucideアイコンコンポーネント:<Icons.Heart />、<Icons.Sword />。完全カタログ:https://lucide.dev/icons |
Chat | component | フルチャットビルディングブロック — <Chat> を参照 |
MessageList | component | 入力なしのメッセージ — <MessageList> を参照 |
MessageInput | component | 入力バーのみ — <MessageInput> を参照 |
ChatCanvas | component | <Chat /> のレガシーエイリアス — <ChatCanvas> を参照 |
exports、module | object | CJSスタイルのエクスポートフォールバック。通常無視してよい |
Reactや上記の名前をimportしないでください — サンドボックスによって注入されます。import React from "react" と書いてもコンパイル時に静かに削除されますが冗長です。
自分のファイルはimport可能 — マルチファイルのルートコンポーネントはESモジュール構文を使います:import StatBar from "./stat-bar"。拡張子 .tsx、.ts、.jsx、.js は省略可。
useYumina() SDK
コンポーネント関数内で呼び出します:
function MyWorld() {
const api = useYumina()
// api.variables, api.sendMessage(...), ...
}目的別にグループ化した完全な表面:
状態読み取り(同期)
最新のゲーム状態を読みます。これらが変化するたびにコンポーネントが再レンダリングされます。
| フィールド | 型 | 意味 |
|---|---|---|
variables | Record<string, unknown> | セッションスコープのゲーム変数。例:{ health: 80, gold: 150 } |
globalVariables | Record<string, unknown> | すべてのセッションで共有されるグローバル変数 |
worldName | string | 現在のワールドの名前 |
worldId | string | 現在のワールドのUUID |
sessionId | string | 現在のプレイセッションのUUID |
currentUser | { id, name?, image? } | null | 生のアカウント:id、表示名、アカウントアバター。ログアウト時は null。「プロフィールを見る」のようなアカウントレベルのUIに使う。ワールド内のロールプレイレンダリングには user を使う |
user | { name: string; avatar: string | null } | ロールプレイされているプレイヤー — マクロと同じペルソナ vs アカウントの分岐。プレイヤーがペルソナをアクティブにしているとき、user.name はペルソナ名、user.avatar はペルソナアバター。それ以外はアカウントにフォールバック。ワールド内チャットバブル、キャラクターカード、プロフィールパネルに使いたいのはこちら |
mode | "session" | "guest-preview" | "session" は実際のプレイセッション。"guest-preview" はログアウト状態のハブプレビュー — 状態を変更するアクションはno-opとなり、サインインプロンプトを親に表示 |
capabilities | { canSendMessage, canPersistSession, canUseSessionApis, requiresAuth } | 現在の mode が許可するもの。no-opになるボタン(ゲストプレビューのSendボタンなど)を無効化したり、インラインの「サインインして続行」CTAをレンダリングするのに読む |
language | string | ホストからのアクティブなi18n言語コード("en"、"zh" …)。ホストのi18nextインスタンスに依存せずカード内で翻訳を選ぶのに使う |
messages | Array<Record<string, unknown>> | 完全なメッセージ履歴 — SandboxMessage を参照 |
isStreaming | boolean | AIが返信生成中は true |
streamingContent | string | AIからのライブストリーミングテキスト(頻繁に更新) |
streamingReasoning | string | AIからのライブ「thinking」/reasoningテキスト(reasoningモデルのみ) |
pendingChoices | string[] | ルールによって出力された選択ボタンラベル |
error | string | null | 現在のエラーメッセージ(APIエラー、生成エラー)または null |
readOnly | boolean | 他者のセッションを表示しているときは true — <Chat /> は自動的に入力を非表示 |
checkpoints | Array<Checkpoint> | 保存されたチェックポイント — Checkpoint を参照 |
greetingContent | string | null | ワールドエントリから計算された挨拶テキスト(<Chat /> が空状態コンテンツとして使用) |
canvasMode | "chat" | "custom" | "fullscreen" | 現在のキャンバスモード |
selectedModel | string | 現在選択されているAIモデルID |
userPlan | string | ユーザーのサブスクリプションプラン("free"、"go"、"plus"、"pro"、"ultra"、"internal") |
preferredProvider | "official" | "private" | 公式API vs ユーザー独自の鍵 |
entries | ReadonlyArray<SandboxEntry> | ワールドのロアブックエントリ — 有効なもののみ、position でソート済み。ロアブックの検索 と SandboxEntry を参照 |
ゲームアクション(fire-and-forget)
これらのメソッドは何も返しません。意図を親アプリにポストするだけです。
| メソッド | 何をするか |
|---|---|
sendMessage(text) | プレイヤーとしてメッセージを送信し、AI応答をトリガー |
setVariable(id, value, options?) | 変数を設定。options:{ scope?: string; targetUserId?: string }。scope で変数スコープを選ぶ(global/personal用)、targetUserId でマルチプレイヤーの特定プレイヤー用の変数を書き込める |
executeAction(actionId) | ルールエンジンで定義された名前付きアクションを発火(例 "attackBoss") |
switchGreeting(index) | 異なる挨拶バリアントにインデックスで切り替え |
clearPendingChoices() | 選択肢を選ばずにペンディングの選択ボタンを破棄 |
setComposerDraft(text) | text をチャットコンポーザーにドロップしてフォーカス。送信しない。 送信前にプレイヤーにメッセージを確認・編集させたいときに使う(会話の口火を準備するNPC対話ボタンなど)。サンドボックスローカル — 親への往復なし — なので同梱の <MessageInput> / <Chat> コンポーネントと併用する場合のみ機能 |
チャット制御
デフォルトのチャットバーができることすべてを公開し、カスタムUIでも同じことができるようにしたもの。
| メソッド | 何をするか |
|---|---|
editMessage(messageId, content) | 既存メッセージを編集。Promise<boolean> を返す。成功時 true |
deleteMessage(messageId) | メッセージを削除。Promise<boolean> を返す |
regenerateMessage(messageId) | AIに指定した返信の再生成を依頼(fire-and-forget) |
continueLastMessage() | 最後のAIメッセージから生成を継続(fire-and-forget) |
stopGeneration() | 現在のストリームを中断(fire-and-forget) |
restartChat() | すべてのメッセージをクリアし状態をリセット、新規開始 |
swipeMessage(messageId, "left" | "right") | メッセージのAI代替(スワイプ)を切り替え。Promise<Record<string, unknown>> を返す |
セッションとブランチ
| メソッド | 何をするか |
|---|---|
revertToMessage(messageId) | 会話を messageId の直前まで巻き戻す。Promise<void> を返す |
branchFromMessage(messageId) | 指定メッセージで新規セッションをフォーク(そのメッセージまでとそれを含むメッセージと状態スナップショットをクローン)。Promise<string | null> を返す — 新セッションID、失敗時は null(ストリーミング中、マルチプレイヤールーム、メッセージ欠落で失敗) |
getBranchContext() | 現在のブランチスライス(自身、親、兄弟、子)を取得。Promise<BranchContext> を返す。毎回再取得、クライアントキャッシュなし。BranchContext を参照 |
createSession(worldId) | ワールド用の新規セッションを開始。新セッションIDを持つ Promise<string> を返す |
deleteSession(sessionId) | セッションを削除。Promise<void> を返す |
listSessions(worldId) | ワールドのすべてのセッションをリスト。Promise<Array<Record<string, unknown>>> を返す |
チェックポイント
チェックポイントは現在のセッション内の名前付きスナップショットで、巻き戻し可能。
| メソッド | 何をするか |
|---|---|
saveCheckpoint() | 現在のセッション状態を新規チェックポイントとして保存。Promise<void> を返す(その後 checkpoints フィールドがプッシュバックされる) |
loadCheckpoints() | 親に checkpoints 配列のリフレッシュを依頼。Promise<void> を返す |
restoreCheckpoint(checkpointId) | セッションを保存済みチェックポイントに復元。Promise<void> を返す |
deleteCheckpoint(checkpointId) | チェックポイントを削除。Promise<void> を返す |
オーディオ
| メソッド | 何をするか |
|---|---|
playAudio(trackId, opts?) | エントリで定義されたオーディオトラックを再生。opts:{ volume?, fadeDuration?, chainTo?, maxDuration?, duckBgm?, loop? } — duration(fadeDuration、maxDuration)は秒単位、chainTo は次に再生するtrackIdを選択、duckBgm は再生中BGMを下げる、loop はこのトラックの loop 設定を今回の再生だけ上書きする |
stopAudio(trackId?, fadeDuration?) | トラックを停止(trackId 省略ですべて停止)。fadeDuration は秒単位。要素を破棄する —— 同じ位置から再開したい場合は pauseAudio を使う |
pauseAudio(trackId) | トラックをその場で一時停止し、再生位置を保持する |
resumeAudio(trackId) | pauseAudio で一時停止したトラックを再開する |
onAudioEnded(cb) | 「ループしないトラックの再生終了」を購読 — cb(trackId)。解除用の関数を返す。自作プレイヤーの自動次曲送りに使う |
setAudioVolume(type, volume) | type は "bgm" または "sfx"、volume は 0-1 |
getAudioVolume(type) | 現在の音量(0-1)を同期的に返す |
UI/ナビゲーション
| メソッド | 何をするか |
|---|---|
toggleImmersive() | 没入(フルスクリーン)モードを切り替え |
openPersonaManager() | プレイヤーのペルソナ管理を開く — ワールドを離れずにペルソナを切り替え / 作成 / 編集(コンポーザーの「+」メニューと同じパネル)。アクティブなペルソナはアカウント全体で共有され、切り替えは次のメッセージから反映 |
copyToClipboard(text) | クリップボードにコピー(navigator.clipboard.writeText の代替) |
navigate(path) | 親に "/app/hub" のようなパスへのルーティングを依頼(window.location = ... の代替) |
showToast(message, type?) | 親UIにトーストを表示。type:"success"、"error"、"info"(デフォルト) |
永続ストレージ(ワールド単位)
localStorageの代替。worldId でスコープされ、ワールド同士は互いのキーを読めません。
| メソッド | 何をするか |
|---|---|
storage.get(key) | 読み取り。Promise<string | null> を返す |
storage.set(key, value) | 書き込み(文字列のみ)。Promise<void> を返す |
storage.remove(key) | 削除。Promise<void> を返す |
複雑なデータが必要? 入出力で JSON.stringify / JSON.parse を。
ロアブックの検索
カード内からワールドのロアブックへの読み取り専用アクセス。サイドLLMプロンプトを組み立てる時の検査や手選びエントリ、ゲーム内ジャーナルビューア構築、デバッグパネル配線に有用。
| フィールド/メソッド | 何をするか |
|---|---|
entries | ReadonlyArray<SandboxEntry> — 有効なすべてのエントリを position で事前ソート。SandboxEntry を参照 |
getEntry(name) | 正確な名前 で1つのエントリを検索。SandboxEntry | null を返す。localhost ではミス時に利用可能な名前と共に一度限り警告ログ — エントリ名変更を忘れたとき便利 |
ほとんどの場合これらを直接触る必要はありません:ai.complete() に includeLorebook: "matched" を渡せばサーバが伝承を組み立てます(下記)。entries / getEntry を使うのは外科的制御が必要なとき — 例 「このNPCは tavern タグのエントリだけを知っている」。
生のAIコンプリーション
メインチャットパイプラインの 外 でLLMを呼び出す。「サイドパネルのNPC内的独白」「AI生成のアイテム説明」「カード内電話チャット」などに使う。メッセージ履歴に書き込みません、状態更新もトリガーしません、greetingsも消費しません。
const api = useYumina()
const text = await api.ai.complete({
messages: [
{ role: "system", content: "You are a surly merchant." },
{ role: "user", content: "Price me an iron sword." },
],
onDelta: (chunk) => setStreaming((s) => s + chunk), // optional, per-token
model: "claude-sonnet-4-6", // optional, defaults to selectedModel
maxTokens: 500, // optional, default 2048, max 8192
temperature: 0.7, // optional
includeLorebook: "matched", // optional — see below
})完全な応答を持つ Promise<string> を返す。120秒のクライアント側タイムアウト。
制限とコスト
| 制限 | 値 | ソース |
|---|---|---|
| 1コールあたり最大メッセージ数 | 50 | サーバがHTTP 400で拒否 |
| 最大合計コンテンツ | 全メッセージ合計50,000文字 | サーバがHTTP 400で拒否 |
maxTokens デフォルト | 2048 | 省略時のデフォルト |
maxTokens 上限 | 8192 | より大きな値は静かにクランプ |
temperature 範囲 | 0-2、デフォルト1.0 | 範囲外はクランプ |
| デフォルトモデル | プレイヤーの selectedModel、model も selectedModel も未設定なら anthropic/claude-sonnet-4.6 にフォールバック | |
| レート制限 | メインチャットと共有 — サイドコールとメインチャットターンは同じ毎分予算に対してカウント | オーバー時 HTTP 429 + INSUFFICIENT_CREDITS スタイルコードを返す |
| クレジット | メインチャットと同じトークン単位の課金。BYOKユーザーはサーバクレジット控除をスキップ するが自身のプロバイダには支払う | エンドポイント "side-completion" でログ |
| 認証 | セッションは現在のプレイヤーに属する必要あり、そうでなければコールはHTTP 404で失敗 |
includeLorebook — ワールド伝承の自動注入
サイドコールはメインチャットのプロンプト組み立てをバイパスするので、伝承を与えない限りモデルはあなたのキャラクターが誰か分かりません。includeLorebook を渡すと、サーバはワールドのエントリから構築したシステムメッセージを前置きします。
| 値 | 動作 |
|---|---|
omitted / false | 注入なし(デフォルト)。ワールドコンテキスト不要な翻訳、要約、分類などに使う |
true / "all" | 有効な非greetingエントリすべてを position でソートして注入。予測可能、より大きなトークンコスト |
"matched" | messages 内の last user message に対してメインチャットと同じキーワードマッチャーを実行。alwaysSend エントリは常に含まれ、キーワードトリガーエントリは関連時のみ追加。キャラクターに留まるサイドコールに推奨 |
これがないと、「キャラクターに留まる」サイドコールはモデルが名前だけから性格をでっち上げ — カード内ペルソナがメインチャットから漂流します。"matched" ならNPCとの電話チャットがメインチャットと同じワールド伝承+キャラクタープロフィールを参照します。
// A phone chat that stays in canon
api.ai.complete({
messages: [
{ role: "system", content: "Stay strictly in character as Balder. Reply in one or two short lines." },
...history,
{ role: "user", content: userText },
],
includeLorebook: "matched", // server pulls Balder's profile + relevant world lore
})より細かい制御が必要なら — 名前で特定エントリを注入、または特定タグのエントリのみ — includeLorebook の代わりに api.entries をイテレートして自分でシステムメッセージを組み立てます:
const tavernLore = api.entries
.filter((e) => e.tags?.includes("tavern"))
.map((e) => `【${e.name}】\n${e.content}`)
.join("\n\n")
api.ai.complete({
messages: [
{ role: "system", content: `You are the tavern keeper.\n\n${tavernLore}` },
{ role: "user", content: userText },
],
})"matched" モードの注意点:最後のユーザーメッセージのキーワードしかスキャンしません(全履歴ではない)、ゲーム変数依存の条件ゲート付きエントリはサイドコールでは発火しません(マッチャーには空状態スタブが見える)。精度がトークンより重要なら true を使ってすべてを強制的に含めましょう。
コンテキスト注入
次の メインチャットAIターンに 単発 のコンテキストメッセージを注入。一度使われたら消費される、見える チャットメッセージは作られない。「電話メッセージ」「NPCのオフステージ対話」「環境変化」など、メインAIが知るべきがプレイヤーにはチャットバブルとして見せたくないものに最適。
api.injectContext("You just received a cryptic text: 'Tonight, 9pm, usual place.'", { role: "system" })
// On the player's next message, the main AI will see this as a system message.options:{ role?: "system" \| "user" }(デフォルトは "system")。
モデルピッカー
| フィールド/メソッド | 何をするか |
|---|---|
selectedModel | 現在のモデルID |
userPlan | ユーザーのプランティア |
preferredProvider | "official" または "private" |
setModel(modelId) | モデルを切り替え(fire-and-forget) |
getModels() | Promise<{ models, pinnedModels, recentlyUsed }> を返す。models は Array<{ id, name, provider, contextLength }> |
pinModel(modelId) / unpinModel(modelId) | モデルをピン留め/解除 |
アセット
| メソッド | 何をするか |
|---|---|
resolveAssetUrl(ref) | @asset:xxx 参照をCDN URLに変換。純粋な文字列変換、ネットワークなし。HTTP/HTTPS URLはそのまま通過 |
マークダウン
| メソッド | 何をするか |
|---|---|
renderMarkdown(text) | マークダウンを 安全なHTML に変換(HTMLエンティティはエスケープ、危険なタグは剥がす、整形は保持)。結果をカスタムバブル内の dangerouslySetInnerHTML に渡せば安全 — 下の例を参照 |
<div dangerouslySetInnerHTML={{ __html: api.renderMarkdown(msg.rawContent) }} />コンポーネント
<Chat>
プラットフォームのフルチャット体験。これが日常のビルディングブロック — propsゼロでデフォルトのチャットになる。
含まれるもの:メッセージリスト、自動スクロール、ストリーミングカーソル、スワイプコントロール、メッセージアクション(編集/削除/再生成)、入力バー、選択ボタン、モデルピッカー、読み取り専用モード、greetingプレースホルダ。
<Chat renderBubble={(msg) => <MyBubble {...msg} />} />Props
| Prop | 型 | 説明 |
|---|---|---|
renderBubble? | (props: BubbleProps) => ReactNode | 各メッセージバブルの見た目をカスタマイズ。省略時はデフォルトのmarkdownレンダリングにフォールバック |
className? | string | 外側コンテナに追加するCSSクラス |
children? | ReactNode | メッセージリストの 上 にレンダリングされるコンテンツ(固定HUDヘッダーなど) |
BubbleProps
renderBubble コールバックが受け取る msg オブジェクト:
| フィールド | 型 | 意味 |
|---|---|---|
contentHtml | string | 事前レンダリングされた安全なHTML(markdownはすでに変換済み)。通常は dangerouslySetInnerHTML に流す |
rawContent | string | レンダリング前の生markdownテキスト(ディレクティブテキスト含む) |
role | "user" | "assistant" | "system" | メッセージの出所 |
messageIndex | number | リスト内位置(0 = 最初、通常greeting) |
isStreaming | boolean | このメッセージがストリーミング中は true |
stateSnapshot | Record<string, unknown> | null | このメッセージが生成された時点のゲーム状態(「当時のHP/locationは何だったか」に有用) |
variables | Record<string, unknown> | 現在(最新)のゲーム変数 |
renderMarkdown | (text) => string | ヘルパ:任意のmarkdownテキストを安全なHTMLに変換 |
<MessageList>
メッセージストリームだけ(スクロール、ストリーミングカーソル、スワイプコントロール付き)。入力バーなし。
<MessageList />renderBubble は取らない — バブルをカスタマイズするには <Chat renderBubble={...} /> を使うか、<MessageList> を完全に省略して api.messages を直接読む(ビジュアルノベルパターン)。
<MessageInput>
入力バーだけ(モデルピッカー、選択ボタン、continue/restartメニュー、ストリーミング状態付き)。
<MessageInput />api.readOnly が true のとき自動で隠れる。
<ChatCanvas>
レガシーエイリアス — <Chat /> と同一。古いワールドは引き続き動作し、新コードは <Chat /> を優先すべき。
useAssetFont()
アップロードされたフォントアセットを @font-face として読み込み、CSS font-family 値にそのまま入れられる文字列を返す。
const fontFamily = useAssetFont("@asset:my-font-id", {
family: "Cinzel",
fallback: "serif",
})
return <div style={{ fontFamily }}>Ancient runes</div>シグネチャ
useAssetFont(
assetRef: string | null | undefined,
options?: AssetFontOptions
): stringフォントは非同期で読み込まれる。読み込み中、フックは options.fallback(デフォルト "serif")を返す。準備できると再レンダリングが発火し、完全なファミリー文字列(名前衝突を避けるためサフィックス付き)になる。
AssetFontOptions
| フィールド | 型 | 説明 |
|---|---|---|
family? | string | フォントファミリー名。省略時はファイル名または assetRef から推測 |
fallback? | string | 読み込み中表示されるフォールバックフォント。デフォルト "serif" |
filename? | string | null | 元のファイル名、フォーマット推測に使う |
mimeType? | string | null | MIMEタイプ、フォーマット推測に使う |
format? | "opentype" | "truetype" | "woff" | "woff2" | null | 明示的フォーマットオーバーライド |
weight? | string | number | font-weight |
style? | string | font-style(例 "italic") |
stretch? | string | font-stretch |
display? | FontDisplay | font-display(デフォルト "swap") |
型
SandboxMessage
api.messages 各要素の形:
interface SandboxMessage {
id: string
sessionId: string
role: "user" | "assistant" | "system"
content: string
status?: "complete" | "streaming" | "failed"
errorMessage?: string | null
authorUserId?: string | null // who sent it (multiplayer)
authorNameSnapshot?: string | null // their display name at send time
stateChanges?: Record<string, unknown> | null // diff of variable updates from this message
stateSnapshot?: Record<string, unknown> | null // full state at message generation
swipes?: Array<{ content, stateSnapshot }> // alternative AI replies
activeSwipeIndex?: number
model?: string | null
tokenCount?: number | null
generationTimeMs?: number | null
compacted?: boolean // hidden in the "older messages" section
attachments?: Array<{ type, mimeType, name, url }> | null
createdAt: string // ISO-8601
}Checkpoint
interface Checkpoint {
id: string
name: string
messageCount: number
createdAt: string // ISO-8601
}SandboxEntry
api.entries と api.getEntry() で公開される、単一の読み取り専用ロアブックエントリ:
interface SandboxEntry {
id: string
name: string
content: string
keywords: string[]
position: number
section: "system-presets" | "examples" | "chat-history" | "post-history"
enabled: boolean
role: string // "system" | "character" | "lore" | etc.
tags?: string[]
}これはエンジン内部の WorldEntry のスリムビュー — プロンプト組み立てにカードが必要なフィールドだけ。ランタイムは無効なエントリを事前フィルタし、position で事前ソートするので、カードがそれをやる必要はない。
BranchContext
interface BranchNode {
id: string
name: string | null
parentSessionId: string | null
branchedFromMessageId: string | null
messageCount: number
updatedAt: string // ISO-8601
createdAt: string // ISO-8601
}
interface BranchContext {
current: BranchNode // the session you're in
parent: BranchNode | null // the branch you forked from, or null at the root
siblings: BranchNode[] // other branches forked from the same parent, oldest first
children: BranchNode[] // branches forked off `current`, oldest first
}ブロックされたブラウザAPI
コードは sandbox="allow-scripts" のクロスオリジンiframe内で動作し、allow-same-origin は ありません。つまり:
- 親アプリのCookie/localStorageへのアクセス不可
- 認証付きネットワークリクエスト不可
- 直接の
window.parent操作不可
以下のAPIは 完全にブロック または SDKブリッジ経由で透過的にリダイレクト されます。
リダイレクト(レガシーコードは動作し続ける)
| あなたが書いたもの | 実際に起きること |
|---|---|
fetch('/api/...') | 親の認証付きfetchを通してプロキシ |
fetch('/cdn/...') | 許可(CSPが認める) |
fetch('any other URL') | 拒否(throw) |
localStorage.getItem/setItem/removeItem/clear | api.storage 経由でルート、ワールド単位スコープ |
sessionStorage.* | 同じ |
navigator.clipboard.writeText() | api.copyToClipboard() と等価 |
navigator.clipboard.readText() / read() / write() | 拒否(throw) |
window.location.pathname / href / assign / replace | 合成オブジェクト。pathname は常に /app/chat/{sessionId}。代入/assign/replace でナビゲーション発火 |
window.location.reload() | セッション再読み込みにブリッジ |
window.__yuminaToggleImmersive() | api.toggleImmersive() と等価 |
推奨される使い方
新コードを書くときは SDKを直接使う — リダイレクトは古いワールド用に存在しますが、SDKの方がクリーンで安定です:
| こう書かないで | こう書く |
|---|---|
fetch('/api/sessions', { method: 'POST' }) | api.createSession(worldId) |
fetch('/api/sessions/' + sid, { method: 'DELETE' }) | api.deleteSession(sid) |
localStorage.getItem("k") | await api.storage.get("k") |
window.location = "/app/hub" | api.navigate("/app/hub") |
navigator.clipboard.writeText(t) | api.copyToClipboard(t) |
利用可能なブラウザAPI
サンドボックスはネットワークや共有オリジンに到達しないものについては寛容です。以下は任意のブラウザと同じく機能し、SDKラッパーは不要です。
| API | カードでの典型的用途 |
|---|---|
<input type="file"> + FileReader.readAsDataURL / readAsText | プレイヤーに画像/音声/テキストファイルを選ばせる → データURLや文字列として変数に保存。Recipe: Player-Uploaded Images を参照 |
URL.createObjectURL / revokeObjectURL | Blob 用の一時的インメモリURLを生成(保存前プレビューなど) |
<canvas> + getContext("2d") + toDataURL / toBlob | 変数保存前に画像をリサイズ、クロップ、合成 |
<img>、<audio>、<video> | ローカルオリジンURL、@asset:... 解決済みURL、data:/blob: URLをレンダリング |
IntersectionObserver、ResizeObserver、matchMedia、requestAnimationFrame | 標準のレイアウト/アニメーションプリミティブ |
crypto.randomUUID、crypto.subtle | クライアント側状態のハッシュとID生成 |
WebAudio(AudioContext) | 軽量なオーディオ合成や解析 |
Notification、navigator.vibrate、screen.orientation | ブラウザレベルの許可で制限、サンドボックス自体ではない |
一目で:API全体
1つの表、一度スキャン。
useYumina()
├── State reads
│ ├── variables, globalVariables, personalVariables, roomPersonalVariables
│ ├── worldName, worldId, sessionId
│ ├── currentUser (account), user (persona-aware)
│ ├── room, mode, capabilities, language
│ ├── messages, permissions, entries
│ ├── isStreaming, streamingContent, streamingReasoning
│ ├── pendingChoices, error, readOnly, greetingContent, canvasMode
│ ├── checkpoints
│ └── selectedModel, userPlan, preferredProvider
├── Game actions
│ ├── sendMessage(text)
│ ├── setVariable(id, value, options?)
│ ├── executeAction(actionId)
│ ├── switchGreeting(index)
│ ├── clearPendingChoices()
│ └── setComposerDraft(text) // prefill, no send
├── Chat control
│ ├── editMessage(id, content) → Promise<boolean>
│ ├── deleteMessage(id) → Promise<boolean>
│ ├── regenerateMessage(id)
│ ├── continueLastMessage()
│ ├── stopGeneration()
│ ├── restartChat()
│ └── swipeMessage(id, direction) → Promise
├── Sessions / branching
│ ├── revertToMessage(id) → Promise<void>
│ ├── branchFromMessage(id) → Promise<string | null>
│ ├── getBranchContext() → Promise<BranchContext>
│ ├── createSession(worldId) → Promise<string>
│ ├── deleteSession(id) → Promise<void>
│ └── listSessions(worldId) → Promise<Array>
├── Checkpoints
│ ├── saveCheckpoint() → Promise<void>
│ ├── loadCheckpoints() → Promise<void>
│ ├── restoreCheckpoint(id) → Promise<void>
│ └── deleteCheckpoint(id) → Promise<void>
├── Audio
│ ├── playAudio(trackId, opts?)
│ ├── stopAudio(trackId?, fadeDuration?)
│ ├── pauseAudio(trackId)
│ ├── resumeAudio(trackId)
│ ├── onAudioEnded(cb) → unsubscribe
│ ├── setAudioVolume(type, volume)
│ └── getAudioVolume(type) → number
├── UI / navigation
│ ├── toggleImmersive()
│ ├── openPersonaManager()
│ ├── copyToClipboard(text)
│ ├── navigate(path)
│ └── showToast(message, type?)
├── Storage
│ ├── storage.get(key) → Promise<string | null>
│ ├── storage.set(key, value) → Promise<void>
│ └── storage.remove(key) → Promise<void>
├── Lorebook
│ ├── entries (ReadonlyArray<SandboxEntry>) // sorted by position, enabled only
│ └── getEntry(name) → SandboxEntry | null
├── AI
│ └── ai.complete({ messages, onDelta?, model?, maxTokens?, temperature?, includeLorebook? }) → Promise<string>
│ // includeLorebook: true | "all" | "matched" — auto-inject world lore
├── Context injection
│ └── injectContext(message, { role? })
├── Model picker
│ ├── setModel(modelId)
│ ├── getModels() → Promise<{ models, pinnedModels, recentlyUsed }>
│ ├── pinModel(id), unpinModel(id)
├── Assets
│ └── resolveAssetUrl(ref) → string
└── Markdown
└── renderMarkdown(text) → string // safe HTML
Sandbox globals (no import)
├── React
├── useYumina, useAssetFont
├── Icons (1400+ Lucide icons)
├── Chat, MessageList, MessageInput, ChatCanvas (legacy alias)
└── Tailwind utility classes (CSS-level)
Blocked / redirected
├── fetch('/api/...') → proxied
├── localStorage / sessionStorage → api.storage
├── window.location → synthetic + navigate
└── navigator.clipboard → copyToClipboard
Browser APIs that work as-is
├── <input type="file"> + FileReader // player file uploads → data URL
├── <canvas>, URL.createObjectURL // image processing
├── IntersectionObserver, ResizeObserver, matchMedia, rAF
├── crypto.randomUUID, crypto.subtle
└── WebAudio (AudioContext)