チュートリアル,ユーティリティ,中級,小型DB,教育

このチュートリアルでは、学習に使えるフラッシュカードアプリを作成します。カードは小型DBに保存されます。学習モードだけでなく、データベースアプリの基本機能であるCRUD機能(作成:Create、読み取り:Read、更新:Update、削除:Delete)もあります。ページの最後にあるダウンロードセクションからソースコードをダウンロードできます。

このアプリでは三つのスクリーンを使っていますが、スクリーン毎に同じグローバル変数を設定したり小型DBなどの同じコンポーネントを使うのは煩雑なので、Screen1だけを使って三つのスクリーンを切り替えて使う、仮想スクリーンテクノロジーを使っています。仮想スクリーンではScreen1に複数の垂直配置コンポーネントを配置してブロック編集内で「目に見える」をON/OFFしてスクリーンを切り替えます。

[プロジェクト]メニューから[新規プロジェクトを始める]を選択し、"FlashCard“と名前を付けます。

デザイン編集

Screen1のタイトルを"フラッシュカードアプリ“にします。

仮想スクリーンを作成

レイアウト パレットから 垂直配置コンポーネント を3個追加し、ビューア上に縦に並べ、横幅は"親要素に合わせる"に設定します。上から順にメインスクリーン一覧スクリーン編集スクリーンという名前にしてください。それぞれの垂直配置コンポーネントが仮想スクリーンになります。それぞれの垂直配置コンポーネント(スクリーン)にコンポーネントを追加するときは他のスクリーンの目に見えるのチェックを外して作業してください。スクリーン切り替えにはこれらのプロシージャを使います。

“メインスクリーン

  • ユーザーインターフェース パレットから ラベル を1個追加し、名前をラベルカードに、高さは"親要素に合わせる"、横幅は"親要素に合わせる"、テキストは空白にします。
  • レイアウト パレットから  水平配置コンポーネントラベルカードの下に追加し、水平に整列するを"中央揃え"、横幅は"親要素に合わせる"にします。名前は水平配置1になります。
  • 水平配置1 の中にユーザーインターフェース パレットから ボタン を3個追加し、左から順に名前はボタンはじめボタン切り替えボタン次、テキストは"始めから"、"表裏切り替え"、"次のカード"にします。
  • レイアウト パレットから  水平配置コンポーネント水平配置1 の下に追加し、水平に整列するを"中央揃え"、横幅は"親要素に合わせる"にします。名前は水平配置2になります。
  • 水平配置2 の中にユーザーインターフェース パレットから ボタン を2個追加し、左から順に名前はボタン追加ボタン一覧、テキストは"カード追加"、"カード一覧"にします。

一覧スクリーン

  • ユーザーインターフェース パレットから リストビュー を追加し、高さと横幅は"親要素に合わせる"に、リストビューレイアウトを"本文"にします。名前はリストビュー1になります。
  • レイアウト パレットから  水平配置コンポーネントリストビュー1 の下に追加し、水平に整列するを"中央揃え"、横幅は"親要素に合わせる"にします。名前は水平配置3になります。
  • 水平配置3 の中にユーザーインターフェース パレットから ボタン を3個追加し、左から順に名前はボタンバックボタン編集ボタン削除に、テキストは"戻る"、"編集"、"削除"にします。

“編集スクリーン

  • ユーザーインターフェース パレットから ラベルテキストボックスラベルテキストボックス をこの順番で追加し、名前をそれぞれラベル質問テキストボックス質問ラベル回答テキストボックス回答 に、テキストは"質問入力"、""、"回答入力"、""にします。テキストボックス質問テキストボックス回答の横幅は"親要素に合わせる"にし、マルチラインはチェックしてください。
  • レイアウト パレットから  水平配置コンポーネントテキストボックス回答 の下に追加し、水平に整列するを"中央揃え"、横幅は"親要素に合わせる"にします。名前は水平配置4になります。
  • 水平配置4 の中にユーザーインターフェース パレットから ボタン を2個追加し、左から順に名前はボタンキャンセルボタン保存 に、テキストは"キャンセル"、"保存"にします。

非可視コンポーネントの追加

ストレージパレットから小型DBを、ユーザーインターフェース パレットから通知機能を追加します。

ここまでで下図のようになります。

ブロック編集機能を使用したプログラミング

🔒 このページは以下の会員レベルで閲覧できます

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内

AI,API,gemini,Google Apps Script,クラウド,中級,生成AI

このチュートリアルでは、Google Gemini APIに3択クイズを自動生成してもらい、正解を当てるアプリを作ります。難易度は「簡単,中級,難解」から選択できます。正解すると点数が加算されます。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。

Google AI Studio で作るGemini APIは課金方法を登録しなくても以下の範囲ならば無料で使えます。APIキーは自分で取得してください。

  • 1 分あたりのリクエスト数: 5
  • 1 日あたりのリクエスト数: 250,000
  • 1 分あたりのトークン数(入力): 100

Google Apps Scriptの設定

API呼び出し処理を行うサーバー側のプログラムをGoogle Apps Scriptに設定します。

Google Apps Scriptの設定方法はGoogle公式ページのこちら、Google AI Studio でのAPIキーの取得方法はGoogle公式ページのこちらを参照ください。

Google Apps Script

Google Apps Scriptに設定するスクリプトはJavaScriptで書きます。http Getで呼び出せるようにdoGet()関数を書きます。ブラウザで簡単にテストできるのでdoGet()関数の方がdoPost()関数よりも便利です。

🔒 このページは以下の会員レベルで閲覧できます

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内

AI,gemini,JavaScript,アプリ開発,チュートリアル,バイブコーディング,中級,生成AI

App Inventorでテトリスゲームを作ろうと思い立ち、いろいろと調べてみたところ、App Inventorだけで実装している記事を見つけました。しかし、その内容を見るとかなり手間がかかっており、大変そうでした。

本来、App Inventorは「簡単にスマホアプリを作る」ことを目的としたツールのはずです。それにもかかわらず、ゲームをひとつ作るのにここまで苦労するのは本末転倒だと感じました。そこで、複雑な処理はWebViewer内でJavaScriptに任せればよいことを思い出しました。

例えば、関数電卓アプリでは、複雑な計算部分をJavaScriptのライブラリで処理し、App Inventor側では結果の表示や操作部分だけを担当させることで、全体をずっと簡潔に構築できます。

そこで今回は、GoogleのGemini 2.5 Proを活用してテトリスゲームを作成します。なお、ここに掲載しているプロンプトと返答は、掲載用に改めて行った会話の内容であり、実際にゲームを作成した際のやり取りとは多少異なります。

実際に作ったテトリスゲームのチュートリアルはこちらです。

Gemini 2.5 Proとの対話

プロンプト

App Inventorでテトリスゲームを作りたい。ゲーム本体はウェブビュー内でJavaScriptで実行する。ゲームプレイ用のボタンなどはApp Inventor側で定義する。ウェブビューで使うhtmlファイルは1個にまとめる。

返答

App InventorとWebViewを組み合わせたテトリスゲームですね。承知いたしました。

WebView内で動作する、単一のHTMLファイルを作成します。このファイルには、ゲームのロジック(JavaScript)、描画(Canvas)、スタイル(CSS)がすべて含まれています。

App Inventor側では、このHTMLファイルをメディアとしてアップロードし、WebViewのHomeUrlに設定します。そして、App Inventorで作成したボタンから、WebViewのJavaScript関数を呼び出す(RunJavaScriptブロックを使用)ことでゲームを操作します。

こちらが、App InventorのWebViewで使用するための単一のHTMLファイルです。

tetris.html

🔒 このページは以下の会員レベルで閲覧できます

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内

AI,API,gemini,Google Apps Script,クラウド,中級,生成AI

最近家族と公園に行ったのですが、見たことのないトンボが飛んでいました。なんという種類のとんぼなのか調べるのに生成AIが使えるんではないかと思い、カメラで撮影して調べるアプリを作ろうと思い立ちました。

そこで、このチュートリアルでは、Google Gemini APIを使って撮影した写真を解析し、写っているものを百科事典を引いてわかりやすく説明してくれるアプリを作ります。さらに説明の読み上げ機能もつけました。生徒の学習用に良いと思います。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いた生成AIお絵描きコーチアプリ用のスクリプトのプロンプトを書き換えています。プロンプトの書き換えにはもちろんGoogle Gemini 2.5 Proを使っています。

Google AI Studio で作るGemini APIは課金方法を登録しなくても以下の範囲ならば無料で使えます。

  • 1 分あたりのリクエスト数: 5
  • 1 日あたりのリクエスト数: 250,000
  • 1 分あたりのトークン数(入力): 100

Google Apps Scriptの設定

Gemini APIの設定からウェブアプリとしてのデプロイ方法、使い方などは生成AIお絵描きコーチアプリを参照してください。

Code.gs – 写真百科事典スクリプト

/**
 * App InventorからのPOSTリクエストを処理する関数
 * @param {Object} e - App Inventorから送信されるイベントオブジェクト
 * @return {ContentService.TextOutput} - Gemini APIからの結果をJSON形式で返す
 */
function doPost(e) {
  // エラーハンドリング
  try {
    // 1. App Inventorから送信されたJSONデータを解析
    const postData = JSON.parse(e.postData.contents);
    const base64Image = postData.image; // App Inventor側で "image" というキーに設定

    // 2. スクリプトプロパティからAPIキーを読み込む
    const API_KEY = PropertiesService.getScriptProperties().getProperty('API_KEY');
    if (!API_KEY) {
      return createErrorResponse('APIキーが設定されていません。');
    }

    // 3. Gemini APIのエンドポイントURL
    const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent?key=${API_KEY}`;
  
    // 4. Gemini APIに送信するリクエストボディを作成
    const payload = {
      "contents": [{
        "parts": [{
          "text": `# 命令
あなたは、あらゆる事象を網羅した百科事典の知識を持つ博士です。提供された画像を注意深く分析し、以下の指示に従って解説文を作成してください。

# 実行ステップ
1.  **被写体の特定:** 画像に写っている最も主要な対象物(物、生物、場所、人物など)を正確に特定します。
2.  **情報抽出:** 特定した対象物について、あなたの知識の中から「名称」「最も重要な特徴」「歴史的背景や興味深い事実」を抽出します。
3.  **文章構成:** 抽出した情報を基に、最も伝えたい核心部分から書き始め、読み手の興味を引くように自然な文章を構成します。

# 出力制約
*   文字数: 200文字以内
*   段落: 必ず一つの段落で完結させること。
*   形式: 箇条書きは絶対に使用しないこと。
*   文体: 専門的でありながらも、その分野に詳しくない人にも分かりやすい、簡潔で平易な言葉で記述すること。`
        }, {
          "inline_data": {
            "mime_type": "image/png", // App InventorのCanvasはPNG形式
            "data": base64Image
          }
        }]
      }]
    };

    // 5. Gemini APIにリクエストを送信するための設定
    const options = {
      'method': 'post',
      'contentType': 'application/json',
      'payload': JSON.stringify(payload),
      'muteHttpExceptions': true // エラーレスポンスを例外ではなくオブジェクトとして取得
    };

    // 6. APIにリクエストを送信
    const response = UrlFetchApp.fetch(API_URL, options);
    const responseCode = response.getResponseCode();
    const responseBody = response.getContentText();

    if (responseCode !== 200) {
       return createErrorResponse(`APIリクエストエラー: ${responseCode} ${responseBody}`);
    }
    
    // 7. APIからのレスポンスを解析
    const resultJson = JSON.parse(responseBody);
    
    // レスポンスの構造をチェックし、テキスト部分を取得
    let generatedText = "解析結果を取得できませんでした。";
    if (resultJson.candidates && resultJson.candidates.length > 0 &&
        resultJson.candidates[0].content && resultJson.candidates[0].content.parts &&
        resultJson.candidates[0].content.parts.length > 0) {
      generatedText = resultJson.candidates[0].content.parts[0].text;
    } else {
       // Geminiからのレスポンスが期待した形式でない場合
       return createErrorResponse(`予期しないAPIレスポンス形式です: ${responseBody}`);
    }

    // 8. App Inventorに返すJSONを作成
    const output = JSON.stringify({
      "status": "success",
      "result": generatedText
    });

    // 9. 結果をJSON形式で返す
    return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.JSON);

  } catch (error) {
    // スクリプト全体で発生したエラーをキャッチ
    return createErrorResponse(`スクリプトエラー: ${error.toString()}`);
  }
}

/**
 * エラーレスポンスを生成するヘルパー関数
 * @param {string} message - エラーメッセージ
 * @return {ContentService.TextOutput} - エラー情報を格納したJSONオブジェクト
 */
function createErrorResponse(message) {
  const errorOutput = JSON.stringify({
    "status": "error",
    "message": message
  });
  return ContentService.createTextOutput(errorOutput).setMimeType(ContentService.MimeType.JSON);
}

App Inventorアプリ

ページの最後にあるダウンロードセクションからソースコードをダウンロードできます。

[プロジェクト]メニューから[新規プロジェクトを始める]を選択し、"EncyclopediaCamera"と名前を付けます。

デザイン編集

🔒 このページは以下の会員レベルで閲覧できます

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内

gemini,JavaScript,ゲーム,チュートリアル,中級

有名なゲームのテトリスを作ります

このチュートリアルでは、有名なゲームのテトリスを作ります。App Inventorだけで作った記事がありましたが随分大変そうなので、Google Gemini 2.5 Proに相談してウェブビュー内のJavaScriptでゲーム本体は作りました。App InventorからはこのJavaScriptをコントロールしています。

ページの最後にあるダウンロードセクションからソースコードをダウンロードできます。

[プロジェクト]メニューから[新規プロジェクトを始める]を選択し、"Tetris"と名前を付けます。

このチュートリアルで学べること

  • ウェブビューでJavaScriptを実行する方法
  • ウェブビューとの値の受け渡し方法

使用コンポーネント

ボタン、ラベル、ウェブビュー、水平配置、垂直配置

使用ブロック

特別なものは無し

デザイン編集

Screen1のタイトルを"テトリス"にします。

ページの最後にあるダウンロードセクションからtetris.htmlとover.mp3をダウンロードして、両方のファイルをファイルをアップロードボタンをクリックしてアップロードします。

レイアウト パレットから 垂直配置コンポーネント をビューアーに追加します。水平に整列するは"中央揃え"に、高さと横幅は"親要素に合わせる"に設定します。名前は垂直配置1になります。

ユーザーインターフェース パレットからウェブビュー垂直配置1に追加します。高さと横幅は"親要素に合わせる"に設定します。名前はウェブビュー1になります。

レイアウト パレットから 水平配置コンポーネント(水平配置1) を垂直配置コンポーネントに追加し、水平に整列するは"中央揃え"に、横幅は"親要素に合わせる"に設定します。水平配置1の中にユーザーインターフェース パレットからボタンを4個追加します。名前はボタン1ボタン2ボタン3ボタン4になります。

ユーザーインターフェース パレットからボタンラベル水平配置1の下に追加します。名前はそれぞれボタン5ラベル1になります。

以下のようにボタンラベルのプロパティを設定します。

  • ボタン1: テキスト="左″
  • ボタン2: テキスト="回転″
  • ボタン3: テキスト="右″
  • ボタン4: テキスト="下″
  • ボタン5: テキスト="スタート″
  • ラベル1: テキスト="″、横幅="親要素に合わせる"

最後にメディアパレットから、 サウンドコンポーネントをビューアーにドラッグアンドドロップします。ソースプロパティをover.mp3に指定します。

下図のようになります。

ブロック編集機能を使用したプログラミング

🔒 このページは以下の会員レベルで閲覧できます

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内

AI,API,Google Apps Script,nano banana,クラウド,中級,生成AI

このチュートリアルでは、Google Gemini APIを使って、撮影した写真から有名な画家が描いたような絵を表示するアプリを作ります。

2025年8月にGoogleから公開されたNano Banana(Gemini 2.5 Flash Image)は写真を人物や被写体の特徴を保ったまま自然言語で自由に加工・変換できるAIです。生成AIコスプレカメラアプリでは撮影した顔写真からコスプレした全身写真を表示しましたが、別の面白い応用として顔写真を有名画家風に変更してみました。

こちらがGoogleによるNano Bananaの解説です。

https://note.com/google_gemini/n/n1afcbf4d5275?rt=email&sub_rt=daily_report_followee_notes

生成AI画家風カメラアプリのデモビデオをこちらからご覧ください。

ただ、Nano BananaはAPI経由では無料では使えませんので、課金方法の登録が必要です。Nano Bananaを使う場合はGoogle AI Studioから課金方法を登録してください。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いた生成AIお絵描きマジックアプリ用のスクリプトのプロンプトを書き換えています。スクリプトのプロンプトを書き換えるだけで簡単に機能が変更できるので、参考にしてください。

Google Apps Scriptの設定

Gemini APIの設定からウェブアプリとしてのデプロイ方法、使い方などは生成AIお絵描きコーチアプリを参照してください。

Code.gs – 絵画風カメラスクリプト

/**
 * App InventorからのPOSTリクエストを処理するメイン関数
 * @param {Object} e - App Inventorから送信されるイベントオブジェクト
 * @returns {ContentService.TextOutput} - 処理結果(新しい画像のBase64文字列 or エラーメッセージ)をJSON形式で返す
 */
function doPost(e) {
  // レスポンスの基本構造
  let response = {
    status: 'error',
    message: 'An unknown error occurred.',
    imageData: null
  };

  try {
    // POSTされたJSONデータをパース
    if (!e || !e.postData || !e.postData.contents) {
      throw new Error('Invalid request: No data received.');
    }
    const jsonData = JSON.parse(e.postData.contents);
    const base64Data = jsonData.imageData;
    const artist = jsonData.artist;
    const prompt = jsonData.prompt || `写真自体の特徴は一切変更しないで、${artist}風に描いて。言葉での返答は不要`;

    // 入力データの検証
    if (!base64Data || typeof base64Data !== 'string') {
      throw new Error('Invalid request: imageData is missing or not a string.');
    }

    // Base64データからヘッダーを削除
    const cleanedBase64 = cleanBase64String(base64Data);

    // スクリプトプロパティからAPIキーを取得
    const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
    if (!apiKey) {
      throw new Error('Configuration error: API key is not set in Script Properties.');
    }

    // Gemini APIを呼び出して新しい画像を生成
    const generatedBase64 = callGeminiApi(apiKey, cleanedBase64, prompt);

    // 成功レスポンスを作成
    response = {
      status: 'success',
      message: 'Image generated successfully.',
      prompt: prompt,
      imageData: generatedBase64
    };

  } catch (error) {
    // エラーレスポンスを作成
    console.error(error.toString());
    response.message = error.message;
  }

  // 結果をJSON形式で返す
  return ContentService.createTextOutput(JSON.stringify(response))
    .setMimeType(ContentService.MimeType.JSON);
}

/**
 * Base64文字列からヘッダー部分(例: "data:image/png;base64,")を削除する
 * @param {string} base64String - 元のBase64文字列
 * @returns {string} - ヘッダーが削除されたBase64文字列
 */
function cleanBase64String(base64String) {
  // "data:[<mime-type>];base64," という形式のヘッダーを正規表現で探して削除
  return base64String.replace(/^data:image\/[a-z]+;base64,/, '');
}

/**
 * Gemini API (Image Generation) を呼び出して画像を生成する
 * @param {string} apiKey - Google AI StudioのAPIキー
 * @param {string} base64Image - 解析・改良の元となる画像のBase64文字列
 * @param {string} prompt - 画像をどのように改良するかを指定する指示文
 * @returns {string} - 生成された新しい画像のBase64文字列
 */
function callGeminiApi(apiKey, base64Image, prompt) {
  // Geminiの画像生成モデルのエンドポイント
  const model = 'gemini-2.5-flash-image-preview';
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;

  // APIに送信するペイロード(リクエストボディ)
  const payload = {
    contents: [{
      parts: [
        // 1. 指示テキスト
        { text: prompt },
        // 2. 入力画像
        {
          inlineData: {
            mimeType: 'image/png', // App InventorのCanvasはPNG形式
            data: base64Image
          }
        }
      ]
    }],
    generationConfig: {
      // レスポンスとして画像とテキストの両方を要求する
      responseModalities: ['IMAGE', 'TEXT'] 
    },
  };

  // APIリクエストのオプション
  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true // HTTPエラー時に例外をスローさせず、レスポンスを直接受け取る
  };

  // APIにリクエストを送信
  const response = UrlFetchApp.fetch(url, options);
  const responseCode = response.getResponseCode();
  const responseBody = response.getContentText();

  // レスポンスを解析
  if (responseCode === 200) {
    const jsonResponse = JSON.parse(responseBody);
    
    // レスポンスの中から画像データ部分を探す
    const imagePart = jsonResponse?.candidates?.[0]?.content?.parts?.find(p => p.inlineData);
    
    if (imagePart && imagePart.inlineData.data) {
      // 画像のBase64データを返す
      return imagePart.inlineData.data;
    } else {
      // 画像が見つからない場合のエラー
      console.error('API response did not contain image data. Response: ' + responseBody);
      // テキスト部分があれば、それをエラーメッセージとして利用
      const textPart = jsonResponse?.candidates?.[0]?.content?.parts?.find(p => p.text);
      const errorMessage = textPart ? textPart.text : 'No image data in response.';
      throw new Error(`API Error: ${errorMessage}`);
    }
  } else {
    // APIがエラーを返した場合
    console.error('API Error. Code: ' + responseCode + '. Body: ' + responseBody);
    throw new Error(`API request failed with status code ${responseCode}. Details: ${responseBody}`);
  }
}

App Inventorアプリ

ページの最後にあるダウンロードセクションからソースコードをダウンロードできます。

[プロジェクト]メニューから[新規プロジェクトを始める]を選択し、"ArtCamera"と名前を付けます。

デザイン編集

🔒 このページは以下の会員レベルで閲覧できます

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内

AI,API,Google Apps Script,nano banana,クラウド,中級,生成AI

このチュートリアルでは、Google Gemini APIを使って、撮影した顔写真からコスプレした全身写真を表示するアプリを作ります。

2025年8月にGoogleから公開されたNano Banana(Gemini 2.5 Flash Image)は写真を人物や被写体の特徴を保ったまま自然言語で自由に加工・変換できるAIです。生成AI表情変更アプリでは撮影した顔写真の表情を変更しましたが、もっと面白い応用としてコスプレさせてみました。

こちらがGoogleによるNano Bananaの解説です。

https://note.com/google_gemini/n/n1afcbf4d5275?rt=email&sub_rt=daily_report_followee_notes

生成AIコスプレカメラアプリのデモビデオをこちらからご覧ください。

ただ、Nano BananaはAPI経由では無料では使えませんので、課金方法の登録が必要です。Nano Bananaを使う場合はGoogle AI Studioから課金方法を登録してください。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いた生成AIお絵描きマジックアプリ用のスクリプトのプロンプトを書き換えています。

Google Apps Scriptの設定

Gemini APIの設定からウェブアプリとしてのデプロイ方法、使い方などは生成AIお絵描きコーチアプリを参照してください。

Code.gs – コスプレカメラスクリプト

/**
 * App InventorからのPOSTリクエストを処理するメイン関数
 * @param {Object} e - App Inventorから送信されるイベントオブジェクト
 * @returns {ContentService.TextOutput} - 処理結果(新しい画像のBase64文字列 or エラーメッセージ)をJSON形式で返す
 */
function doPost(e) {
  // レスポンスの基本構造
  let response = {
    status: 'error',
    message: 'An unknown error occurred.',
    imageData: null
  };

  try {
    // POSTされたJSONデータをパース
    if (!e || !e.postData || !e.postData.contents) {
      throw new Error('Invalid request: No data received.');
    }
    const jsonData = JSON.parse(e.postData.contents);
    const base64Data = jsonData.imageData;
    const characer = jsonData.characer;
    const prompt = jsonData.prompt || `全身写真にしてから顔自体は一切変更しないで、${characer}でコスプレして。言葉での返答は不要`;

    // 入力データの検証
    if (!base64Data || typeof base64Data !== 'string') {
      throw new Error('Invalid request: imageData is missing or not a string.');
    }

    // Base64データからヘッダーを削除
    const cleanedBase64 = cleanBase64String(base64Data);

    // スクリプトプロパティからAPIキーを取得
    const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
    if (!apiKey) {
      throw new Error('Configuration error: API key is not set in Script Properties.');
    }

    // Gemini APIを呼び出して新しい画像を生成
    const generatedBase64 = callGeminiApi(apiKey, cleanedBase64, prompt);

    // 成功レスポンスを作成
    response = {
      status: 'success',
      message: 'Image generated successfully.',
      prompt: prompt,
      imageData: generatedBase64
    };

  } catch (error) {
    // エラーレスポンスを作成
    console.error(error.toString());
    response.message = error.message;
  }

  // 結果をJSON形式で返す
  return ContentService.createTextOutput(JSON.stringify(response))
    .setMimeType(ContentService.MimeType.JSON);
}

/**
 * Base64文字列からヘッダー部分(例: "data:image/png;base64,")を削除する
 * @param {string} base64String - 元のBase64文字列
 * @returns {string} - ヘッダーが削除されたBase64文字列
 */
function cleanBase64String(base64String) {
  // "data:[<mime-type>];base64," という形式のヘッダーを正規表現で探して削除
  return base64String.replace(/^data:image\/[a-z]+;base64,/, '');
}

/**
 * Gemini API (Image Generation) を呼び出して画像を生成する
 * @param {string} apiKey - Google AI StudioのAPIキー
 * @param {string} base64Image - 解析・改良の元となる画像のBase64文字列
 * @param {string} prompt - 画像をどのように改良するかを指定する指示文
 * @returns {string} - 生成された新しい画像のBase64文字列
 */
function callGeminiApi(apiKey, base64Image, prompt) {
  // Geminiの画像生成モデルのエンドポイント
  const model = 'gemini-2.5-flash-image-preview';
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;

  // APIに送信するペイロード(リクエストボディ)
  const payload = {
    contents: [{
      parts: [
        // 1. 指示テキスト
        { text: prompt },
        // 2. 入力画像
        {
          inlineData: {
            mimeType: 'image/png', // App InventorのCanvasはPNG形式
            data: base64Image
          }
        }
      ]
    }],
    generationConfig: {
      // レスポンスとして画像とテキストの両方を要求する
      responseModalities: ['IMAGE', 'TEXT'] 
    },
  };

  // APIリクエストのオプション
  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true // HTTPエラー時に例外をスローさせず、レスポンスを直接受け取る
  };

  // APIにリクエストを送信
  const response = UrlFetchApp.fetch(url, options);
  const responseCode = response.getResponseCode();
  const responseBody = response.getContentText();

  // レスポンスを解析
  if (responseCode === 200) {
    const jsonResponse = JSON.parse(responseBody);
    
    // レスポンスの中から画像データ部分を探す
    const imagePart = jsonResponse?.candidates?.[0]?.content?.parts?.find(p => p.inlineData);
    
    if (imagePart && imagePart.inlineData.data) {
      // 画像のBase64データを返す
      return imagePart.inlineData.data;
    } else {
      // 画像が見つからない場合のエラー
      console.error('API response did not contain image data. Response: ' + responseBody);
      // テキスト部分があれば、それをエラーメッセージとして利用
      const textPart = jsonResponse?.candidates?.[0]?.content?.parts?.find(p => p.text);
      const errorMessage = textPart ? textPart.text : 'No image data in response.';
      throw new Error(`API Error: ${errorMessage}`);
    }
  } else {
    // APIがエラーを返した場合
    console.error('API Error. Code: ' + responseCode + '. Body: ' + responseBody);
    throw new Error(`API request failed with status code ${responseCode}. Details: ${responseBody}`);
  }
}

App Inventorアプリ

ページの最後にあるダウンロードセクションからソースコードをダウンロードできます。

[プロジェクト]メニューから[新規プロジェクトを始める]を選択し、"CosplayCamera"と名前を付けます。

デザイン編集

🔒 このページは以下の会員レベルで閲覧できます

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内

AI,API,Google Apps Script,nano banana,クラウド,中級,生成AI

このチュートリアルでは、Google Gemini APIを使って撮影した顔写真の表情を変更して表示するアプリを作ります。

2025年8月にGoogleから公開されたNano Banana(Gemini 2.5 Flash Image)は写真を人物や被写体の特徴を保ったまま自然言語で自由に加工・変換できるAIです。たとえば、従来の gemini-2.0-flash-preview-image-generation ではうまく顔の表情の変換はできませんでしたが、Nano Bananaだと上手に変換できます。こちらがGoogleの解説です。

https://note.com/google_gemini/n/n1afcbf4d5275?rt=email&sub_rt=daily_report_followee_notes

生成AI表情変更アプリのデモビデオをこちらからご覧ください。

ただ、Nano BananaはAPI経由では無料では使えませんので、課金方法の登録が必要です。Nano Bananaを使う場合はGoogle AI Studioから課金方法を登録してください。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いた生成AIお絵描きマジックアプリ用のスクリプトのプロンプトを書き換えています。

Google Apps Scriptの設定

Gemini APIの設定からウェブアプリとしてのデプロイ方法、使い方などは生成AIお絵描きコーチアプリを参照してください。

Code.gs – 表情変更スクリプト

/**
 * App InventorからのPOSTリクエストを処理するメイン関数
 * @param {Object} e - App Inventorから送信されるイベントオブジェクト
 * @returns {ContentService.TextOutput} - 処理結果(新しい画像のBase64文字列 or エラーメッセージ)をJSON形式で返す
 */
function doPost(e) {
  // レスポンスの基本構造
  let response = {
    status: 'error',
    message: 'An unknown error occurred.',
    imageData: null
  };

  try {
    // POSTされたJSONデータをパース
    if (!e || !e.postData || !e.postData.contents) {
      throw new Error('Invalid request: No data received.');
    }
    const jsonData = JSON.parse(e.postData.contents);
    const base64Data = jsonData.imageData;
// expression には表情が入っている。「笑った顔」など
    const expression = jsonData.expression;
    const prompt = jsonData.prompt || `この写真が顔写真ならば${expression}に変換して画像を戻して。言葉での返答は不要だから画像を戻して`;

    // 入力データの検証
    if (!base64Data || typeof base64Data !== 'string') {
      throw new Error('Invalid request: imageData is missing or not a string.');
    }

    // Base64データからヘッダーを削除
    const cleanedBase64 = cleanBase64String(base64Data);

    // スクリプトプロパティからAPIキーを取得
    const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
    if (!apiKey) {
      throw new Error('Configuration error: API key is not set in Script Properties.');
    }

    // Gemini APIを呼び出して新しい画像を生成
    const generatedBase64 = callGeminiApi(apiKey, cleanedBase64, prompt);

    // 成功レスポンスを作成
    response = {
      status: 'success',
      message: 'Image generated successfully.',
      prompt: prompt,
      imageData: generatedBase64
    };

  } catch (error) {
    // エラーレスポンスを作成
    console.error(error.toString());
    response.message = error.message;
  }

  // 結果をJSON形式で返す
  return ContentService.createTextOutput(JSON.stringify(response))
    .setMimeType(ContentService.MimeType.JSON);
}

/**
 * Base64文字列からヘッダー部分(例: "data:image/png;base64,")を削除する
 * @param {string} base64String - 元のBase64文字列
 * @returns {string} - ヘッダーが削除されたBase64文字列
 */
function cleanBase64String(base64String) {
  // "data:[<mime-type>];base64," という形式のヘッダーを正規表現で探して削除
  return base64String.replace(/^data:image\/[a-z]+;base64,/, '');
}

/**
 * Gemini API (Image Generation) を呼び出して画像を生成する
 * @param {string} apiKey - Google AI StudioのAPIキー
 * @param {string} base64Image - 解析・改良の元となる画像のBase64文字列
 * @param {string} prompt - 画像をどのように改良するかを指定する指示文
 * @returns {string} - 生成された新しい画像のBase64文字列
 */
function callGeminiApi(apiKey, base64Image, prompt) {
  // Geminiの画像生成モデルのエンドポイント
  const model = 'gemini-2.5-flash-image-preview';
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;

  // APIに送信するペイロード(リクエストボディ)
  const payload = {
    contents: [{
      parts: [
        // 1. 指示テキスト
        { text: prompt },
        // 2. 入力画像
        {
          inlineData: {
            mimeType: 'image/png', // App InventorのCanvasはPNG形式
            data: base64Image
          }
        }
      ]
    }],
    generationConfig: {
      // レスポンスとして画像とテキストの両方を要求する
      responseModalities: ['IMAGE', 'TEXT'] 
    },
  };

  // APIリクエストのオプション
  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true // HTTPエラー時に例外をスローさせず、レスポンスを直接受け取る
  };

  // APIにリクエストを送信
  const response = UrlFetchApp.fetch(url, options);
  const responseCode = response.getResponseCode();
  const responseBody = response.getContentText();

  // レスポンスを解析
  if (responseCode === 200) {
    const jsonResponse = JSON.parse(responseBody);
    
    // レスポンスの中から画像データ部分を探す
    const imagePart = jsonResponse?.candidates?.[0]?.content?.parts?.find(p => p.inlineData);
    
    if (imagePart && imagePart.inlineData.data) {
      // 画像のBase64データを返す
      return imagePart.inlineData.data;
    } else {
      // 画像が見つからない場合のエラー
      console.error('API response did not contain image data. Response: ' + responseBody);
      // テキスト部分があれば、それをエラーメッセージとして利用
      const textPart = jsonResponse?.candidates?.[0]?.content?.parts?.find(p => p.text);
      const errorMessage = textPart ? textPart.text : 'No image data in response.';
      throw new Error(`API Error: ${errorMessage}`);
    }
  } else {
    // APIがエラーを返した場合
    console.error('API Error. Code: ' + responseCode + '. Body: ' + responseBody);
    throw new Error(`API request failed with status code ${responseCode}. Details: ${responseBody}`);
  }
}

App Inventorアプリ

ページの最後にあるダウンロードセクションからソースコードをダウンロードできます。

[プロジェクト]メニューから[新規プロジェクトを始める]を選択し、"FaceChanger"と名前を付けます。

デザイン編集

🔒 このページは以下の会員レベルで閲覧できます

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内

AI,API,gemini,Google Apps Script,クラウド,中級,生成AI

このチュートリアルでは、Google Gemini APIを使って撮影した写真の解説をしてくれるアプリを作ります。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いた生成AIお絵描きコーチアプリ用のスクリプトのプロンプトを書き換えています。

Google AI Studio で作るGemini APIは課金方法を登録しなくても以下の範囲ならば無料で使えます。

  • 1 分あたりのリクエスト数: 5
  • 1 日あたりのリクエスト数: 250,000
  • 1 分あたりのトークン数(入力): 100

Google Apps Scriptの設定

Gemini APIの設定からウェブアプリとしてのデプロイ方法、使い方などは生成AIお絵描きコーチアプリを参照してください。

Code.gs – 写真解説スクリプト

/**
 * App InventorからのPOSTリクエストを処理する関数
 * @param {Object} e - App Inventorから送信されるイベントオブジェクト
 * @return {ContentService.TextOutput} - Gemini APIからの結果をJSON形式で返す
 */
function doPost(e) {
  // エラーハンドリング
  try {
    // 1. App Inventorから送信されたJSONデータを解析
    const postData = JSON.parse(e.postData.contents);
    const base64Image = postData.image; // App Inventor側で "image" というキーに設定

    // 2. スクリプトプロパティからAPIキーを読み込む
    const API_KEY = PropertiesService.getScriptProperties().getProperty('API_KEY');
    if (!API_KEY) {
      return createErrorResponse('APIキーが設定されていません。');
    }

    // 3. Gemini APIのエンドポイントURL
    const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent?key=${API_KEY}`;
  
    // 4. Gemini APIに送信するリクエストボディを作成
    const payload = {
      "contents": [{
        "parts": [{
          "text": "画像を解析してください。  まず、ランドマークや観光地が写っている場合は正式な地名を含めてください。  そうでない場合は人物を確認し、有名人(俳優・女優・著名人)であればその人物名を含めてください。  最後に、写真全体の内容を200文字以内で自然な文章として解説してください。  出力は必ず一つの段落の文章で返答してください。箇条書きは禁止です。"
        }, {
          "inline_data": {
            "mime_type": "image/png", // App InventorのCanvasはPNG形式
            "data": base64Image
          }
        }]
      }]
    };

    // 5. Gemini APIにリクエストを送信するための設定
    const options = {
      'method': 'post',
      'contentType': 'application/json',
      'payload': JSON.stringify(payload),
      'muteHttpExceptions': true // エラーレスポンスを例外ではなくオブジェクトとして取得
    };

    // 6. APIにリクエストを送信
    const response = UrlFetchApp.fetch(API_URL, options);
    const responseCode = response.getResponseCode();
    const responseBody = response.getContentText();

    if (responseCode !== 200) {
       return createErrorResponse(`APIリクエストエラー: ${responseCode} ${responseBody}`);
    }
    
    // 7. APIからのレスポンスを解析
    const resultJson = JSON.parse(responseBody);
    
    // レスポンスの構造をチェックし、テキスト部分を取得
    let generatedText = "解析結果を取得できませんでした。";
    if (resultJson.candidates && resultJson.candidates.length > 0 &&
        resultJson.candidates[0].content && resultJson.candidates[0].content.parts &&
        resultJson.candidates[0].content.parts.length > 0) {
      generatedText = resultJson.candidates[0].content.parts[0].text;
    } else {
       // Geminiからのレスポンスが期待した形式でない場合
       return createErrorResponse(`予期しないAPIレスポンス形式です: ${responseBody}`);
    }

    // 8. App Inventorに返すJSONを作成
    const output = JSON.stringify({
      "status": "success",
      "result": generatedText
    });

    // 9. 結果をJSON形式で返す
    return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.JSON);

  } catch (error) {
    // スクリプト全体で発生したエラーをキャッチ
    return createErrorResponse(`スクリプトエラー: ${error.toString()}`);
  }
}

/**
 * エラーレスポンスを生成するヘルパー関数
 * @param {string} message - エラーメッセージ
 * @return {ContentService.TextOutput} - エラー情報を格納したJSONオブジェクト
 */
function createErrorResponse(message) {
  const errorOutput = JSON.stringify({
    "status": "error",
    "message": message
  });
  return ContentService.createTextOutput(errorOutput).setMimeType(ContentService.MimeType.JSON);
}

App Inventorアプリ

ページの最後にあるダウンロードセクションからソースコードをダウンロードできます。

[プロジェクト]メニューから[新規プロジェクトを始める]を選択し、"KnowledgeCamera"と名前を付けます。

デザイン編集

🔒 このページは以下の会員レベルで閲覧できます

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内

AI,API,gemini,Google Apps Script,クラウド,センサー,中級,地図,生成AI

このチュートリアルでは、地図の中心周辺の観光名所をGoogle Gemini APIに問い合わせて地図上に表示し、マーカーをクリックするとその場所の説明を表示するアプリを作ります。

生成AI観光名所地図アプリでは観光名所をGeminiで検索してその場所を地図上にマーカーで表示しましたが、結構知らない場所も表示されるので、どんなところかわかると便利だろうと思い、マーカーをクリックするとその名前と住所に加えてその場所の説明も表示するようにしました。

この機能追加のために、アプリ本体とGASスクリプトを変更しています。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。

Google AI Studio で作るGemini APIは課金方法を登録しなくても以下の範囲ならば無料で使えます。

  • 1 分あたりのリクエスト数: 5
  • 1 日あたりのリクエスト数: 250,000
  • 1 分あたりのトークン数(入力): 100

Google Apps Scriptの設定

Google Apps Scriptに設定方法などはここを参照してください。

GASスクリプトの変更

200文字以内の説明も返すようにマニュアルでスクリプトの中のプロンプトを変更します。

観光地検索API (Google Apps Script)

/**
 * App InventorからのGETリクエストを処理するメイン関数
 * @param {object} e - App Inventorから渡されるイベントオブジェクト
 * @returns {ContentService.TextOutput} - 観光地情報のJSONまたはエラーメッセージ
 */
function doGet(e) {
  // レスポンスのMIMEタイプをJSONに設定
  const response = ContentService.createTextOutput();
  response.setMimeType(ContentService.MimeType.JSON);

  try {
    // クエリパラメータから緯度、経度、半径を取得
    const lat = e.parameter.latitude;
    const lon = e.parameter.longitude;
    const radius = e.parameter.radius;

    // パラメータが不足している場合はエラーを返す
    if (!lat || !lon || !radius) {
      response.setContent(JSON.stringify({
        error: "パラメータが不足しています。'latitude', 'longitude', 'radius'が必要です。"
      }));
      return response;
    }

    // Gemini APIを呼び出して観光地情報を取得
    const touristSpots = getTouristSpots(lat, lon, radius);
    
    // 取得した情報をJSON形式で返す
    response.setContent(JSON.stringify(touristSpots));

  } catch (error) {
    // エラーが発生した場合、エラーメッセージをJSONで返す
    response.setContent(JSON.stringify({
      error: "データの取得中にエラーが発生しました。",
      details: error.message
    }));
  }

  return response;
}

/**
 * Gemini APIを呼び出して、指定された座標周辺の観光地情報を取得する
 * @param {number} latitude - 中心の緯度
 * @param {number} longitude - 中心の経度
 * @param {number} radius - 検索半径(メートル)
 * @returns {Array<Object>} - 観光地のリスト
 */
function getTouristSpots(latitude, longitude, radius) {
  // スクリプトプロパティからAPIキーを読み込む
  const API_KEY = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
  if (!API_KEY) {
    throw new Error("APIキーがスクリプトプロパティに設定されていません。");
  }

  const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${API_KEY}`;

  // Geminiに渡すプロンプト(指示文)- 緯度経度が不要なことを明記
  const prompt = `
あなたは優秀なローカルツアーガイドです。
以下の中心座標と半径の範囲内にある観光地を最大5件まで探してください。
結果は必ず指定されたJSON形式で、名前と住所と200文字以内の説明のみを含めてください。緯度と経度は不要です。

中心緯度: ${latitude}
中心経度: ${longitude}
半径: ${radius}メートル
`;

  // Gemini APIに送信するデータ(ペイロード)- responseSchemaから緯度経度を削除
  const payload = {
    "contents": [{
      "parts": [{
        "text": prompt
      }]
    }],
    "generationConfig": {
      "responseMimeType": "application/json",
      "responseSchema": {
        "type": "ARRAY",
        "items": {
          "type": "OBJECT",
          "properties": {
            "name": { "type": "STRING", "description": "観光地の名前" },
            "address": { "type": "STRING", "description": "観光地の住所" },
            "desc": { "type": "STRING", "description": "観光地の説明" }
          },
          "required": ["name", "address"]
        }
      }
    }
  };

  // APIリクエストのオプション
  const options = {
    'method': 'post',
    'contentType': 'application/json',
    'payload': JSON.stringify(payload),
    'muteHttpExceptions': true // HTTPエラー時もレスポンスを返す
  };

  // APIを呼び出す
  const response = UrlFetchApp.fetch(API_URL, options);
  const responseCode = response.getResponseCode();
  const responseBody = response.getContentText();

  if (responseCode !== 200) {
    throw new Error(`APIリクエストに失敗しました。ステータスコード: ${responseCode}, レスポンス: ${responseBody}`);
  }
  
  // レスポンスからJSON部分を抽出してパースする
  const result = JSON.parse(responseBody);
  const jsonText = result.candidates[0].content.parts[0].text;
  
  return JSON.parse(jsonText);
}

App Inventorアプリ

ページの最後にあるダウンロードセクションからソースコードをダウンロードできます。

[プロジェクト]メニューから[新規プロジェクトを始める]を選択し、"AITravelGuide"と名前を付けます。

デザイン編集

🔒 このページは以下の会員レベルで閲覧できます

残りのコンテンツを表示するにはログインする必要がありますどうぞ . 会員ではないですか ? 入会のご案内