ChatGPT,アプリ開発,ノート,生成AI

考えたことメモ > App InventorからJavaScriptへどうやって移行するのか?

Google Gemini 2.5 Pro Deep Researchによれば、「一度App Inventorで作ったアプリをPythonで再現してみるというアプローチは、目的が明確なため非常に効果的な学習方法です」とのことでした。これは、AI時代における実践的なプログラミング学習の一例といえます。

Pythonは文法が比較的シンプルで、初心者にも扱いやすい言語です。AI開発、データ分析、Webサイト制作、作業の自動化など、幅広い分野に応用できる点が特徴です。

一方、JavaScriptPythonにならんで非常にポピュラーなプログラミング言語で、ウェブページで使われるのでウェブサイトを作るには必須と言えるプログラミング言語です。node.jsを使えばサーバーサイドの開発もできるので、ウェブアプリの開発にはこれ一つ知っていれば十分です。

App InventorからPythonへどうやって移行するのか?」でChatGPT-4oがApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてそのまま動作するPythonコードを書けました。

ここではChatGPT-5を使ってApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてJavaScriptコードを書いてみます。

出発点のApp Inventorアプリは「オンデマンドアカデミー」で紹介されている カラフルお絵かきアプリです。このアプリは、基本的なUIの構成やイベント処理の理解に適しています。

aiaファイルの読み込みと解析

要件定義書作成

JavaScriptプログラミング

動作確認

サマリー

ChatGPT-5はApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてそのまま動作するJavaScriptコードを書けました。

aiaファイルの読み込みと解析

プロンプト

あなたはApp Inventor 2の内部構造に精通したプロのソフトウェアエンジニアです。このaiaファイルを完全に解析してください。

返答

了解です。アップロードいただいた **DoodlePlus.aia** を、中身(.scm / .bky / project.properties)まで読み解いて、構造・ロジック・設定を“丸裸”にしてご報告します。以下は App Inventor 2 の内部表現(JSON/Blockly XML)をベースにした、技術者向けの完全解析です。

# プロジェクト概要

* アプリ名: **DoodlePlus**(画面タイトル: **お絵かきアプリ+**)
* スクリーン数: **1(Screen1)**
* 作成/保存タイムスタンプ: **Sat Apr 12 01:36:28 UTC 2025**
* バージョン: **versionCode=1 / versionName=1.0**
* レイアウト: **sizing=Responsive**, **ActionBar=True**, **Theme=AppTheme.Light.DarkActionBar**
* 既定の権限/設定: **useslocation=False**, **defaultfilescope=App**, **showlistsasjson=True**
* 配色(プロジェクト設定)

  * Primary: `&HFF3F51B5`
  * Primary Dark: `&HFF303F9F`
  * Accent: `&HFFFF4081`

# 画面とUI階層(Screen1)

Screen1 直下に下記 12 コンポーネントが存在します。

* **キャンバス1**(Canvas): `Width=-2`, `Height=-2` → いずれも **Fill Parent**
* **水平配置1**(HorizontalArrangement)
  ├─ ボタン **黒**(BackgroundColor `#000000`, FontSize 10)
  ├─ ボタン **青**(`#0000FF`, FontSize 10)
  ├─ ボタン **グリーン**(`#00FF00`, FontSize 10)
  └─ ボタン **赤**(`#FF0000`, FontSize 10)
* **水平配置2**
  ├─ ボタン **細**(Text=「細」)
  └─ ボタン **中**(Text=「中」)
* **水平配置3**
  ├─ ボタン **画面消去**(Text=「画面消去」)
  └─ ボタン **消しゴム**(Text=「消しゴム」)

> 補足: Canvas は全面に描画。3 つの水平配置がパレット(色/太さ/アクション)になっています。

# グローバル変数(global\_declaration)

* **選択マーク** : 文字列 `"✔︎"`
* **選択色ボタン** : Button 参照(初期値 **黒**)
* **色ボタンリスト** : `[黒, 青, グリーン, 赤]`
* **ペン太さ** : Button 参照(初期値 **細**)
* **太さボタンリスト** : `[細, 中]`
* **太さリスト** : `[3.0, 5.0]`  ← 太さボタンの位置と対応
* **消しゴム** : `false`(ブール)

# 手続き(procedures)

1. **色ボタンリセット**

   * `色ボタンリスト` を全走査し、各ボタンの **Text を空文字**にする(✔︎マーク消去)。

2. **色ボタン選択(ボタン)**

   * 以前の **選択色ボタン** の **TextColor を黒**に戻す。
   * 引数 **ボタン** の **TextColor を白**にし、\*\*Text に「✔︎」\*\*を表示。
   * **選択色ボタン ← ボタン** に更新。
   * **キャンバス1.PaintColor ← 選択色ボタン.BackgroundColor** に設定。
   * (実装全体として)色選択時には **消しゴム ← false** に戻す挙動が組まれています。

3. **太さボタンリセット**

   * `太さボタンリスト` を全走査し、各ボタンの **TextColor を黒**に戻す。

4. **太さボタン選択(ボタン)**

   * **ボタン.TextColor ← 赤**(選択強調)。
   * **ペン太さ ← ボタン** に更新。
   * `太さボタンリスト` 内での **ペン太さ** の位置に基づき、`太さリスト` から対応値を取得し、
     **キャンバス1.LineWidth** に反映(`細→3.0px`, `中→5.0px`)。

5. **消しゴムボタン選択**

   * **消しゴム** をトグル。

     * ON 時: **キャンバス1.PaintColor ← 白**。
     * OFF 時: **キャンバス1.PaintColor ← 選択色ボタン.BackgroundColor** に戻す。

# イベント駆動フロー(重要)

* **Screen1.Initialize**

  * `色ボタン選択(選択色ボタン)` を最初に呼び(既定は **黒**)、
  * `太さボタン選択(ペン太さ)` を次に呼ぶ(既定は **細**)。

* **任意の Button.Click(Any Button)**
  クリックされた **component** に応じて分岐:

  1. `色ボタンリスト` に含まれる →
     **色ボタンリセット** → **消しゴム ← false** → **色ボタン選択(component)**
  2. `太さボタンリスト` に含まれる →
     **太さボタンリセット** → **太さボタン選択(component)**
  3. **画面消去** → **キャンバス1.Clear()**
  4. **消しゴム** → **消しゴムボタン選択()**

* **キャンバス1.Dragged(prevX, prevY, currentX, currentY, ...)**

  * **キャンバス1.DrawLine(prevX, prevY, currentX, currentY)**
    (線の色=`PaintColor`、太さ=`LineWidth` はボタン操作で設定済み)

# ブロック統計(Screen1.bky)

* 総ブロック数: **126**
* 代表的なブロック種別(上位)

  * `lexical_variable_get` 34 / `component_component_block` 14 / `component_set_get` 14
  * `global_declaration` 7 / `procedures_callnoreturn` 7 / `procedures_defnoreturn` 5
  * 制御系 `controls_if` 4 / `controls_forEach` 2
  * 色定数: `color_black` 3, `color_white` 2, `color_red` 2 など
* コンポーネントメソッド呼び出し

  * **Canvas.DrawLine**(ドラッグ時の描画)
  * **Canvas.Clear**(「画面消去」)

# 擬似コード(挙動イメージ)

```text
On Initialize:
  色ボタン選択(選択色ボタン=黒)
  太さボタン選択(ペン太さ=細)

Any Button.Click(component):
  if component ∈ 色ボタンリスト:
    色ボタンリセット()
    消しゴム := false
    色ボタン選択(component)          // ✔︎を付けて PaintColor を更新
  else if component ∈ 太さボタンリスト:
    太さボタンリセット()
    太さボタン選択(component)        // TextColor=赤, LineWidth を更新
  else if component == 画面消去:
    Canvas.Clear()
  else if component == 消しゴム:
    消しゴムボタン選択()             // 白/元色 へ PaintColor 切替

Canvas.Dragged(prevX, prevY, x, y):
  Canvas.DrawLine(prevX, prevY, x, y)
```

# 仕様上のポイント/注意

* **色選択と消しゴムは排他**:色ボタンを押すと必ず **消しゴム=false** に戻り、色の BackgroundColor が `PaintColor` に復帰します。
* **太さは 2 段階**(3px / 5px)。必要に応じて `太さボタンリスト` と `太さリスト` に要素を追加すれば拡張容易。
* **選択状態の可視化**:

  * 色…✔︎(Text)と TextColor(白/黒)で表現
  * 太さ…Button の TextColor(赤)で表現
* **永続化や共有は未実装**:描画の保存(画像出力/共有)、Undo/Redo、ズームなどはなし。

# 改善提案(実装のしやすさ順)

1. **太さ段階の拡張**:`太さボタンリスト` と `太さリスト` に「太」「極太」などを追加。
2. **Undo/Redo**:ドラッグで引いた線分の履歴(座標+色+太さ)をスタックに積む設計。
3. **保存/共有**:`Canvas.Save`(拡張/外部コンポーネント)や `ActivityStarter` 連携で画像保存・共有。
4. **筆圧/スムージング**:ドラッグ点列の間引きや補間、丸めを入れて滑らかなストロークに。
5. **UI アクセシビリティ**:選択強調をテキスト色だけに依存せず、アウトラインやアイコン化で視認性UP。
6. **消しゴムの太さ別管理**:消しゴム使用中の LineWidth を色ペンとは別に持つと使い勝手向上。

# 解析アーカイブ(生データ)

必要であれば、内部ファイルを直接ご確認いただけます:

* [Screen1.scm(JSON 抜粋テキスト)](sandbox:/mnt/data/Screen1_scm.txt)
* Blockly XML(抜粋)

  * [Screen1.Initialize イベント](sandbox:/mnt/data/event_init.xml)
  * [Any Button.Click イベント](sandbox:/mnt/data/event_any_button_click.xml)
  * [手続き: 色ボタン選択](sandbox:/mnt/data/proc_color_select.xml)
  * [手続き: 太さボタン選択](sandbox:/mnt/data/proc_thick_select.xml)
  * [手続き: 消しゴムボタン選択](sandbox:/mnt/data/proc_erasure.xml)
* コンポーネント一覧(CSV): [components.csv](sandbox:/mnt/data/components.csv)

---

さらに深掘り(たとえば「Undo をこう設計して」「太さを 4 段階にしてブロックを出して」等)も、そのままこちらで具体ブロックをご提案できます。どこから手を付けるか、希望があれば教えてください!

要件定義書作成

プロンプト

このアプリの要件定義書をMarkdownで書いて

返答

了解しました。先ほど解析した **DoodlePlus.aia** の内容を基に、ソフトウェアエンジニアが利用できる形式の **要件定義書(Markdown)** をまとめました。

---

# 要件定義書

## プロジェクト概要

* **アプリ名**: DoodlePlus
* **概要**: スマートフォンやタブレット上で指先を用いて自由にお絵かきできるアプリ。色や線の太さを変更でき、消しゴムや画面消去機能を備える。
* **対象プラットフォーム**: Android(MIT App Inventor 2 ベース)
* **バージョン**: 1.0

---

## 目的

* 子供から大人まで誰でも直感的に絵を描ける「シンプルなお絵かきアプリ」を提供する。
* 学習や創作の補助ツールとして利用可能。

---

## 機能要件

### 描画機能

* 指のドラッグ操作により線を描画できる。
* 線の色と太さをユーザーが選択可能。

### 色選択

* 黒、青、緑、赤の 4 色から選択可能。
* 選択中の色は ✔︎ マークで可視化される。
* 色ボタンを選択した際、消しゴムモードは解除される。

### 太さ選択

* 細(3px)、中(5px)の 2 段階。
* 選択中の太さは赤文字で表示される。

### 消しゴム機能

* ON/OFF 切り替え可能。
* ON 時はキャンバスの背景色(白)で描画し、既存の線を消せる。
* OFF 時は直前に選択した色へ戻る。

### 画面消去

* ワンタップでキャンバス全体をクリアする。

---

## 非機能要件

* **UI**: シンプルで直感的。文字よりも色やアイコンを重視。
* **操作性**: 子供でも使える直感的な操作。
* **パフォーマンス**: 描画時に遅延が発生しないこと。
* **可搬性**: Android スマホ/タブレット両対応。

---

## UI設計

### 画面構成

1. **キャンバス領域**

   * 画面全体に広がる描画スペース。

2. **色選択バー(水平配置1)**

   * 黒、青、緑、赤のボタン。

3. **太さ選択バー(水平配置2)**

   * 細、中のボタン。

4. **操作バー(水平配置3)**

   * 「画面消去」ボタン。
   * 「消しゴム」ボタン。

---

## データ要件

* 永続化なし(保存機能なし)。
* アプリ起動ごとにキャンバスは初期化される。

---

## 制約条件

* MIT App Inventor 2 による開発。
* デバイス依存: Android 4.4 以上を想定。

---

## 今後の拡張要件(任意)

* 線の太さ段階の追加(太、極太)。
* 描画内容の保存(端末ストレージ/共有)。
* Undo/Redo 機能。
* カラーパレットによる色追加。

---

---

この文書は「現状の DoodlePlus.aia の実装仕様」=「要件定義書」としてまとめています。

👉 ご希望があれば、このまま **設計書(UIフローや処理シーケンス図付き)** に発展させることも可能です。やりますか?

JavaScriptプログラミング

プロンプト

この要件定義書に基づいてJavaScriptのコードを書いて。初心者にも何をしているかわかるように、また、App Inventorアプリとの対応関係がわかるように、詳しいコメントをつけて。

返答

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>DoodlePlus (JavaScript 版)</title>
  <style>
    /* =============================
       シンプルな見た目のためのCSS
       ============================= */
    :root {
      --primary: #3f51b5; /* App Inventor プロジェクト設定のPrimary色に合わせた雰囲気 */
      --accent: #ff4081;
      --bg: #ffffff;
      --text: #222;
      --muted: #888;
    }
    * { box-sizing: border-box; }
    html, body { height: 100%; margin: 0; background: var(--bg); color: var(--text); font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans JP", "Hiragino Kaku Gothic ProN", Meiryo, sans-serif; }
    main { display: grid; grid-template-rows: auto 1fr; height: 100%; }
    header { padding: .5rem .75rem; border-bottom: 1px solid #eee; display: flex; align-items: center; gap: .75rem; }
    header h1 { font-size: 1rem; font-weight: 600; margin: 0; color: var(--primary); }

    /* ツールバー(App Inventorの「水平配置」に相当) */
    #toolbar { display: flex; gap: .75rem; padding: .5rem; border-bottom: 1px solid #eee; flex-wrap: wrap; }
    .group { display: flex; gap: .5rem; align-items: center; }
    .group-label { font-size: .85rem; color: var(--muted); }

    /* ボタン(App InventorのButtonに相当) */
    button { appearance: none; border: 1px solid #ddd; background: #fff; color: #222; padding: .4rem .6rem; border-radius: .5rem; cursor: pointer; font-size: .95rem; }
    button:focus { outline: 2px solid #d0d8ff; outline-offset: 2px; }
    button.selected { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(63,81,181,0.12) inset; }

    /* 色ボタンは背景色で色を示す。選択状態はチェックマークで表現(App InventorのTextに"✔︎"を出すのに対応) */
    .color-btn { width: 44px; height: 32px; border-radius: .5rem; position: relative; }
    .color-btn .check { position: absolute; right: 6px; top: 4px; font-size: 16px; font-weight: 700; color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.35); display: none; }
    .color-btn.selected .check { display: block; }

    /* 太さボタンは選択中を赤文字(App InventorのTextColor=赤)で表す */
    .thick-btn.selected { color: #d32f2f; font-weight: 700; }

    /* アクションボタン */
    #eraser.selected { background: #fafafa; border-color: #bbb; }
    #clear { border-color: #ffe0e0; }

    /* キャンバス(App InventorのCanvasに相当) */
    #stage-wrap { position: relative; }
    #stage { width: 100%; height: 100%; display: block; background: #fff; touch-action: none; /* ジェスチャーでのスクロール/ズームを抑制 */ }

    /* 小さなヘルプ */
    footer { padding: .35rem .75rem; font-size: .8rem; color: var(--muted); border-top: 1px solid #eee; }
  </style>
</head>
<body>
  <main>
    <!-- App Inventor: Screen1.Title 相当 -->
    <header>
      <h1>お絵かきアプリ+(DoodlePlus / JS版)</h1>
      <span style="font-size:.85rem;color:#888">App Inventorのブロック構成をJavaScript実装に写経</span>
    </header>

    <!-- App Inventor: 水平配置1/2/3 をまとめたツールバー -->
    <div id="toolbar" role="toolbar" aria-label="Drawing toolbar">
      <!-- 色ボタン群(水平配置1) -->
      <div class="group" id="colors" aria-label="colors">
        <span class="group-label">色:</span>
        <button class="color-btn" id="btn-black"  data-color="#000000" title="黒" aria-pressed="true" style="background:#000000"><span class="check">✔︎</span></button>
        <button class="color-btn" id="btn-blue"   data-color="#0000ff" title="青" style="background:#0000ff"><span class="check">✔︎</span></button>
        <button class="color-btn" id="btn-green"  data-color="#00ff00" title="グリーン" style="background:#00ff00"><span class="check">✔︎</span></button>
        <button class="color-btn" id="btn-red"    data-color="#ff0000" title="赤" style="background:#ff0000"><span class="check">✔︎</span></button>
      </div>

      <!-- 太さボタン群(水平配置2) -->
      <div class="group" id="thickness" aria-label="thickness">
        <span class="group-label">太さ:</span>
        <button class="thick-btn" id="btn-thin"   data-width="3">細</button>
        <button class="thick-btn" id="btn-medium" data-width="5">中</button>
      </div>

      <!-- アクション群(水平配置3) -->
      <div class="group" id="actions" aria-label="actions">
        <button id="clear" title="画面消去 (Canvas.Clear)">画面消去</button>
        <button id="eraser" title="消しゴム (白で描く)">消しゴム</button>
      </div>
    </div>

    <!-- キャンバス領域(Canvas) -->
    <div id="stage-wrap">
      <canvas id="stage"></canvas>
    </div>

    <footer>
      ヒント: ドラッグして線を描画。色を押すと消しゴムは解除されます(App Inventorの仕様を踏襲)。
    </footer>
  </main>

  <script>
    // =============================================================
    //  App Inventorの状態と手続きをJavaScriptで表現
    // =============================================================
    // ▼App Inventorでのグローバル変数に相当
    //   - 選択色ボタン(Button参照)
    //   - 色ボタンリスト
    //   - ペン太さ(Button参照)
    //   - 太さボタンリスト
    //   - 太さリスト [3.0, 5.0]
    //   - 消しゴム(boolean)

    const colorButtons = [
      document.getElementById('btn-black'),
      document.getElementById('btn-blue'),
      document.getElementById('btn-green'),
      document.getElementById('btn-red')
    ];
    let selectedColorButton = colorButtons[0]; // 既定は「黒」

    const thicknessButtons = [
      document.getElementById('btn-thin'),
      document.getElementById('btn-medium')
    ];
    const thicknessList = [3, 5]; // App Inventor: 太さリスト [3.0, 5.0]
    let selectedThicknessButton = thicknessButtons[0]; // 既定は「細」

    let eraser = false; // 消しゴムモードかどうか

    // ▼Canvas関連
    const canvas = document.getElementById('stage');
    const ctx = canvas.getContext('2d');

    // 高DPIでもクッキリ描けるように、CSSサイズと内部ピクセルを分けて設定
    function resizeCanvasToDisplaySize() {
      const dpr = window.devicePixelRatio || 1;
      const rect = canvas.getBoundingClientRect();
      const width = Math.floor(rect.width * dpr);
      const height = Math.floor(rect.height * dpr);
      if (canvas.width !== width || canvas.height !== height) {
        // 既存の描画は消えるので、必要ならイメージを保持してからリサイズしてください。
        const img = ctx.getImageData(0, 0, canvas.width, canvas.height);
        canvas.width = width;
        canvas.height = height;
        ctx.putImageData(img, 0, 0); // 単純復元(拡大縮小はしない)
        ctx.scale(dpr, dpr); // 以降の描画はCSSピクセル基準
      }
      // 背景が透明になっている場合、白で塗りつぶしてApp Inventorの見た目に合せる
      // (初回のみ塗る・クリア時は別処理)
    }

    // レイアウト: ツールバーを除いた残りの領域いっぱいにキャンバスを広げる
    function layout() {
      const headerH = document.querySelector('header').offsetHeight;
      const toolbarH = document.getElementById('toolbar').offsetHeight;
      const footerH = document.querySelector('footer').offsetHeight;
      const wrap = document.getElementById('stage-wrap');
      wrap.style.height = `calc(100vh - ${headerH + toolbarH + footerH}px)`;
      resizeCanvasToDisplaySize();
    }

    window.addEventListener('resize', layout);

    // =============================================================
    //  App Inventor: 手続き(procedures)の移植
    // =============================================================
    // 手続き: 色ボタンリセット(全ボタンから✔︎を外す)
    function resetColorButtons() {
      colorButtons.forEach(btn => btn.classList.remove('selected'));
      colorButtons.forEach(btn => btn.setAttribute('aria-pressed', 'false'));
    }

    // 手続き: 色ボタン選択(ボタン)
    function selectColorButton(btn) {
      // App Inventor: 選択色ボタン.TextColor=白 相当 → ここではUI強調として✔︎表示
      btn.classList.add('selected');
      btn.setAttribute('aria-pressed', 'true');
      selectedColorButton = btn;

      // キャンバスの線色(PaintColor)を更新
      updateStrokeStyle();
    }

    // 手続き: 太さボタンリセット(全ボタンのTextColor=黒 相当)
    function resetThicknessButtons() {
      thicknessButtons.forEach(btn => btn.classList.remove('selected'));
    }

    // 手続き: 太さボタン選択(ボタン)
    function selectThicknessButton(btn) {
      btn.classList.add('selected'); // App Inventor: TextColor=赤 相当
      selectedThicknessButton = btn;
      // LineWidthを更新
      updateLineWidth();
    }

    // 手続き: 消しゴムボタン選択(トグル)
    function toggleEraser() {
      eraser = !eraser;
      document.getElementById('eraser').classList.toggle('selected', eraser);
      updateStrokeStyle(); // PaintColorを白 or 選択色へ
    }

    // 付帯: 線色を状態に合わせて更新
    function updateStrokeStyle() {
      if (eraser) {
        ctx.strokeStyle = '#ffffff'; // App Inventor: 白で上書き
      } else {
        ctx.strokeStyle = selectedColorButton.dataset.color;
      }
    }

    // 付帯: 線の太さを状態に合わせて更新
    function updateLineWidth() {
      const w = Number(selectedThicknessButton.dataset.width) || thicknessList[0];
      ctx.lineWidth = w;
      ctx.lineCap = 'round';
      ctx.lineJoin = 'round';
    }

    // 付帯: 画面消去(Canvas.Clear)
    function clearCanvas() {
      // 背景を白で塗りつぶし、App InventorのCanvas.Clearに近い見た目に
      ctx.save();
      ctx.setTransform(1,0,0,1,0,0); // 変換を一時的にリセット
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      // 白背景
      ctx.fillStyle = '#ffffff';
      const rect = canvas.getBoundingClientRect();
      ctx.fillRect(0, 0, rect.width, rect.height);
      ctx.restore();
    }

    // =============================================================
    //  App Inventor: Screen1.Initialize 相当(初期化)
    // =============================================================
    function initialize() {
      layout();
      // 既定の選択状態をApp Inventorと同じにする
      resetColorButtons();
      selectColorButton(selectedColorButton); // 黒

      resetThicknessButtons();
      selectThicknessButton(selectedThicknessButton); // 細(3px)

      // 背景を白で初期化
      clearCanvas();
    }

    // =============================================================
    //  App Inventor: Any Button.Click 相当のイベント分岐
    // =============================================================
    function onButtonClick(e) {
      const el = e.target.closest('button');
      if (!el) return;

      if (colorButtons.includes(el)) {
        // 色ボタン: 色ボタンリセット → 消しゴム=false → 色ボタン選択
        resetColorButtons();
        eraser = false;
        document.getElementById('eraser').classList.remove('selected');
        selectColorButton(el);
        return;
      }

      if (thicknessButtons.includes(el)) {
        // 太さボタン: 太さボタンリセット → 太さボタン選択
        resetThicknessButtons();
        selectThicknessButton(el);
        return;
      }

      if (el.id === 'clear') {
        clearCanvas();
        return;
      }

      if (el.id === 'eraser') {
        toggleEraser();
        return;
      }
    }

    document.getElementById('toolbar').addEventListener('click', onButtonClick);

    // =============================================================
    //  App Inventor: Canvas.Dragged(prevX, prevY, currentX, currentY)
    //  → JSではPointer Eventsで実装
    // =============================================================
    let drawing = false;
    let last = { x: 0, y: 0 };

    function toCanvasCoords(evt) {
      const rect = canvas.getBoundingClientRect();
      const x = (evt.clientX - rect.left);
      const y = (evt.clientY - rect.top);
      return { x, y };
    }

    function pointerDown(evt) {
      drawing = true;
      last = toCanvasCoords(evt);
    }

    function pointerMove(evt) {
      if (!drawing) return;
      const p = toCanvasCoords(evt);
      // Canvas.DrawLine(prevX, prevY, x, y) 相当
      ctx.beginPath();
      ctx.moveTo(last.x, last.y);
      ctx.lineTo(p.x, p.y);
      ctx.stroke();
      last = p;
    }

    function pointerUp() {
      drawing = false;
    }

    canvas.addEventListener('pointerdown', pointerDown);
    canvas.addEventListener('pointermove', pointerMove);
    window.addEventListener('pointerup', pointerUp);

    // スクロールなどで外に出た場合の保険
    canvas.addEventListener('pointerleave', pointerUp);

    // =============================================================
    // 起動
    // =============================================================
    initialize();
  </script>
</body>
</html>
できました!JS版の実装をキャンバスに置きました。

App Inventorの Screen1 / Canvas / 水平配置 / Button / 手続き を、それぞれ HTML要素 / <canvas> / ツールバー群 / <button> / JS関数 に対応づけ、ブロック名どおりの処理と詳しい日本語コメントを入れています。

そのままブラウザで動きます。色ボタンで✔︎が出て、太さは選択で赤表示、消しゴムは白で上書き、画面消去は白塗りクリアです。

他に欲しい拡張(保存・Undo/Redo・太さ追加・色数拡張・モバイル最適化など)があれば、この実装をベースにすぐに組み込みます。

動作確認

DoodlePlus.htmlに保存しGoogle Chromeで実行してみました。言葉が少し変なところもありますが、動作自体は以下のように問題無いです。

公開済みAI関連アプリチュートリアル:シルバー会員特典

ゴールド会員特典はAIが中心

初心者にはこちらも

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"と名前を付けます。

デザイン編集

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

AI,API,gemini,Google Apps Script,クラウド,センサー,バイブコーディング,中級,地図,生成AI

このチュートリアルでは、地図の中心周辺の観光名所をGoogle Gemini APIに問い合わせて地図上に表示するアプリを作ります。観光名所地図アプリではGoogle Sheets APIを使ってGoogleスプレッドシートに入力された東京の観光名所を地図上に表示したので、あらかじめ入力した観光名所しか表示できませんでしたが、このアプリでは原理的にどの場所の観光名所でも表示できます。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いています。バイブコーディングです。

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

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

Google Apps Scriptの設定

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

Gemini 2.5 Pro を使って Google Apps Script を書く

無料のアカウントでもこちらからGemini 2.5 Proが使えます。

プロンプト

観光名所を地図上に表示するには緯度、経度が必要ですが、Geminiは緯度、経度を知っているとは限らないので、代わりに知っているはずの住所を返してもらい、位置センサーコンポーネントを使って緯度、経度に変換します。

App inventorの地図コンポーネントの中心の緯度、経度から指定した半径内にある観光地の名前と住所(緯度、経度は不要)を最大5件取得し、地図上にマーカーで表示したい。Google apps script とGoogle ai studio で取得するGemini APIキーでGemini 2.5 Flashを使う。他のAPIは使わない。結果はJSONで戻す。APIキーはスクリプトプロパティから読む。App inventor部分は自分でやるから、それ以外の部分を教えて。

返答

Google Apps Scriptだけではなく、Gemini APIの設定からウェブアプリとしてのデプロイ方法、使い方まで詳しく教えてくれます。

はい、承知いたしました。App Inventorから呼び出すためのGoogle Apps Scriptを作成します。

このスクリプトは、指定された緯度・経度・半径を基に、Gemini 2.5 Flashモデルを使用して周辺の観光地情報を最大5件取得し、JSON形式で返します。

以下に、Google Apps Scriptのコードと、その設定方法を説明します。

1. Google Apps Scriptの準備
まず、Google Apps Scriptプロジェクトを準備する手順を説明します。

Google Apps Script プロジェクトの作成と設定ガイド

  1. Google Driveを開く: Google Driveにアクセスします。
  2. 新規プロジェクト作成:

    • 左上の「+ 新規」ボタンをクリックします。
    • その他」 > 「Google Apps Script」を選択します。
    • もし表示されない場合は、「アプリを追加」から「Google Apps Script」を検索して接続してください。

  3. プロジェクトに名前を付ける:

    • 左上の「無題のプロジェクト」をクリックし、「観光地検索API」などの分かりやすい名前に変更します。

  4. スクリプトの貼り付け:

    • エディタに最初から書かれている function myFunction() { ... } をすべて削除します。
    • 後述するコードをすべてコピーして、エディタに貼り付けます。

  5. APIキーの設定:

    • 左側のメニューから歯車アイコンの「プロジェクトの設定」をクリックします。
    • スクリプト プロパティ」のセクションにある「スクリプト プロパティを追加」ボタンをクリックします。
    • プロパティGEMINI_API_KEY と入力します。
    • に、Google AI Studioで取得したご自身のAPIキーを貼り付けます。
    • スクリプト プロパティを保存」をクリックします。

  6. プロジェクトのデプロイ:

    • 右上の青い「デプロイ」ボタンをクリックし、「新しいデプロイ」を選択します。
    • 種類の選択(歯車アイコンの隣)で「ウェブアプリ」を選択します。
    • 次の設定を行います。

      • 説明: 「観光地検索API for App Inventor」など、分かりやすい説明を入力します。
      • 次のユーザーとして実行: 自分
      • アクセスできるユーザー: 全員

    • デプロイ」ボタンをクリックします。
    • 初回は「アクセスを承認」を求められます。ご自身のアカウントを選択し、詳細設定から安全でないページへの移動を許可して、スクリプトの実行を許可してください。
    • デプロイが完了すると、「ウェブアプリのURL」が表示されます。このURLをコピーしてください。App InventorからAPIを呼び出す際にこのURLが必要になります。

以上で、Google Apps Script側の準備は完了です。

2. Google Apps Script コード
こちらのコードを、先ほど作成したGoogle Apps Scriptプロジェクトのエディタに貼り付けてください。

観光地検索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形式で、名前、住所、緯度、経度を含めてください。

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

  // Gemini APIに送信するデータ(ペイロード)
  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": "観光地の住所" },
            "latitude": { "type": "NUMBER", "description": "観光地の緯度" },
            "longitude": { "type": "NUMBER", "description": "観光地の経度" }
          },
          "required": ["name", "address", "latitude", "longitude"]
        }
      }
    }
  };

  // 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);
}

3. App Inventorでの使い方
App Inventor側では、「Web」コンポーネントを使って、デプロイ時に取得したウェブアプリのURLにGETリクエストを送信します。

URLの末尾に、以下のようにパラメータを追加してください。

<あなたのウェブアプリのURL>?latitude=35.681236&longitude=139.767125&radius=1000
latitude: 地図の中心の緯度

longitude: 地図の中心の経度

radius: 検索したい半径(メートル単位)

Webコンポーネントが GotText イベントで結果を受け取ったら、返ってきたJSON文字列を解析して、リストとして処理します。その後、各観光地の名前、住所、緯度、経度を使って地図上にマーカーを配置してください。

これで、App InventorアプリからGoogle Apps ScriptとGemini APIを連携させて、周辺の観光地情報を地図に表示する機能が実装できます。もし何かご不明な点があれば、お気軽にご質問ください。

プロンプト

結果に緯度、経度が含まれているので、緯度、経度を除いてコードを書き直してもらいます。

Geminiの結果から観光地の緯度、観光地の経度を除いて観光地検索APIコードを書き直して。

返答

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

AI,API,gemini,Google Apps Script,クラウド,バイブコーディング,中級,生成AI

このチュートリアルでは、手書きのイラストをGoogle Gemini APIで加工・改良し、美しく仕上げた画像を表示するアプリを作ります。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いています。バイブコーディングです。

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

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

Google Apps Scriptの設定

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

Gemini 2.5 Pro を使って Google Apps Script を書く

無料のアカウントでもこちらからGemini 2.5 Proが使えます。

プロンプト

App inventorのキャンバスに描いた絵を解析して改良した絵を出力させたい。改良した絵はbase64エンコードした文字列で受け取ってオリジナルの下のキャンバスに表示したい。
Google Apps Script からGoogle ai studio のAPIを呼び出して使う。キャンバスに描いた絵はエクステンションでbase64エンコードしてJSONでアップロードするのでGASでbase64が正しいか確認し、必要であれば修正する。結果はJSONで戻す。APIキーはスクリプトプロパティから読む。
App inventor部分は自分でやるから、それ以外の部分を教えて。

返答

Google Apps Scriptだけではなく、Gemini APIの設定からウェブアプリとしてのデプロイ方法、使い方まで詳しく教えてくれます。

App InventorとGoogle AIのGeminiモデルを連携させるためのGoogle Apps Script(GAS)ですね。承知いたしました。

App Inventorから送られてくる手描きの絵(Base64文字列)をGemini APIで解析・改良し、新しい画像を生成してApp Inventorに返すためのGASコードを作成します。

このスクリプトは、以下の処理を行います。

POSTリクエストの受付: App Inventorから送信されたJSONデータ(画像データとプロンプトを含む)を受け取ります。

Base64データの整形: 受け取ったBase64文字列から不要なヘッダー(data:image/png;base64,など)を削除します。

Gemini APIの呼び出し:

スクリプトプロパティに保存されたAPIキーを安全に読み込みます。

入力された画像とプロンプトをgemini-2.0-flash-preview-image-generationモデルに送信し、画像の改良を依頼します。このモデルは、入力画像を解釈し、指示に基づいて新しい画像を生成できます。

結果の返却: 生成された新しい画像のBase64文字列をJSON形式でApp Inventorに返します。

以下のドキュメントに、GASのコードと設定方法の詳細を記載します。

画像改良GAS for App Inventor

/**
 * 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 prompt = jsonData.prompt || 'この手描きの絵を、より洗練された高品質なデジタルアートにしてください。';

    // 入力データの検証
    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.',
      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.0-flash-preview-image-generation';
  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}`);
  }
}

設定と使用方法
Google Apps Scriptプロジェクトの作成:

Googleドライブで「新規」>「その他」>「Google Apps Script」を選択して、新しいプロジェクトを作成します。

上記のコードをコピーして、コード.gsファイルに貼り付けます。

APIキーの設定:

GASエディタの左側のメニューから「プロジェクトの設定」(歯車アイコン)をクリックします。

「スクリプト プロパティ」のセクションで、「スクリプト プロパティを追加」をクリックします。

プロパティに GEMINI_API_KEY と入力します。

値に、ご自身のGoogle AI StudioのAPIキーを貼り付けます。

「スクリプト プロパティを保存」をクリックします。

デプロイ:

エディタ右上の「デプロイ」ボタンをクリックし、「新しいデプロイ」を選択します。

種類の選択で「ウェブアプリ」を選択します。

「アクセスできるユーザー」を**「全員」**に設定します。(App Inventorから呼び出すために必要です)

「デプロイ」ボタンをクリックします。初回は承認を求められるので、許可してください。

表示されるウェブアプリのURLをコピーします。このURLをApp Inventorから呼び出すことになります。

App Inventor側の実装(ヒント)
App Inventor側では、以下の手順でこのGASと連携します。

Webコンポーネントを配置します。

描画完了のボタンなどが押されたら、CanvasのSaveAsブロックを使って描画内容をファイルに保存し、そのファイルパスを取得します。

エクステンション(例: TaifunFileエクステンションなど)を使って、保存したファイルをBase64形式にエンコードします。

WebコンポーネントのPostTextブロックを使います。

url: 上記でコピーしたGASのウェブアプリURLを設定します。

text: joinブロックやBuildJsonブロック(Dictionaryを使うと便利)を使い、以下のようなJSON文字列を作成して渡します。

JSON

{"imageData": "(ここにBase64文字列)", "prompt": "プロのイラストレーター風にしてください"}
WebコンポーネントのGotTextイベントで、GASからの応答を受け取ります。

responseContentをJSONとしてパース(JsonTextDecodeブロック)します。

statusがsuccessであることを確認します。

imageDataキーの値(改良された画像のBase64文字列)を取得します。

取得したBase64文字列を、結果表示用の下のキャンバスのBackgroundImageプロパティなどに設定します。(data:image/png;base64,を先頭に付ける必要があるかもしれません)

これで、App Inventorで描いた絵をAIで改良して表示する仕組みが完成します。ご不明な点があれば、お気軽にご質問ください。

プロンプト

Gemini APIのAPIキーの取り方を生成AIお絵描きコーチアプリチュートリアルから転記します。

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

ChatGPT,claude,gemini,アプリ開発,ノート,生成AI

考えたことメモ > App InventorからPythonへどうやって移行するのか?

Google Gemini 2.5 Pro Deep Researchによれば、「一度App Inventorで作ったアプリをPythonで再現してみるというアプローチは、目的が明確なため非常に効果的な学習方法です」とのことでした。これは、AI時代における実践的なプログラミング学習の一例といえます。

Pythonは文法が比較的シンプルで、初心者にも扱いやすい言語です。AI開発、データ分析、Webサイト制作、作業の自動化など、幅広い分野に応用できる点が特徴です。

今回は、出発点として「オンデマンドアカデミー」で紹介されている カラフルお絵かきアプリを題材に選びました。このアプリは、基本的なUIの構成やイベント処理の理解に適しており、Pythonでの再構築を目標に据えるにはちょうどよい教材です。

どこまでAIが支援できるかを検討するため、Claude Sonnet 4, Gemini 2.5 Pro, ChatGPT-4oを比較対象に選びました。これらのAIを活用しながら、App InventorアプリをPythonでどう再現していけるのか、段階的に分析していきます。

aiaファイルの読み込みと解析

要件定義書作成とPythonプログラミング

動作確認

サマリー

ChatGPT-4oはApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてそのまま動作するPythonコードを書けました。

aiaファイルの読み込みと解析

aiaファイルをアップロードして正しく認識できるかをClaude Sonnet 4, Gemini 2.5 Pro, ChatGPT-4oで試してみました。ChatGPT-4oだけが、正しくaiaファイルを解析できました。

Claude Sonnet 4

Gemini 2.5 Pro

ChatGPT-4o

Claude Sonnet 4

aiaファイルのアップロード不可。


Gemini 2.5 Pro

プロンプト

あなたはApp Inventor 2の内部構造に精通したプロのソフトウェアエンジニアです。このaiaファイルを完全に解析してください。

返答

はい、承知いたしました。提供された `DoodlePlus.aia` ファイルを専門的な観点から解析します。

このファイルは、MIT App Inventor 2で作成されたプロジェクトのアーカイブファイルです。`.aia` ファイルは実質的にZIP形式のアーカイブであり、プロジェクトを構成するすべてのファイル(画面デザイン、プログラミングロジック、アセットなど)を含んでいます。

提供された情報から、このプロジェクトの構造と内容を以下のように解析します。

### プロジェクトの概要

* [cite_start]**プロジェクト名**: `DoodlePlus` [cite: 1]
* [cite_start]**作成者アカウント**: `ai_XXXXXXX` [cite: 1]
* [cite_start]**プラットフォーム**: MIT App Inventor 2 [cite: 1]
* **内容の推測**: プロジェクト名に「Doodle」(いたずら書き)とあることから、これは指で画面に絵を描くことができる**お絵かきアプリケーション**であると強く推測されます。「Plus」という接尾辞は、基本的なお絵かき機能に加えて、何らかの追加機能(色の変更、線の太さの調整、保存機能など)が実装されている可能性を示唆しています。

---

### ファイル構造の解析

AIAファイルは、主に以下の3種類のコアファイルで構成されています。このプロジェクトには `Screen1` という名前の画面が少なくとも1つ存在します。

#### 1. `youngandroidproject/project.properties`
[cite_start]このファイルは、プロジェクト全体の設定を定義するプロパティファイルです。 [cite: 1] テキスト形式で、以下のようなキーと値のペアが含まれています。

* `name`: プロジェクト名(この場合は `DoodlePlus`)。
* `versioncode` & `versionname`: アプリのバージョン情報。
* `theme`: アプリケーションの全体的なデザインテーマ(例: "Classic" または "DeviceDefault")。
* `user`: プロジェクトの作成者アカウント(この場合は `ai_tam0130`)。

#### 2. `src/appinventor/ai_tam0130/DoodlePlus/Screen1.scm`
[cite_start]これは、`Screen1` の**UIデザイン**を定義するファイルです。 [cite: 1] SCMは "Scheme" の略ですが、実際にはJSONに似た形式で画面上のコンポーネントの階層と各プロパティを記述しています。

* **役割**: どのコンポーネント(ボタン、ラベル、キャンバスなど)が画面に配置されているか、またそのプロパティ(サイズ、色、テキストなど)がどう設定されているかを定義します。
* **推測されるコンポーネント**: お絵かきアプリであるため、このファイルにはおそらく以下のようなコンポーネントの定義が含まれています。
    * `Canvas`: ユーザーが絵を描くための主要な描画領域。
    * `Button`: 「色変更」「消去」「保存」などの機能を実行するためのボタン。
    * `HorizontalArrangement` / `VerticalArrangement`: ボタンなどのUI要素を整理・配置するためのレイアウトコンポーネント。
    * `Slider`: 線の太さなどを直感的に変更するために使われる可能性があります。

#### 3. `src/appinventor/ai_tam0130/DoodlePlus/Screen1.bky`
[cite_start]これは、`Screen1` の**プログラミングロジック**を定義する最も重要なファイルです。 [cite: 1] GoogleのBlocklyライブラリをベースにしており、ブロックで組まれたプログラムがXML形式で保存されています。

* **役割**: アプリケーションの動作(「いつ」「何が」「どうなるか」)を定義します。例えば、「ボタンがクリックされたら、キャンバスの色を赤に変える」といったロジックが記述されます。
* **推測されるロジック**: このXMLファイルには、以下のようなイベントハンドラやプロシージャが含まれていると考えられます。
    * `when Canvas.Dragged do ...`: キャンバス上で指がドラッグされたときのイベント。この中で `Canvas.DrawLine` ブロックを使い、指の動きに合わせて線を描画する処理が記述されているはずです。
    * `when ColorButton.Click do ...`: 特定の色のボタンがクリックされたときに、`Canvas.PaintColor` プロパティを対応する色に変更する処理。
    * `when ClearButton.Click do ...`: 消去ボタンがクリックされたときに、`Canvas.Clear` メソッドを呼び出してキャンバスを白紙に戻す処理。

---

### 結論

提供された `DoodlePlus.aia` は、`ai_XXXXXX` というユーザーによって作成された、`Screen1` という単一の画面を持つ**インタラクティブなお絵かきアプリケーション**のプロジェクトファイルです。

このプロジェクトは、App Inventor 2の標準的なアーキテクチャに従っており、UIデザイン (`.scm`) とイベント駆動型のロジック (`.bky`) が明確に分離されています。提供されたデータはファイル内容の断片であるため、具体的なコンポーネントのプロパティ値やブロックの組み合わせを完全に復元することはできません。しかし、そのファイル構造と命名規則から、アプリケーションの全体像と主要な機能を専門的に特定することが可能です。

プロンプト

中身を全部詳細に解析して

返答

はい、承知いたしました。
前回の解析に基づき、各ファイルに**通常どのようなコードが記述されているか**を、具体的なサンプルを交えてさらに詳細に解析します。

提供されたファイルの中身はデータが破損しているため直接読み解くことはできませんが、App Inventorのエンジニアとして、その構造から**中身を具体的に復元・推測**することは可能です。

### 🎨 DoodlePlus アプリの全体像

このアプリは、指で絵が描けるキャンバスと、色や線の太さを変更したり、絵を消したりするためのボタン群で構成されていると推測されます。

-----

### \#\# 1. UIデザイン (`Screen1.scm` の詳細解析)

このファイルは、画面の見た目を定義します。JSON形式で、どのコンポーネントが、どのようなプロパティ(設定値)で配置されているかが記述されています。

`DoodlePlus` の場合、`Screen1.scm` の中身は以下のようになっていると考えられます。

```json
{
  "$Name": "Screen1",
  "$Type": "Form",
  "$Version": "...",
  "Title": "DoodlePlus",
  "components": [
    {
      "$Name": "DrawingCanvas",
      "$Type": "Canvas",
      "Width": -2, // -2は「親に合わせる(Fill Parent)」
      "Height": -2,
      "PaintColor": "#FF000000" // 初期色は黒
    },
    {
      "$Name": "ControlPanel",
      "$Type": "HorizontalArrangement", // ボタンを水平に並べるレイアウト
      "Width": -2,
      "components": [
        {
          "$Name": "RedButton",
          "$Type": "Button",
          "Text": "Red",
          "BackgroundColor": "#FFFF0000"
        },
        {
          "$Name": "BlueButton",
          "$Type": "Button",
          "Text": "Blue",
          "BackgroundColor": "#FF0000FF"
        },
        {
          "$Name": "ClearButton",
          "$Type": "Button",
          "Text": "Clear"
        }
      ]
    },
    {
      "$Name": "LineWidthSlider", // 「Plus」機能の可能性
      "$Type": "Slider",
      "Width": -2,
      "MaxValue": "30.0",
      "MinValue": "2.0",
      "ThumbPosition": "5.0" // 初期値の線の太さ
    }
  ]
}
```

**【ポイント】**

  * **`DrawingCanvas`**: 絵を描くための主要な領域です。幅と高さを「親に合わせる」に設定し、画面いっぱいに広げている可能性が高いです。
  * **`ControlPanel`**: 色変更ボタンや消去ボタンを横一列に並べるための入れ物です。
  * **`RedButton`, `BlueButton` など**: クリックすると線の色が変わるボタンです。
  * **`LineWidthSlider`**: 「Plus」機能の核心部分で、スライダーを動かすことで線の太さを直感的に変更できるようにしていると推測されます。

-----

### \#\# 2. プログラムロジック (`Screen1.bky` の詳細解析)

このファイルは、アプリの実際の動作を定義するブロックプログラム(XML形式)です。上記のUIコンポーネントが、ユーザーの操作によってどのように動くかが記述されています。

#### **中核機能:線を描くロジック**

これが最も基本的な機能です。キャンバス上で指をドラッグした時に、その軌跡に線を引きます。

  * **イベント**: `when DrawingCanvas.Dragged` (DrawingCanvasがドラッグされた時)
  * **アクション**: `call DrawingCanvas.DrawLine` (線を引く)
      * `x1`, `y1`: 前の座標 (prevX, prevY)
      * `x2`, `y2`: 現在の座標 (currentX, currentY)

#### **色を変更するロジック**

各色のボタンが押された時に、キャンバスの `PaintColor` (描画色) プロパティを変更します。

  * **イベント**: `when RedButton.Click` (RedButtonがクリックされた時)

  * **アクション**: `set DrawingCanvas.PaintColor to` (描画色をセットする)

      * **値**: 赤色のカラーブロック

  * **イベント**: `when BlueButton.Click` (BlueButtonがクリックされた時)

  * **アクション**: `set DrawingCanvas.PaintColor to`

      * **値**: 青色のカラーブロック

#### **全消去するロジック**

消去ボタンが押された時に、キャンバスの内容をすべてクリアします。

  * **イベント**: `when ClearButton.Click` (ClearButtonがクリックされた時)
  * **アクション**: `call DrawingCanvas.Clear` (キャンバスをクリアする)

#### **「Plus」機能:線の太さを変更するロジック**

スライダーの位置が変わった時に、キャンバスの `LineWidth` (線の太さ) プロパティを変更します。

  * **イベント**: `when LineWidthSlider.PositionChanged` (LineWidthSliderの位置が変わった時)
  * **アクション**: `set DrawingCanvas.LineWidth to` (線の太さをセットする)
      * **値**: `get thumbPosition` (スライダーの現在の値)

### **結論**

この `DoodlePlus.aia` は、単に線が描けるだけでなく、**「複数の色への変更機能」「全消去機能」「スライダーによる線の太さの動的な変更機能」** を備えた、多機能お絵かきアプリであると結論付けられます。UI定義ファイル (`.scm`) とロジック定義ファイル (`.bky`) が連携し、これらの機能を実現しています。

ハルシネーションがひどい。できないならできないとはっきり言って欲しい。

  • 提供されたファイルの中身はデータが破損しているため直接読み解くことはできませんが、App Inventorのエンジニアとして、その構造から中身を具体的に復元・推測することは可能です。
  • Screen1.scm の中身は以下のようになっていると考えられます。

ChatGPT-4o

プロンプト

あなたはApp Inventor 2の内部構造に精通したプロのソフトウェアエンジニアです。このaiaファイルを完全に解析してください。

返答

`.aia`ファイルはApp InventorプロジェクトのZIP形式のアーカイブです。`DoodlePlus.aia`を展開したところ、次の主要ファイルが含まれていました:

* `src/appinventor/ai_tam0130/DoodlePlus/Screen1.bky`: ブロックエディタの情報(Blockly形式のデータ)
* `src/appinventor/ai_tam0130/DoodlePlus/Screen1.scm`: 画面のUI構造とプロパティ設定(YAILコード)
* `youngandroidproject/project.properties`: プロジェクト全体の設定情報

次に、各ファイルの内容を解析して、どのようなアプリなのか、どんな機能があるのかを詳細に説明します。まずは `.scm` ファイルから見て、ユーザーインターフェースの構成と設定を確認します。


このアプリ「DoodlePlus」のUI構成(`Screen1.scm`)から、以下のような構成であることがわかります:

### アプリ情報

* **アプリ名**:DoodlePlus(表示タイトルは「お絵かきアプリ+」)
* **スクリーン名**:Screen1
* **バージョン**:31

---

### 主なUIコンポーネント

#### 1. `Canvas1`(キャンバス)

* 描画用のキャンバス。ユーザーが絵を描くエリア。

#### 2. `HorizontalArrangement1`(水平配置1)色選択ボタン群

* 黒(背景色:`#000000`)
* 青(`#0000FF`)
* 緑(`#00FF00`)
* 赤(`#FF0000`)

#### 3. `HorizontalArrangement2`(水平配置2)線の太さ選択

* 「細」(テキスト:細)
* 「中」(テキスト:中)

#### 4. `HorizontalArrangement3`(水平配置3)描画操作

* 「画面消去」:キャンバスをクリアする
* 「消しゴム」:消去モードへの切り替えと推測

---

次に、ブロックエディタ側の `.bky` ファイル(`Screen1.bky`)を解析して、このUIにどのようなロジックや動作が関連付けられているのかを調べます。


`.bky`ファイルの冒頭を解析した結果、このアプリは明確なロジック構造を持ち、描画操作を柔軟に制御できるようになっています。

---

### **ブロックエディタの主要ロジック(抜粋)**

#### 🔷 グローバル変数の定義

* `選択マーク`: `"✔︎"` — ボタン選択時に表示するチェックマーク。
* `選択色ボタン`: 初期状態は「黒」ボタン。
* `色ボタンリスト`: 「黒」「青」「グリーン」「赤」の4色ボタン。
* `ペン太さ`: 初期状態は「細」ボタン。
* `太さボタンリスト`: 「細」「中」の太さ選択ボタン。

---

### 🎨 推定される機能と動作

1. **色の選択**

   * 色ボタン(黒・青・緑・赤)を押すと、選択された色で線が描かれる。
   * 押されたボタンに「✔︎」が表示され、他のボタンのマークは消える。

2. **線の太さ選択**

   * 「細」「中」ボタンを押して描画する線の太さを切り替え。
   * 同様に選択されたボタンに「✔︎」を付加。

3. **消去機能**

   * 「画面消去」ボタン:Canvas全体をクリア。
   * 「消しゴム」ボタン:描画色を背景色と同じにして消すように描画。

---

### ✅ 結論:このアプリの概要

* **アプリ名**:「DoodlePlus(お絵かきアプリ+)」
* **主な機能**:

  * 指やタッチで絵を描く
  * 色・太さの選択
  * キャンバスの消去
  * 消しゴムモードの実装
* **技術的特徴**:

  * App InventorのCanvasコンポーネントを中心に、色ボタンと状態管理変数で操作性を実現
  * ユーザーフレンドリーなインターフェース設計(✔︎マーク付きの選択表現)

---

さらに詳しく(例:ブロック全体図や各イベント処理の詳細)をご希望の場合は、そのようにお知らせください。ご要望に応じて完全なフローチャートや動作の可視化も可能です。

要件定義書作成とPythonプログラミング

これ以降はChatGPT-4oだけで実施します。

プロンプト

このアプリの要件定義書をMarkdownで書いて

返答

以下は、`DoodlePlus` アプリの\*\*要件定義書(Markdown形式)\*\*です。App Inventorで開発されたお絵かきアプリの機能と仕様を整理しています。

---

# 📋 要件定義書:DoodlePlus(お絵かきアプリ+)

## 1. 概要

**DoodlePlus** は、タッチデバイス上で簡単に絵を描いたり消したりできるシンプルなお絵かきアプリです。ユーザーは色や線の太さを選択してキャンバス上に自由に描画でき、消しゴムや画面消去などの基本機能も備えています。

---

## 2. 目的

* 指やスタイラスで自由に絵を描ける環境を提供する。
* 子どもや初心者にも使いやすい直感的なUIを実現する。
* 色・太さ・消去といった基本的な描画操作をスムーズに行えるようにする。

---

## 3. ユースケース

| ユーザーアクション     | システムの反応                    |
| ------------- | -------------------------- |
| 色ボタンをタップ      | 線の描画色を変更、「✔︎」で選択中を表示       |
| 太さボタンをタップ     | 線の太さを変更、「✔︎」で選択中を表示        |
| キャンバス上をドラッグ   | 指の動きに応じて線を描画               |
| 「消しゴム」ボタンをタップ | ペンの色が背景色に変更され、描いた線を消すように動作 |
| 「画面消去」ボタンをタップ | キャンバス上のすべての描画をクリア          |

---

## 4. 画面仕様(UI構成)

### Screen1

| コンポーネント名   | 種類        | 説明                    |
| ---------- | --------- | --------------------- |
| Canvas1    | キャンバス     | 描画エリア。指やタッチで線を描ける。    |
| 黒、青、グリーン、赤 | ボタン(色選択)  | 線の色を変更。選択中は✔︎マークを表示。  |
| 細、中        | ボタン(太さ選択) | 線の太さを変更。選択中は✔︎マークを表示。 |
| 画面消去       | ボタン       | Canvasを全消去            |
| 消しゴム       | ボタン       | 線を背景色で描いて消す機能         |

---

## 5. 機能仕様

### 色の変更

* 対応色:黒、青、緑、赤
* 色ボタン押下時に線の描画色を即時変更
* 現在選択中のボタンには✔︎マークを追加

### 線の太さ変更

* 太さ:細、中(今後拡張可能)
* 現在選択中の太さボタンにも✔︎を追加

### 消しゴム

* 「消しゴム」押下時、ペン色をCanvasの背景色(白)に変更
* 色ボタンと✔︎の状態は保持

### 画面消去

* 「画面消去」ボタンを押すとCanvasの全描画を初期化

---

## 6. データ定義

### グローバル変数

| 変数名      | 用途               |
| -------- | ---------------- |
| 選択マーク    | ボタンに表示する選択記号(✔︎) |
| 選択色ボタン   | 現在選択中の色ボタンを保持    |
| 色ボタンリスト  | 色変更可能なボタン群(4色)   |
| ペン太さ     | 現在選択中の太さボタン      |
| 太さボタンリスト | 太さ変更可能なボタン群(細・中) |

---

## 7. 非機能要件

* **対応プラットフォーム**:Android(App Inventorによりビルド)
* **使用言語**:日本語(UIラベルおよびブロック)
* **パフォーマンス**:スムーズな描画処理(タッチに遅延なく追従)
* **操作性**:視認性の高いボタン、明確なフィードバック(✔︎)

---

## 8. 今後の拡張案

* 線の太さを3段階以上に
* カラーピッカー機能の追加
* 描画の保存・読み込み機能
* 子ども向けUIモードの強化(アイコン表示など)

---

必要に応じて、**画面遷移図**や**ブロック構成の図解**も作成できます。ご希望があればお知らせください。

プロンプト

この要件定義書に基づいてPythonのコードを書いて。初心者にも何をしているかわかるように、また、App Inventorアプリとの対応関係がわかるように、詳しいコメントをつけて。

返答

以下に、要件定義書に基づいた DoodlePlus アプリのPythonコード(tkinter を使用)を提示します。App Inventor のUI構成やブロックに相当する機能を意識して、初心者向けに丁寧なコメントを添えています。
✅ Python版「DoodlePlus」:App Inventor対応付き
📌 対応ライブラリ:tkinter(Python標準GUIライブラリ)
import tkinter as tk
from tkinter import ttk

# -------------------------
# アプリ設定と初期値の定義
# -------------------------

# キャンバス背景色(消しゴム用にも使用)
BACKGROUND_COLOR = "white"

# 初期ペン設定(黒・細)
current_color = "black"
current_width = 2  # 細い線
eraser_mode = False  # 消しゴムモード:初期はOFF

# 色選択用のボタン一覧(App Inventorの「色ボタンリスト」に相当)
color_buttons = {}

# 太さ選択用のボタン一覧(App Inventorの「太さボタンリスト」に相当)
width_buttons = {}

# 現在選択中のボタン(✔︎マーク用)を保持
selected_color_btn = None
selected_width_btn = None

# ✔︎マーク(選択中表示)
CHECK_MARK = "✔︎"

# -------------------------
# 線を描くときの関数定義
# -------------------------

def start_draw(event):
    """描画のスタート位置を記録"""
    canvas.old_x, canvas.old_y = event.x, event.y

def draw(event):
    """マウスドラッグに合わせて線を描画"""
    if not hasattr(canvas, "old_x") or not hasattr(canvas, "old_y"):
        return

    # 線の色と太さを使用して描画
    canvas.create_line(canvas.old_x, canvas.old_y, event.x, event.y,
                       width=current_width,
                       fill=current_color,
                       capstyle=tk.ROUND,
                       smooth=True)

    # 次回の起点を更新
    canvas.old_x, canvas.old_y = event.x, event.y

def reset_draw(event):
    """描画の終了:座標初期化"""
    canvas.old_x, canvas.old_y = None, None

# -------------------------
# 色・太さの変更機能
# -------------------------

def change_color(new_color, btn):
    """ペンの色を変更し、選択マークを更新"""
    global current_color, eraser_mode, selected_color_btn

    current_color = new_color
    eraser_mode = False

    # ✔︎の更新処理
    if selected_color_btn:
        selected_color_btn.config(text=selected_color_btn["text"].replace(CHECK_MARK, ""))
    btn.config(text=btn["text"] + CHECK_MARK)
    selected_color_btn = btn

def change_width(new_width, btn):
    """線の太さを変更し、選択マークを更新"""
    global current_width, selected_width_btn

    current_width = new_width

    # ✔︎の更新処理
    if selected_width_btn:
        selected_width_btn.config(text=selected_width_btn["text"].replace(CHECK_MARK, ""))
    btn.config(text=btn["text"] + CHECK_MARK)
    selected_width_btn = btn

# -------------------------
# 消しゴム・画面消去
# -------------------------

def use_eraser():
    """消しゴムモードに切り替え"""
    global current_color, eraser_mode
    current_color = BACKGROUND_COLOR
    eraser_mode = True

def clear_canvas():
    """Canvasを全消去"""
    canvas.delete("all")

# -------------------------
# UI作成(App Inventor対応UI)
# -------------------------

root = tk.Tk()
root.title("DoodlePlus(Python版)")

# Canvas:描画エリア(App InventorのCanvas1)
canvas = tk.Canvas(root, width=600, height=400, bg=BACKGROUND_COLOR)
canvas.grid(row=0, column=0, columnspan=6, pady=10)

# マウス操作のバインド
canvas.bind("<Button-1>", start_draw)
canvas.bind("<B1-Motion>", draw)
canvas.bind("<ButtonRelease-1>", reset_draw)

# -------------------------
# 色選択ボタン(App Inventor:黒・青・緑・赤)
# -------------------------

colors = [("黒", "black"), ("青", "blue"), ("グリーン", "green"), ("赤", "red")]
for i, (label, color) in enumerate(colors):
    btn = tk.Button(root, text=label, width=8, command=lambda c=color, b=label: change_color(c, color_buttons[b]))
    btn.grid(row=1, column=i)
    color_buttons[label] = btn

# 初期選択:黒に✔︎
color_buttons["黒"].config(text="黒" + CHECK_MARK)
selected_color_btn = color_buttons["黒"]

# -------------------------
# 太さ選択ボタン(App Inventor:細・中)
# -------------------------

widths = [("細", 2), ("中", 5)]
for i, (label, width) in enumerate(widths):
    btn = tk.Button(root, text=label, width=8, command=lambda w=width, b=label: change_width(w, width_buttons[b]))
    btn.grid(row=2, column=i)
    width_buttons[label] = btn

# 初期選択:細に✔︎
width_buttons["細"].config(text="細" + CHECK_MARK)
selected_width_btn = width_buttons["細"]

# -------------------------
# 消しゴム/画面消去ボタン
# -------------------------

eraser_btn = tk.Button(root, text="消しゴム", width=10, command=use_eraser)
eraser_btn.grid(row=2, column=2, padx=5)

clear_btn = tk.Button(root, text="画面消去", width=10, command=clear_canvas)
clear_btn.grid(row=2, column=3, padx=5)

# アプリ起動
root.mainloop()
📱 App InventorとPythonコードの対応表
App Inventorの要素	Pythonコードでの対応
Canvas1(描画)	tk.Canvas
色ボタン(黒〜赤)	change_color() 関数+ボタン配置
太さボタン(細・中)	change_width() 関数+ボタン配置
✔︎マークの表示	selected_color_btn / selected_width_btn の管理
消しゴム	use_eraser()(色を白に)
画面消去	clear_canvas()

💡補足
本コードはマウス操作向け(タッチでも動作するタブレットPC可)
App Inventorで言えば、すべてのボタンやキャンバスの「ブロック(処理)」に相当するものをPython関数で表現しています。

ご希望があれば:
画像の保存機能
カラーピッカー追加
タッチ対応強化(Kivyなど)
も実装できます!必要であればお知らせください。

動作確認

Windows 11でAnaconda のJupyter Notebookで実行してみました。一切変更なしで、以下のように問題なく動きました。

公開済みAI関連アプリチュートリアル:シルバー会員特典

ゴールド会員特典はAIが中心

初心者にはこちらも

gemini,ノート資料

このレポートは Google Gemini 2.5 Pro Deep Researchに以下プロンプトを与えて作ったものです。

AIを使ったプログラミングが企業で急速に普及してきているにも関わらず、大学などの教育機関ではAIを使ったプログラミング教育は進んでいないらしい。このままでは卒業生が就職できたとしても仕事ができなくなるのではないか?こういった現状で学生はどうしたらいいのか?
App Inventorを勉強中の学生へのアドバイスはある?

1. App Inventorを「思考の訓練場」として最大限に活用する

App Inventorの最大の利点は、複雑な文法に煩わされることなく、プログラミングの本質である「問題解決」と「論理的思考」を鍛えられる点にあります 。Google Blocklyのようなビジュアルプログラミングツールは、教育支援の現場でも活用されています 。  

  • 目的意識を持つ: 重要なのは、App Inventorというツールを使いこなすこと自体が目的ではなく、それを通じて、大きな問題を小さなタスクに分解する能力や、物事を順序立てて考える論理的思考力を養うことです 。  
  • 普遍的なスキルを磨く: これらのスキルは、将来どのプログラミング言語を使うことになっても通用する、普遍的な能力となります 。ブロックを組み合わせながら、「どうすればこの機能は実現できるか」「もっと効率的な処理はないか」と試行錯誤するプロセスそのものが、あなたの財産になります。  

2. 「作りたいもの」を形にし、ポートフォリオを構築する

採用担当者は、言語を知っていること以上に、その技術を使って実際に問題を解決した経験を重視します。App Inventorはアイデアを迅速に形にできるため、ポートフォリオ構築に最適です。

  • オリジナルアプリを開発する: チュートリアルを終えたら、ぜひ自分だけのオリジナルアプリ開発に挑戦してください 。身の回りの小さな不便を解決するツールでも、自分の趣味に関するアプリでも構いません。重要なのは、企画から開発、可能であれば一般公開までの一連のプロセスを経験することです 。  
  • 経験をアピールする: このような実践的なプロジェクト経験は、あなたのポートフォリオを豊かにし、「プロダクトを創れる」人材であることを証明する強力な武器になります 。  

3. AI時代への架け橋として利用する

App Inventorは、AIを学ぶ上での入り口としても非常に有効です。AI開発の最前線ではPythonなどが主流ですが 、多くのAIサービスはAPI(外部サービスと連携するための窓口)を通じて機能を提供しています。  

  • AIと連携させる: App Inventorを使って、例えば画像認識AIや翻訳AIのAPIに接続するアプリを作ってみましょう。これにより、ユーザーがどのようにAIと対話し、AIがどのようにプロダクトに組み込まれるのかを実践的に理解できます 。  
  • 目的意識を明確にする: AIを使って「何を作りたいか」という目的意識を持つことが、学習を継続する上で最も重要です 。App Inventorでのプロトタイピングは、その目的を見つけるための素晴らしい手段です。  

4. 次のステップへ:テキストベース言語への移行を見据える

App Inventorでプログラミングの楽しさと論理構築に自信がついたら、次のステップとしてPythonのようなテキストベースの言語に挑戦することをお勧めします 。  

  • スキルの応用: App Inventorで培った問題解決能力があれば、新しい言語の文法を覚えることは、それほど難しいことではありません 。  
  • 効果的な学習法: 一度App Inventorで作ったアプリをPythonで再現してみる、というアプローチは、目的が明確なため非常に効果的な学習方法です。ロジックはすでに頭の中にあるため、純粋に「文法の学習」に集中できます。

App Inventorは、コーディングの技術的なハードルを下げ、開発の楽しさや問題解決の本質に集中させてくれる素晴らしい学習ツールです。そこで得た論理的思考力と「アイデアを形にする経験」を土台に、より高度な技術へとステップアップしていくことで、AI時代に活躍できるエンジニアへと成長できるでしょう。

gemini,ノート資料

このレポートは Google Gemini 2.5 Pro Deep Researchに以下プロンプトを与えて作ったものです。

AIを使ったプログラミングが企業で急速に普及してきているにも関わらず、大学などの教育機関ではAIを使ったプログラミング教育は進んでいないらしい。このままでは卒業生が就職できたとしても仕事ができなくなるのではないか?こういった現状で学生はどうしたらいいのか?

エグゼクティブサマリー

本レポートは、企業のソフトウェア開発におけるAIの急速な統合と、大学のカリキュラムの遅々とした進展との間に存在する、看過できないほど拡大しつつある乖離について論じる。企業がAIコーディングアシスタントを活用して前例のない生産性向上を実現する一方で、学生たちは自らの正規教育が現代の職場の現実に即していないのではないかという懸念を強めている。本分析は、これらの懸念を実証し、「AI拡張エンジニア」に不可欠なスキルセットを再定義し、学生のための包括的かつ実行可能な青写真を提供する。本レポートの核心的なメッセージは、エンパワーメントである。すなわち、積極的な自己学習、実践的なプロジェクトワーク、そしてコミュニティへの参加という戦略を能動的に採用することにより、学生はこの溝を埋めるだけでなく、次世代のテクノロジーリーダーとして自らを位置づけることが可能となる。

第1章 新たな産業革命:AIによる企業ソフトウェア開発の変革

本章では、このパラダイムシフトの現実を確立する。誇大広告を超え、AIがプロフェッショナルなソフトウェア開発をいかに根本的に変えつつあるか、具体的な証拠を提示する。これにより、利用者の前提を検証し、状況の緊急性を明確にする。

1.1 オートコンプリートを超えて:AI駆動開発への深掘り

現代のソフトウェア開発は、単なるコード補完機能の進化を超えた、根本的な変革の渦中にある。この変革を牽引しているのが、GitHub Copilotに代表されるAI搭載の開発ツール群である 1。これらのツールは、開発者のワークフローに深く統合され、従来は人間の手作業に依存していた多くのタスクを自動化・支援する。

その中核機能は多岐にわたる。リアルタイムでのコード補完は言うまでもなく、開発者が自然言語(日本語を含む)でコメントを記述するだけで、その意図を汲み取ったコードブロック全体を生成する能力を持つ 2。さらに、既存のコードからテストケースを自動生成したり、関数やクラスのドキュメンテーションを作成したりすることも可能である 2。GitHub Copilot Chatのような対話型インターフェースを通じて、実装前の設計思想についてAIに相談したり、書き終えたコードのレビューを依頼したり、あるいは特定の機能を実現するための複数の実装パターンを提案させることさえできる 2。これはもはや単なる「ツール」ではなく、開発プロセス全体にわたる「協働パートナー」と呼ぶべき存在である。

この変革は、大手企業において戦略的に推進されている。例えば、日立製作所はGitHub Copilotを単なる生産性向上ツールとしてだけでなく、特定のベンダーに依存しないグローバルスタンダードな開発環境へ移行するための中核的な要素と位置づけている 4。同社が実施した社内アンケート調査では、利用者の83%が「タスクを迅速に完了できる」と回答しており、その効果は定量的に証明されている 4

さらに衝撃的なデータは、Accenture社とGitHubの共同調査によってもたらされた。この調査では、Copilotの導入によりタスク完了までの時間が平均で55%も短縮され、コードレビューの合格率が15%向上したことが確認されている 1。また、法人向けにサービスを提供するZoomInfo社では、400名以上のエンジニアが日常的にCopilotを利用しており、1日あたり約6,500件の提案のうち33%が受け入れられているという事実は、AIが単なる補助機能ではなく、開発ワークフローの根幹に深く組み込まれていることを示している 1

1.2 影響の定量化:生産性と品質の新たな指標

AIがもたらす影響は、個々の開発者の主観的な感想にとどまらない。それは、企業の競争力を左右する測定可能なビジネス上の優位性となっている。日立製作所が掲げる「2027年までにシステム開発の生産性を30%向上させる」という野心的な目標は、AI活用が経営戦略そのものであることを物語っている 4。この生産性向上の本質は、コーディングや単体テストといった反復的で時間のかかるタスクをAIに委譲し、人間である開発者をより高度な問題解決やアーキテクチャ設計といった創造的な業務に集中させることにある 6

従来の開発現場では、「1日に書いたコードの行数」が生産性の一つの指標とされることもあった。しかし、タスク完了時間が55%短縮される世界では、この指標は意味をなさなくなる 1。価値の源泉は、コードを「書く」行為そのものから、AIを駆使して複雑な問題を効率的かつ堅牢に「解決する」能力へと移行している。1日かけて手作業でコードを書くエンジニアよりも、洗練されたプロンプトと検証作業を通じて1時間で問題を解決するエンジニアの方が、たとえ一行もコードを「書かなかった」としても、はるかに高い価値を持つ。これは、特に若手人材の評価や育成方法に根本的な見直しを迫るものである。開発者が費やす時間は、能動的なコーディングから、プロンプト入力、レビュー、そしてAIの応答を待つ時間へとシフトしている 8

さらに、AIツールは速度だけでなく、品質とセキュリティの向上にも大きく貢献している。リアルタイムでのエラー検出や、SQLインジェクションのような脆弱なコード提案を自動的にブロックする機能は、開発サイクルの早期段階で欠陥を排除し、手戻りを削減する 1。複雑なコードのリファクタリング(構造改善)を支援することで、より保守性が高く、長期的に維持可能なソフトウェア資産の構築を可能にする 6

見過ごされがちだが極めて重要なのが、開発者体験(Developer Experience)の向上である。Accenture社の調査で、開発者の90%がCopilot導入後に仕事への充実感が増したと回答した事実は、AIが開発者を退屈な作業から解放し、創造性を刺激する存在であることを示している 1。満足度の高い開発者は、より質の高い仕事をし、組織への定着率も高まる傾向にある。

1.3 再構築されるソフトウェア開発ライフサイクル(SDLC)

生成AIの影響は、コーディングという一工程に限定されない。それは、アイデアの着想から設計、実装、テスト、デプロイメントに至るソフトウェア開発ライフサイクル(SDLC)の全段階に及んでいる 6

従来、ビジネス担当者が持つ漠然としたアイデアを、エンジニアが詳細な要件定義に落とし込むプロセスには、多くのコミュニケーションコストと時間を要した。生成AIは、自然言語で記述された高レベルのアイデアを、具体的な機能要件やユーザーストーリーに変換する支援を行う 6。さらに、AIを用いてUIデザインのプロトタイプを生成し、対話形式で要件との整合性を確認するといった活用も始まっている 5

テスト工程においても、AIは手動テストではカバーしきれない広範なシナリオのテストケースを自動生成し、重要なテストを優先順位付けすることで、リソースを効率的に配分する 6。これにより、テストカバレッジの拡大と品質保証の高度化が両立される。

この変革は、「開発の民主化」とも呼ばれる現象を引き起こしている。AIツールが技術的な障壁を一部取り払うことで、従来は専門的なエンジニアでなければ関与が難しかったプロセスに、非技術系のビジネス担当者もより深く参加できるようになる 6。これにより、部門間のコラボレーションが促進され、ビジネスニーズと技術的実装の間のギャップが埋まり、イノベーションが加速される。

1.4 明確な軌道:AIエージェントの台頭

現在主流のAIコーディングアシスタントは、この変革の序章に過ぎない。技術の進化が示す明確な軌道は、「コード提案」から「自律的なアクション」へと向かっている。次世代のAIは、単一の指示に応答するだけでなく、複数のステップからなる複雑なタスクを自律的に計画し、実行する「AIエージェント」へと進化していく 11

この進化は、開発者の役割をさらに一段階引き上げる。開発者は、もはやコードを書く職人ではなく、複雑なビジネス課題を解決するために、これらのインテリジェントなエージェントを設計し、監督し、指導する「AIアーキテクト」としての役割を担うようになる 11。企業の競争力は、いかに優秀なAIエージェントを構築し、ビジネスプロセスに組み込めるかにかかってくる。

日立のような企業が「Generative AIセンター」を設立し 4, NTTデータが生成AIを競争力獲得のための「ゲームチェンジングテクノロジー」と位置づけているのは 12、この未来を見据えた戦略的な動きに他ならない。彼らは単にツールを導入しているのではなく、AIを中核に据えた次世代の開発体制と組織文化を構築しようとしている。学生にとって、この事実は極めて重要である。AIスキルは、もはや選択科目ではなく、これからの技術者にとって必須の基礎教養となりつつある。このトレンドを無視することは、1990年代後半にインターネットの重要性を無視するに等しく、キャリア形成において致命的な判断ミスとなりかねない。

一方で、この生産性の飛躍は新たなリスクも生み出す。AIが生成した高品質なシステムであっても、その内部コードが過度に複雑で人間には解読困難な「ブラックボックス」と化す可能性がある 5。もしそのコードを理解し、保守できる人材がいなければ、将来的に深刻な問題を引き起こしかねない。これは、AIを使いこなす能力と、その出力物を批判的に評価し管理するために不可欠な、コンピュータサイエンスの普遍的な原理原則を学ぶことの重要性を逆説的に示している。

第2章 象牙の塔のジレンマ:大学のカリキュラムはなぜ追いつけないのか

本章では、教育現場が産業界の急速な変化に追いつけていない背景にある、構造的・教育的な課題を探る。現実的な制約と教育哲学上のジレンマを認識した上で、この変革期を巧みに乗り越えつつある先進的な大学の事例を紹介し、学生が何を求め、どこに希望を見出すべきかを示す。

2.1 変化のペース vs 制度の慣性

大学という組織が、産業界の指数関数的な技術進化のペースに追随することが困難な理由は、根深い構造的問題に起因する。第一に、カリキュラムの改訂プロセスは、多くの場合、数年単位の時間を要する慎重な審議と承認を必要とする 13。これは教育の質と一貫性を担保するためには不可欠な仕組みであるが、数ヶ月単位で状況が激変する生成AIの分野では、著しい足枷となる。

第二に、教員自身のAIリテラシーの問題がある。生成AIは専門家でさえその全貌を理解しきれていない新しい技術であり 14、多くの教員が自ら学びながら手探りで指導方法を模索しているのが現状である。AIを効果的に活用できる教員の育成が追いついていないという「教師のAIリテラシー不足」は、教育の質のボトルネックとなっている 13。この問題は、AIスキルを持つ人材が産業界に高待遇で吸収され、学術界から人材が流出しやすいという構造によってさらに悪化する。結果として、大学は自らをアップデートするために必要な専門知識を外部から獲得することも、内部で育成することも困難な状況に陥っている。

第三に、物理的なインフラの問題がある。GIGAスクール構想以前の日本の教育現場におけるICT環境が国際的に見ても著しく遅れていた事実は 15、教育機関におけるインフラ投資が常に課題であったことを示している。生成AIを本格的に教育に導入するには、安定したネットワーク環境や、学生全員が利用できる十分な計算リソースが必要となり、これには多大なコストが伴う 13

2.2 教育哲学上の論争:ツールか、それとも思考の松葉杖か

構造的な問題に加え、教育者たちの間では「生成AIを教育にどう取り入れるべきか」という、より本質的な教育哲学上の議論が続いている。

その中心にあるのが、「思考力の低下」への懸念である。プログラミング教育の本来の目的は、単にコードを書く技術を教えることではなく、論理的思考力、問題解決能力、創造性といった「プログラミング的思考」を育成することにある。もし学生が、アルゴリズムの原理を理解することなくAIにコードを生成させるだけで課題を完了できてしまうなら、それは本来育成すべき能力を阻害する「思考の松葉杖」になりかねない 16。研究によれば、学習者が安易に生成AIを利用すると、試行錯誤の機会が失われ、期待された学習効果が得られない可能性が指摘されている 17

また、AIの出力が常に正しいとは限らないという「ブラックボックス問題」も存在する。AIは学習データに含まれるバイアスを反映したり、事実に基づかない情報(ハルシネーション)を生成したりすることがある。そのため、学生にはAIの出力を鵜呑みにせず、その限界を理解し、批判的に評価する能力(AIリテラシー)を身につけさせることが不可欠である 14。東京大学の議論でも、AIリテラシーとは「個人が批判的にAI技術を評価する能力のセット」と定義されており、その育成が急務であるとされている 14

一方で、この技術は教育のあり方を根底から変える可能性を秘めている。東京大学の専門家パネルでは、生成AIがアイデアを形にする技術的なハードルを劇的に下げることで、教育の重点を従来の「思考力重視」から、実際に何かを「作ること重視」へとシフトさせるべきだという提言がなされている 14。AIがコーディングを代行してくれる時代において、人間に残される最も重要な役割は、何を「つくりたい」かという創造的な欲求やビジョンを持つことである。この視点に立てば、AIは思考を奪う脅威ではなく、創造性を解放する強力なツールとなる。

この論争の核心は、学生がAIが生成したコンテンツをただ受け入れるだけの「消費者」になるか、AIを駆使して自らのアイデアを形にする「創造者」になるかの分岐点にある 14。先進的な教育機関は、後者の育成を目指し、単にAIの使い方を教えるのではなく、「AIと共に思考する方法」を教えるという、より高度な教育モデルを模索している。

2.3 進歩の灯火:教育におけるAI活用のパイオニアたち

こうした困難な状況の中にあっても、一部の先進的な大学はAI時代に対応するための革新的な取り組みを始めている。これらの事例は、学生が自らの大学を評価し、あるいは学外に求めるべき学習機会がどのようなものであるかを知る上で、重要な指標となる。

2.3.1 国内のイノベーター

  • 東北大学: 日本の大学におけるAI活用の先駆者であり、全国に先駆けてChatGPTを大学の公式業務に導入した 18。単に導入するだけでなく、会議の議事録要約アプリを内製開発したり、学内データと連携した独自のチャットボットを構築したりと、実践的な活用を推進している 19。さらに重要なのは、学生と教職員それぞれに向けた詳細な利用ガイドラインを策定・公開し、AIのリテラシー教育と並行して導入を進めている点である 18
  • 東洋大学: 情報連携学部において、GPT-4を活用した教育システム「AI-MOP」を導入 18。このシステムはSlackと連携しており、学生がプログラミングやシステム開発に関する疑問をボットに質問することで、自学自習を促進することを目的としている。大学として「生成AIとの対話を繰り返すことが自分の考えを深める」という見解を示し、積極的な活用を推奨している 18
  • 立命館大学: 英語学習ツール「Transable」の試験導入において、極めて示唆に富んだルールを設けた。それは、「生成された英文をそのまま使用することを禁止する」というものである 18。これにより、学生はAIを単なる答えを出す機械としてではなく、自らの思考の「叩き台」や「壁打ち相手」として利用することを強いられる。AIの生成物を批判的に検討し、修正・改善するというプロセスを通じて、より高度な学習効果を狙っている。これは、「AIと共に思考する方法」を教える優れた教育実践例と言える。
  • 会津大学: コンピュータ理工学に特化した大学として、以前から徹底した実践的プログラミング教育で知られている 21。3Dプリンタなどを備えた工作室「Aizu Geek Dojo」のような「ものづくり」を奨励する環境や、アクセンチュア、富士通、NECといった大手企業との強固な産学連携体制が特徴である 23。学生は共同研究プロジェクトなどを通じて、現実世界の課題解決に直接関わる機会を豊富に得られる。

2.3.2 世界のリーダー

  • カーネギーメロン大学(CMU): コンピュータサイエンスの名門校であり、米国で初めてAIの学士課程を設立した 26。そのカリキュラムの最大の特徴は、倫理と社会貢献を重視している点にある。社会学部や公共政策学部の教員がAIの倫理的・社会的側面について教鞭をとり、運輸や医療といった分野でのAI活用法を探求する独立した学習機会が設けられている 26
  • スタンフォード大学: 機械学習やAIに関する基礎的なオンラインコースを早期から無償で公開し、世界中の何百万人もの学習者に門戸を開いたパイオニアである 27。同大学のAndrew Ng教授による講義は、この分野のデファクトスタンダードとなっている。近年では、AIが生徒の学習を支援するだけでなく、チューター(指導者)に助言を与えることで教育の質そのものを向上させる研究にも取り組んでいる 29
  • プロジェクトベース学習(PBL)モデル: ハーバード大学の「CS50’s Introduction to Artificial Intelligence with Python」やフロリダ大学の「AI Across the Curriculum」構想のように、AI時代における効果的な教育手法として、プロジェクトベース学習(PBL)が再評価されている 30。PBLでは、学生は現実世界の問題を解決するためのプロジェクトにチームで取り組み、その過程で必要な知識やスキルを能動的に学んでいく。AIツールは、このPBLにおいて、アイデアのブレインストーミングや情報収集、プロトタイピングを加速させる強力な触媒として機能する 33

これらの先進事例から浮かび上がるのは、大学がAIを単なる業務効率化ツールとして導入する(例:事務作業の自動化)だけでなく、それを教育・研究の中核にどう組み込むかという戦略的な視点を持っていることである。東北大学や近畿大学のように、まず事務部門でAI活用を進めることで、組織全体のAIへの習熟度を高め、安全な利用ポリシーを確立し、その上で教育への展開を図るというアプローチは、教育現場へのAI導入をスムーズに進めるための有効な戦略と言えるだろう 18

第3章 AI拡張エンジニア:現代のスキルセットの再定義

本章は、本レポートの理論的な核心である。従来のプログラマーから、現代のAI拡張エンジニアへと移行するために学生が習得すべき新たな能力を明確に定義する。

3.1 コーダーから指揮者へ:根本的な役割の変化

ソフトウェアエンジニアの役割は、コードを手作業で記述するという労働集約的なタスクから、AIツールが生み出す成果物を指揮し、検証し、統合するという、より高度で監督的な役割へと進化している 6。これは、オーケストラの演奏者が、自ら楽器を演奏するだけでなく、各パートの音を調整し、全体のハーモニーを創り出す指揮者へと役割を変えるのに似ている。エンジニアの仕事は、構文の正確さよりも、戦略の妥当性が問われるようになる 36

この変化に伴い、エンジニアの価値の源泉もシフトする。単純な繰り返し作業や定型的なコーディングはAIによって自動化されるため 7、人間の価値は、より抽象的で創造的なタスクへと移行する。具体的には、システムの全体像を描くアーキテクチャ設計、ビジネス上の曖昧な課題を技術的に解決可能な問題へと落とし込む能力、そして最終的な製品がビジネス目標とユーザーのニーズを満たしているかを保証する責任などが、これまで以上に重要になる 6

3.2 新たなコアコンピテンシー

AI時代に求められるスキルは、従来のプログラミングスキルを置き換えるものではなく、それを土台とした上で、新たな能力を積み上げるものである。

  • 高度なプロンプトエンジニアリング: これは、機械との新たな対話方法である。単に平易な言葉で質問を投げかけるのではなく、生成AIから望ましい、高品質かつ安全な出力を引き出すために、指示(プロンプト)を体系的に設計し、改良し、最適化する一連の技術を指す 40。これには、タスクの背景情報(コンテキスト)の提供、出力形式の厳密な指定、少数の例を提示してAIの挙動を誘導するフューショット・プロンプティング、そしてAIに思考のプロセスを段階的に記述させる思考の連鎖(Chain-of-Thought)プロンプティングといった、多様なテクニックが含まれる 40。これはもはや単なる「質問力」ではなく、それ自体が高度な専門スキルとして認識されている 44
  • システム思考とアーキテクチャの完全性: AIが個々の部品(コード)を効率的に生成してくれるようになるからこそ、それらを組み合わせて一つの堅牢な建築物(システム)を構築する、全体論的な視点が極めて重要になる 11。システム思考とは、個々のコンポーネントがどのように相互作用し、システム全体としてどのような特性(パフォーマンス、スケーラビリティ、回復力など)が生まれるかを理解する能力である 46。複雑なシステムにおけるトレードオフを管理し、ある部分への変更が他に予期せぬ影響を及ぼさないかを見通すこの能力は、現時点のAIには模倣できない、人間に固有の価値である 36
  • 人間参加型ループ:批判的なコードレビューと倫理的検証: AIが生成したコードは、決して完璧ではない。それは、微妙なバグやセキュリティ上の脆弱性を含んでいる可能性があり、また、その学習データに内在する偏見を反映することもある 5。したがって、人間であるエンジニアは、AIの生成物を無批判に受け入れるのではなく、それを精査し、デバッグし、改良するための最後の、そして最も重要な品質保証の関門として機能しなければならない 37。この役割は、最終的な成果物に対する「説明責任」を伴う。AIは責任を取ることができないため、この責任は常に人間が負うことになる 57。この責任を果たすためには、付け焼き刃の知識ではなく、コンピュータサイエンスの揺るぎない基礎知識が不可欠である。
  • 強化されたソフトスキル: AIが実装の詳細、すなわち「何を(What)」作るかを担うようになるにつれて、人間は「なぜ(Why)」それを作るのかという、より上流の問いに関わるスキルセットの価値を高めることになる。
    • 問題の分解能力: 曖昧で大規模なビジネス課題を、AIが処理可能な、明確に定義された小さなタスク群へと分解する能力 39
    • コミュニケーションと協調: 技術的な要件をAIに正確に伝える言語能力と、AIの出力とその意味合いを非技術系の関係者に分かりやすく説明する能力 58
    • 創造性とイノベーション: AIの新たな応用分野を見つけ出したり、AIの学習データに含まれる既存のパターンを超えた、斬新な解決策を考案したりする能力 39

表1:ソフトウェアエンジニアの進化するスキルセット

このセクションの中心的な議論を明確にするため、以下の表で従来のスキルとAI拡張時代のスキルを対比する。これは、学生が自らの学習の方向性を評価するための思考の枠組みを提供する。

従来のスキル焦点AI拡張スキルの焦点典拠
高速な手動コーディング: 速いタイピング、構文や定型コードの暗記。高度なプロンプトエンジニアリング: AIによるコード生成を導くための、正確で文脈豊かな指示の作成。40
既知のアルゴリズムの実装: 標準的なアルゴリズム(ソート、検索など)をゼロから書く能力。戦略的なツールとアルゴリズムの選択: AIにどのアルゴリズムを実装させるべきかを知り、それを統合する能力。38
一行ずつのデバッグ: 実行を手動で追い、エラーを発見する作業。AI出力の批判的評価と検証: AI生成コードの正確性、セキュリティ、効率性をレビューする能力。3
単一コンポーネントへの集中: 特定のライブラリや関数に関する深い知識。システム思考とアーキテクチャ: システム全体を理解し、AI生成コンポーネントの相互作用を管理する能力。11
技術力が主要な価値: 価値は深い技術的知識から生まれる。問題の分解とコミュニケーション: ビジネスニーズをAIが管理可能なタスクに分解し、結果を説明する能力。58
詳細な仕様の遵守: 事前に定義された要件の実装。創造性と問題のフレーミング: 解決すべき問題を定義し、AIを創造的に適用して斬新な解決策を見出す能力。35

この比較から明らかなように、求められるスキルはより戦略的、全体論的、そして人間中心的なものへとシフトしている。「コードが書ける」から、「AIを使って問題を解決し、その結果に責任を持てる」へと、エンジニアに求められる価値の定義そのものが変化しているのである。

第4章 意欲的な学生のための戦略的青写真

本章は、本レポートの中で最も実践的な部分であり、学生が自らの手で未来を切り拓くための、具体的かつ段階的な行動計画を提示する。

4.1 フェーズ1:基礎の強化(自己主導型学習)

AIツールは既存の知識を増幅させる「アンプ」のようなものである。したがって、強力な基礎知識は、これまで以上に重要であり、交渉の余地のない必須条件である 7。大学の授業だけに頼るのではなく、自らの手でパーソナルなカリキュラムを構築する必要がある。

4.1.1 オンラインプラットフォームの活用

質の高い学習コンテンツが、かつてないほど容易に入手可能になっている。これらを戦略的に活用することで、大学のカリキュラムを補完し、最先端の知識を習得することができる。

  • Coursera: スタンフォード大学のAndrew Ng教授のようなAI分野のパイオニアによる講座や、IBM、Googleといった業界の巨人が提供するプログラムが豊富に存在する 62。これらのプラットフォームは、AIの基礎理論、機械学習、プロンプトエンジニアリングなどを体系的に学べる専門講座(Specialization)やプロフェッショナル認定(Professional Certificate)を提供しており、基礎固めに最適である 64
  • Udemy: より実践的で、特定の技術に焦点を当てたハンズオン形式のコースを手頃な価格で受講できるプラットフォームとして位置づけられる 65。「AI for Everyone」のような概論的なコースから、特定のPythonライブラリを深く学ぶコースまで、幅広い選択肢がある。
  • 国内のプラットフォーム: 日本語での学習を好む学生には、「Aidemy」や「Chainerチュートリアル」といった国内サービスも有力な選択肢となる 69

4.1.2 AI時代の必読書

構文の学習を超え、より高度な思考法を身につけるためには、良質な書籍から学ぶことが不可欠である。

  • システム思考: 第3章で論じた全体論的・構造的な思考法を養うために、『実践システム・シンキング』のような書籍は、エンジニアとしての視野を広げる上で非常に有益である 71
  • ソフトウェア設計原則: AIが生成したコードを批判的に評価する「目」を養うためには、『リーダブルコード』や『良いコード/悪いコードで学ぶ設計入門』といった、ソフトウェア設計の原理原則を解説した古典的・現代的な名著を読むことが推奨される 72

4.1.3 AIをパーソナルチューターとして活用する

学習プロセスそのものにAIを組み込むことで、学習効率を飛躍的に高めることができる。これは、AI時代に必須の「メタスキル」である 75

  • コード解説: 理解できないコード片をAIに提示し、「このコードの目的と仕組みを初学者向けに一行ずつ解説してください」と依頼する。
  • エラー解決: プログラム実行時に発生したエラーメッセージをそのままAIに渡し、考えられる原因と具体的な対処法を尋ねる。
  • 概念の明確化: 再帰やポリモーフィズムといった抽象的な概念について、「小学生にもわかるように、たとえ話を使って説明してください」など、理解できるまで様々な角度から質問を繰り返す。
  • 学習ログの構築: AIとの有益な対話は、Notionなどのツールに記録しておく。これにより、自分だけのカスタマイズされたQ&A集が構築され、後から容易に復習できる 75

4.2 フェーズ2:検証可能な実世界経験の構築(実践的応用)

理論的な知識だけでは不十分である。採用担当者が最も重視するのは、その知識を応用して実際に問題を解決した経験である。

4.2.1 競争を通じたスキルアップ(データサイエンスとヒューリスティクス)

  • KaggleとSIGNATE: これらは単なるコンペティションサイトではない。現実世界の不完全でノイズの多いデータセットを扱うための実践的な訓練場である 76。他の参加者が公開しているコード(Notebook)を読むことで、多様なアプローチを学ぶことができる。特にSIGNATEは、日本語で参加できるため、日本の初心者にとって参入障壁が低い 79
  • AtCoder Heuristic Contest: 従来の競技プログラミングとは異なり、唯一の正解が存在しない複雑な最適化問題に対して、「できるだけ良い解」を見つけ出す能力を競う 80。これは、現実のビジネスや工学における多くの問題設定に近く、実用的な問題解決能力を鍛えるのに適している。

4.2.2 協働と創造(ソフトウェア開発)

  • ハッカソンとアイデアソン: 短期間で集中的にチームを組み、アイデアの着想からプロトタイプの開発、そしてプレゼンテーションまでを経験する絶好の機会である 82。チームワーク、迅速な開発、そしてAIを活用したアイデアを他者に説得力をもって伝える能力が試される。
  • オープンソースへの貢献(例:Google Summer of Code): これは、学生が積める経験の中でも特に評価が高いものの一つである。実際の巨大なコードベースに触れ、世界中のエンジニアと英語でコミュニケーションを取りながら、経験豊富なメンターの指導のもとで開発を進める 85。過去に参加した日本人学生の体験談は、技術力だけでなく、語学力や国際的な協業能力を証明する強力な材料となる 85

4.2.3 専門分野の選択と探求

AIは広大な分野であるため、早い段階で自身の興味や強みに合った専門領域(例:自然言語処理、コンピュータビジョン、ロボティクス)を見つけることが重要である 88。大学の研究室を選ぶ際には、「スタビキ」のようなサービスを活用して、自分の関心分野に合致する教員や研究室を事前に調査することが、有意義な研究活動につながる 90

4.3 フェーズ3:スキルの市場価値化(ポートフォリオと面接)

習得したスキルと経験は、採用担当者に伝わらなければ意味がない。

4.3.1 AI時代に響くポートフォリオの作り方

現代のポートフォリオは、単なる成果物のギャラリーではない。それは、自身の問題解決能力を物語るストーリーでなければならない。採用担当者は、完成品の完璧さよりも、その背後にある思考プロセスや学習能力を重視する 91

各プロジェクトには、以下の要素を明確に記述することが求められる。

  • 課題設定(The Problem): どのようなビジネス上またはユーザーの課題を解決しようとしたのか 93
  • プロセス(The Process): AIをどのように活用したか。具体的なプロンプトの例や、なぜその技術・設計を選択したのかという意思決定の過程を説明する 91
  • 人間の貢献(The Human Contribution): AIの出力をどのように評価し、改良したのか。AIの提案のどこに問題があり、それをどう修正したのか。「AIによる初期出力(Before)」と「人間による改善後のコード(After)」を並べて示すことで、自身の付加価値を明確にアピールできる 92
  • 成果(The Outcome): プロジェクトの結果、何が達成されたのか。「処理速度をX%改善した」のように、可能な限り定量的に示す 94

チュートリアルを単に模倣しただけのプロジェクトよりも、たとえ小規模であっても、このような思考の過程が丁寧に文書化されたプロジェクトの方が、はるかに高く評価される 95

4.3.2 AI中心の技術面接を乗り切る

面接で問われる内容も変化する。従来のアルゴリズムに関する知識に加えて、新たな能力を問う質問に備える必要がある 96

  • 想定される質問例:
    • 「この設計課題に対して、AIアシスタントをどのように活用してアプローチしますか?」
    • 「ここにAIが生成したコードがあります。これをレビューして、問題点を指摘してください。」
    • 「AIツールの出力が間違っていた経験について教えてください。どのようにしてその誤りを発見し、修正しましたか?」

これらの質問は、候補者がAIを単なるツールとして使うだけでなく、その限界を理解し、主体的にコントロールできるかを試すものである。

4.4 フェーズ4:成長の加速(コミュニティとネットワーキング)

孤立した学習は非効率であり、モチベーションの維持も困難である。コミュニティに参加することで、学習は加速し、新たな機会が生まれる 98

  • 学生技術コミュニティの力: 「PlayGround」や「GeekSalon」のような学生主体の技術コミュニティは、同じ志を持つ仲間との出会いの場である 98。ピアラーニング(仲間同士の学び合い)、メンターからの助言、チームでのプロジェクト開発、さらには提携企業からのインターンシップや就職のオファーといった、多くの利点がある 99
  • 最先端に追いつき続ける戦略: AI分野の進化は非常に速いため、継続的な情報収集が不可欠である。
    • X(旧Twitter)での情報収集: 深津貴之氏やチャエン氏といった、国内外のAI分野における第一人者や影響力のあるインフルエンサーをフォローすることで、最新のトレンドやツールに関する情報を効率的に得ることができる 100
    • トップカンファレンスの動向把握: NeurIPS、ICML、ICLRといった世界トップクラスの学術会議で発表される論文は、数年後の技術トレンドの源泉である 104。これらの動向を追うことで、技術の潮流を大局的に理解できる。
    • 情報収集の自動化: AIを活用して、最新情報を収集するプロセス自体を自動化することも有効である。特定のキーワードに関するニュースを要約して定期的にメールで送信するようAIエージェントに指示するなど、情報収集の負担を軽減することができる 107

学生が今、身につけるべき最も重要な「メタスキル」は、AIを活用して他のすべてをより速く学ぶ能力である。KaggleコンペのコードをAIにデバッグさせ、専門書の新しい設計パターンをAIに解説させ、トップカンファレンスの研究論文の要約をAIに作成させる。このスキルを習得した学生は、そうでない同級生を指数関数的に凌駕するだろう。これは、学生が目指すべき究極の戦略目標である。

第5章 結論:AI拡張エンジニアとしてのあなたの未来

ソフトウェア開発の世界は、手作業によるコーディングが中心だった時代から、人間とAIが協働する新たな時代へと、不可逆的なパラダイムシフトを遂げた。この変化は、一部の仕事を陳腐化させる脅威であると同時に、エンジニアがこれまで以上に複雑で意義深い課題に取り組むことを可能にする、またとない機会でもある。

本レポートで明らかにしたように、大学教育システムがこの急速な変化に適応するには、まだ時間を要するだろう。しかし、それは学生がただ手をこまねいて待つべきだということを意味しない。むしろ、自らのキャリアの主導権を握り、未来を形作る力が、これまで以上に学生自身の手に委ねられている。

本レポートで提示した4つのフェーズからなる戦略的青写真は、そのための具体的な道筋である。基礎知識を主体的に学び、実践的なプロジェクトを通じて経験を積み、その成果を効果的に伝え、コミュニティと繋がりながら常に学び続ける。このサイクルを回すことで、学生は産業界と学術界の間の溝を埋めるだけでなく、AIを自在に操る次世代のエンジニアとして、自らを差別化することができる。

未来のソフトウェアエンジニアリングは、人間の創造性、批判的思考、そして強力なAIツールの間のシナジーによって駆動される。あなたがこれから歩む道は、単に「就職するための準備」ではない。それは、この刺激的な新時代の最前線に立ち、前例のないイノベーションを創出し、人類が直面するより大きな問題を解決していくための、第一歩なのである 10

AI,API,gemini,Google Apps Script,クラウド,バイブコーディング,中級,生成AI

このチュートリアルでは、Google Gemini APIを使って手書きで描いた絵の説明をして改善点を教えてくれるアプリを作ります。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いています。バイブコーディングです。

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

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

Google Apps Scriptの設定

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

Gemini 2.5 Pro を使って Google Apps Script を書く

無料のアカウントでもこちらからGemini 2.5 Proが使えます。

プロンプト

App inventorのキャンバスに描いた絵に説明をつけて改良点を提案させたい。Google apps script とGoogle ai studio を使う。キャンバスに描いた絵はエクステンションでbase64エンコードしてJSONでアップロードする。結果はJSONで戻す。APIキーはスクリプトプロパティから読む。App inventor部分は自分でやるから、それ以外の部分を教えて。

返答

Google Apps Scriptだけではなく、Gemini APIの設定からウェブアプリとしてのデプロイ方法、使い方まで詳しく教えてくれます。

はい、承知いたしました。
App Inventorで描いた絵をGoogle AI(Gemini)で分析し、説明と改善点を返すGoogle Apps Scriptのウェブアプリケーションを作成します。

App Inventor側の実装はご自身でされるとのことですので、Google Apps Script側の設定方法とコードを提供します。

全体の流れ
App Inventor:

Canvasに絵を描く。

描いた絵をBase64形式でエンコードする。

Base64文字列をJSONに含めて、Google Apps Scriptで作成したウェブアプリのURLにPOSTリクエストを送信する。

Google Apps Script (今回作成するもの):

App InventorからのPOSTリクエストを受け取る。

JSONからBase64形式の画像データを取り出す。

スクリプトプロパティに保存したAPIキーを読み込む。

Google AI (Gemini Pro Vision) APIに画像データとプロンプトを送信する。

Geminiから返ってきた「説明」と「改善点の提案」をJSON形式で整形する。

整形したJSONをApp Inventorに返す。

App Inventor:

Google Apps Scriptから返ってきたJSONを解析し、結果をアプリに表示する。

それでは、Google Apps Scriptの設定とコードを以下に示します。

Google Apps Script (GAS) の設定手順

Google AI (Gemini) APIを利用するためのGoogle Apps Scriptプロジェクトの設定手順です。

1. Google Apps Script プロジェクトの作成

  1. Google Drive を開きます。
  2. 「+ 新規」 > 「その他」 > 「Google Apps Script」 をクリックして、新しいプロジェクトを作成します。
  3. プロジェクトに名前を付けます(例:「画像分析AI」)。

2. APIキーの保存

APIキーをコードに直接書き込むのはセキュリティ上好ましくありません。代わりにスクリプトプロパティに保存します。

  1. GASエディタの左側メニューから「プロジェクトの設定」(歯車のアイコン)をクリックします。
  2. 「スクリプト プロパティ」のセクションにある「スクリプト プロパティを追加」をクリックします。
  3. 以下の情報を入力します。

    • プロパティ: GEMINI_API_KEY
    • 値: ご自身のGoogle AI StudioのAPIキー

  4. 「スクリプト プロパティを保存」をクリックします。

3. ウェブアプリとしてデプロイ

作成したスクリプトを外部(App Inventor)から利用できるように、ウェブアプリとして公開します。

  1. GASエディタの右上にある「デプロイ」ボタンをクリックし、「新しいデプロイ」を選択します。
  2. 「種類の選択」の横にある歯車アイコンをクリックし、「ウェブアプリ」を選択します。
  3. 設定を以下のように変更します。

    • 説明: (任意)画像分析用のAPIなど
    • 次のユーザーとして実行: 「自分」
    • アクセスできるユーザー: 「全員」

  4. 「デプロイ」ボタンをクリックします。
  5. 初回は「アクセスを承認」を求められます。ご自身のアカウントを選択し、詳細画面で「(安全でないページに)移動」をクリックして、アクセスを許可してください。
  6. 承認が終わると、ウェブアプリのURLが表示されます。このURLをコピーしてください。App Inventorからリクエストを送信する先のURLになります。

重要: コードを修正した場合は、再度「デプロイ」 > 「デプロイを管理」から、新しいバージョンを公開する必要があります。その際、「バージョン」を「新バージョン」にしてデプロイしてください。ウェブアプリのURLは通常変わりません。

これで、Google Apps Script側の準備は完了です。次に示すコードをGASエディタに貼り付けて保存してください。

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-pro-vision:generateContent?key=${API_KEY}`;

    // 4. Gemini APIに送信するリクエストボディを作成
    const payload = {
      "contents": [{
        "parts": [{
          "text": "この絵を説明して、改善点を提案してください。説明と提案は明確に分けて記述してください。"
        }, {
          "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);
}

プロンプト

Gemini APIのAPIキーの取り方が無かったので、追加で尋ねます。

APIキーの取り方教えて

返答

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

AI,API,gemini,Google Apps Script,クラウド,バイブコーディング,中級,生成AI

このチュートリアルでは、Google Cloud Vision APIを使って手書き文字の文字認識を行うアプリを作ります。クラウド光学文字認識(OCR)アプリでは写真で撮影した文字の光学文字認識を行いましたが、このアプリでは画面に手書きした文字を認識します。

Google Cloud Vision APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いています。バイブコーディングです。

Google Cloud Vision API1月1,000回まで無料ですが、それ以降は1,000回 あたり$1.50課金されます

Google Apps Scriptの設定

API呼び出し処理を行うサーバー側のプログラムをGoogle Apps Scriptに設定します。クラウド光学文字認識(OCR)アプリで使ったものとまったく同じなので、すでに設定済みの場合は同じものを使ってください。

Gemini 2.5 Pro を使って Google Apps Script を書く

無料のアカウントでもこちらからGemini 2.5 Proが使えます。

プロンプト

Google Apps ScriptでVision APIを使ってOCRするウェブアプリを作りたい。JSONでBase64画像をPOSTで受け取り、返すJSONには、認識したテキスト全文と、Vision APIからの生のレスポンスデータの両方を含める。Vision APIのAPIキーはスクリプトプロパティから読み込む。

返答

Google Apps Scriptだけではなく、Vision APIの設定からウェブアプリとしてのデプロイ方法、使い方まで詳しく教えてくれます。わからないところはGeminiに聞けば詳しく説明してくれます。たとえばこんな感じに。「コードをもっと詳しく説明して」

はい、承知いたしました。Google Apps Script (GAS) を使用して、Base64エンコードされた画像をJSON形式で受け取り、Google Cloud Vision APIでOCR処理を行うウェブアプリの作成方法を解説します。

完成したウェブアプリは、以下の仕様を満たします。

  * HTTP POSTリクエストで動作します。
  * `{"base64Image": "..."}` という形式のJSONを受け取ります。
  * Vision APIのAPIキーは、コードに直接書き込まず、安全なスクリプトプロパティから読み込みます。
  * 処理結果として、認識したテキスト全体 (`text`) と、Vision APIからの生のレスポンス (`rawResponse`) を含むJSONを返します。

-----

## 1\. Google Apps Script コード

まず、GASプロジェクトに記述するコードです。以下のコードをスクリプトエディタにコピー&ペーストしてください。

```javascript
/**
 * POSTリクエストを受け取り、Vision APIでOCRを実行するメイン関数
 * @param {Object} e - GASがPOSTリクエスト時に自動的に渡すイベントオブジェクト
 * @returns {ContentService.TextOutput} - JSON形式のレスポンス
 */
function doPost(e) {
  try {
    // 1. リクエストボディからBase64画像データを取得
    if (!e.postData || !e.postData.contents) {
      return createJsonResponse({ error: 'Invalid request body.' }, 400);
    }
    const postData = JSON.parse(e.postData.contents);
    const base64Image = postData.base64Image;

    if (!base64Image) {
      return createJsonResponse({ error: 'The "base64Image" key is missing in the JSON payload.' }, 400);
    }

    // 2. スクリプトプロパティからAPIキーを読み込む
    const apiKey = PropertiesService.getScriptProperties().getProperty('VISION_API_KEY');
    if (!apiKey) {
      return createJsonResponse({ error: 'Vision API key is not set in Script Properties.' }, 500);
    }

    // 3. Vision APIへのリクエストを作成
    const visionApiUrl = `https://vision.googleapis.com/v1/images:annotate?key=${apiKey}`;
    const payload = {
      requests: [
        {
          image: {
            content: base64Image,
          },
          features: [
            {
              // 高精度なOCRのためにDOCUMENT_TEXT_DETECTIONを推奨
              type: 'DOCUMENT_TEXT_DETECTION', 
            },
          ],
        },
      ],
    };

    const options = {
      method: 'post',
      contentType: 'application/json',
      payload: JSON.stringify(payload),
      muteHttpExceptions: true, // APIからのエラーレスポンスを正しく受け取るため
    };

    // 4. Vision APIを呼び出し
    const response = UrlFetchApp.fetch(visionApiUrl, options);
    const responseCode = response.getResponseCode();
    const visionResponse = JSON.parse(response.getContentText());

    // Vision APIからのエラーをハンドリング
    if (responseCode !== 200) {
      return createJsonResponse({
        error: 'Vision API returned an error.',
        details: visionResponse,
      }, responseCode);
    }
    
    // 5. レスポンスからテキスト全体を抽出
    // ?. (オプショナルチェイニング) を使い、プロパティが存在しない場合でもエラーを防ぐ
    const fullText = visionResponse.responses[0]?.fullTextAnnotation?.text || '';

    // 6. 成功レスポンスを生成
    const result = {
      text: fullText,
      rawResponse: visionResponse,
    };

    return createJsonResponse(result, 200);

  } catch (error) {
    // JSONのパース失敗など、その他の予期せぬエラーをハンドリング
    return createJsonResponse({
      error: 'An unexpected error occurred.',
      details: error.message,
    }, 500);
  }
}

/**
 * JSON形式のレスポンスを生成するヘルパー関数
 * @param {Object} data - レスポンスとして返すオブジェクト
 * @param {number} statusCode - HTTPステータスコード (GASでは直接設定不可だが、ログ等のため)
 * @returns {ContentService.TextOutput}
 */
function createJsonResponse(data, statusCode) {
  // GASではHTTPステータスコードを直接設定できないため、
  // 返却するJSONデータにエラー情報を含めることでクライアント側で処理を分岐させます。
  console.log(`Response (Status ${statusCode}): ${JSON.stringify(data)}`);
  return ContentService.createTextOutput(JSON.stringify(data))
    .setMimeType(ContentService.MimeType.JSON);
}
```

-----

## 2\. セットアップ手順

コードを動作させるには、いくつかの準備が必要です。

### ステップ1: Cloud Vision APIの有効化とAPIキーの取得

1.  **Google Cloudプロジェクト**に移動し、Vision APIを使用したいプロジェクトを選択または新規作成します。
2.  ナビゲーションメニューから **[APIとサービス] \> [ライブラリ]** を選択します。
3.  「**Cloud Vision API**」を検索し、**[有効にする]** をクリックします。
4.  次に、**[APIとサービス] \> [認証情報]** に移動します。
5.  **[+ 認証情報を作成]** をクリックし、**[API キー]** を選択します。
6.  作成されたAPIキーをコピーしておきます。セキュリティのため、このキーには適切な制限(IPアドレス制限やAPI制限など)をかけることを強く推奨します。

### ステップ2: スクリプトプロパティにAPIキーを設定

APIキーをコードに直接書き込むのは危険です。GASのスクリプトプロパティ機能を使って安全に管理します。

1.  GASのスクリプトエディタ画面で、左側のメニューから**歯車アイコン (プロジェクトの設定)** をクリックします。
2.  「**スクリプト プロパティ**」のセクションで、**[スクリプト プロパティを追加]** をクリックします。
3.  以下の通りに設定し、**[スクリプト プロパティを保存]** をクリックします。
      * **プロパティ:** `VISION_API_KEY`
      * **値:** 先ほどコピーしたAPIキー

-----

## 3\. ウェブアプリとしてデプロイ

スクリプトを外部から呼び出せるように、ウェブアプリとしてデプロイします。

1.  スクリプトエディタの右上にある **[デプロイ]** ボタンをクリックし、**[新しいデプロイ]** を選択します。
2.  種類の選択で**歯車アイコン**をクリックし、**[ウェブアプリ]** を選択します。
3.  以下の通り設定します。
      * **説明:** (任意) Vision API OCR Web App など
      * **次のユーザーとして実行:** **自分**
      * **アクセスできるユーザー:** **全員**
4.  **[デプロイ]** をクリックします。
5.  初回デプロイ時には「承認が必要です」というポップアップが表示されるので、指示に従ってアカウントへのアクセスを許可してください。
6.  デプロイが完了すると「**ウェブアプリのURL**」が表示されるので、このURLをコピーします。これがAPIのエンドポイントになります。

-----

## 4\. 使い方 (curlでのテスト例)

デプロイしたウェブアプリをテストするには、`curl` コマンドや他のAPIテストツール(Postmanなど)が使用できます。

まず、テストしたい画像をBase64形式にエンコードします。macOSやLinuxでは以下のコマンドでエンコードできます。

```bash
base64 -i path/to/your/image.png -o image.txt
```

次に、`curl` を使ってPOSTリクエストを送信します。

```bash
# ウェブアプリのURLをあなたのものに置き換えてください
WEB_APP_URL="ここにデプロイしたウェブアプリのURLを貼り付け"

# Base64エンコードされた画像データをファイルから読み込み、JSONを作成して送信
curl -L -X POST "${WEB_APP_URL}" \
-H "Content-Type: application/json" \
-d '{ "base64Image": "'"$(cat image.txt)"'" }'
```

### 成功時のレスポンス例

```json
{
  "text": "これはテスト用の\n画像です。\nGoogle Cloud Vision\n",
  "rawResponse": {
    "responses": [
      {
        "fullTextAnnotation": {
          "pages": [
            // ... Vision APIからの詳細なレスポンスデータ ...
          ],
          "text": "これはテスト用の\n画像です。\nGoogle Cloud Vision\n"
        },
        "textAnnotations": [
            // ... 各単語の位置情報など ...
        ]
      }
    ]
  }
}
```

App Inventorアプリ

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

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

デザイン編集

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

AI,API,gemini,Google Apps Script,クラウド,バイブコーディング,中級,生成AI

このチュートリアルでは、Google Cloud Vision APIを使って光学文字認識を行うアプリを作ります。

光学文字認識(OCR)アプリではウェブビューでJavaScriptのTesseract.jsを使って光学文字認識を行いましたが、日本語の認識精度があまりよくありませんでした。Google Cloud Vision APIを使うとはるかに精度の良い光学文字認識が行えます。

Google Cloud Vision APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いています。バイブコーディングです。

Google Cloud Vision API1月1,000回まで無料ですが、それ以降は1,000回 あたり$1.50課金されます

Google Apps Scriptの設定

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

Gemini 2.5 Pro を使って Google Apps Script を書く

無料のアカウントでもこちらからGemini 2.5 Proが使えます。

プロンプト

Google Apps ScriptでVision APIを使ってOCRするウェブアプリを作りたい。JSONでBase64画像をPOSTで受け取り、返すJSONには、認識したテキスト全文と、Vision APIからの生のレスポンスデータの両方を含める。Vision APIのAPIキーはスクリプトプロパティから読み込む。

返答

Google Apps Scriptだけではなく、Vision APIの設定からウェブアプリとしてのデプロイ方法、使い方まで詳しく教えてくれます。わからないところはGeminiに聞けば詳しく説明してくれます。たとえばこんな感じに。「コードをもっと詳しく説明して」

はい、承知いたしました。Google Apps Script (GAS) を使用して、Base64エンコードされた画像をJSON形式で受け取り、Google Cloud Vision APIでOCR処理を行うウェブアプリの作成方法を解説します。

完成したウェブアプリは、以下の仕様を満たします。

  * HTTP POSTリクエストで動作します。
  * `{"base64Image": "..."}` という形式のJSONを受け取ります。
  * Vision APIのAPIキーは、コードに直接書き込まず、安全なスクリプトプロパティから読み込みます。
  * 処理結果として、認識したテキスト全体 (`text`) と、Vision APIからの生のレスポンス (`rawResponse`) を含むJSONを返します。

-----

## 1\. Google Apps Script コード

まず、GASプロジェクトに記述するコードです。以下のコードをスクリプトエディタにコピー&ペーストしてください。

```javascript
/**
 * POSTリクエストを受け取り、Vision APIでOCRを実行するメイン関数
 * @param {Object} e - GASがPOSTリクエスト時に自動的に渡すイベントオブジェクト
 * @returns {ContentService.TextOutput} - JSON形式のレスポンス
 */
function doPost(e) {
  try {
    // 1. リクエストボディからBase64画像データを取得
    if (!e.postData || !e.postData.contents) {
      return createJsonResponse({ error: 'Invalid request body.' }, 400);
    }
    const postData = JSON.parse(e.postData.contents);
    const base64Image = postData.base64Image;

    if (!base64Image) {
      return createJsonResponse({ error: 'The "base64Image" key is missing in the JSON payload.' }, 400);
    }

    // 2. スクリプトプロパティからAPIキーを読み込む
    const apiKey = PropertiesService.getScriptProperties().getProperty('VISION_API_KEY');
    if (!apiKey) {
      return createJsonResponse({ error: 'Vision API key is not set in Script Properties.' }, 500);
    }

    // 3. Vision APIへのリクエストを作成
    const visionApiUrl = `https://vision.googleapis.com/v1/images:annotate?key=${apiKey}`;
    const payload = {
      requests: [
        {
          image: {
            content: base64Image,
          },
          features: [
            {
              // 高精度なOCRのためにDOCUMENT_TEXT_DETECTIONを推奨
              type: 'DOCUMENT_TEXT_DETECTION', 
            },
          ],
        },
      ],
    };

    const options = {
      method: 'post',
      contentType: 'application/json',
      payload: JSON.stringify(payload),
      muteHttpExceptions: true, // APIからのエラーレスポンスを正しく受け取るため
    };

    // 4. Vision APIを呼び出し
    const response = UrlFetchApp.fetch(visionApiUrl, options);
    const responseCode = response.getResponseCode();
    const visionResponse = JSON.parse(response.getContentText());

    // Vision APIからのエラーをハンドリング
    if (responseCode !== 200) {
      return createJsonResponse({
        error: 'Vision API returned an error.',
        details: visionResponse,
      }, responseCode);
    }
    
    // 5. レスポンスからテキスト全体を抽出
    // ?. (オプショナルチェイニング) を使い、プロパティが存在しない場合でもエラーを防ぐ
    const fullText = visionResponse.responses[0]?.fullTextAnnotation?.text || '';

    // 6. 成功レスポンスを生成
    const result = {
      text: fullText,
      rawResponse: visionResponse,
    };

    return createJsonResponse(result, 200);

  } catch (error) {
    // JSONのパース失敗など、その他の予期せぬエラーをハンドリング
    return createJsonResponse({
      error: 'An unexpected error occurred.',
      details: error.message,
    }, 500);
  }
}

/**
 * JSON形式のレスポンスを生成するヘルパー関数
 * @param {Object} data - レスポンスとして返すオブジェクト
 * @param {number} statusCode - HTTPステータスコード (GASでは直接設定不可だが、ログ等のため)
 * @returns {ContentService.TextOutput}
 */
function createJsonResponse(data, statusCode) {
  // GASではHTTPステータスコードを直接設定できないため、
  // 返却するJSONデータにエラー情報を含めることでクライアント側で処理を分岐させます。
  console.log(`Response (Status ${statusCode}): ${JSON.stringify(data)}`);
  return ContentService.createTextOutput(JSON.stringify(data))
    .setMimeType(ContentService.MimeType.JSON);
}
```

-----

## 2\. セットアップ手順

コードを動作させるには、いくつかの準備が必要です。

### ステップ1: Cloud Vision APIの有効化とAPIキーの取得

1.  **Google Cloudプロジェクト**に移動し、Vision APIを使用したいプロジェクトを選択または新規作成します。
2.  ナビゲーションメニューから **[APIとサービス] \> [ライブラリ]** を選択します。
3.  「**Cloud Vision API**」を検索し、**[有効にする]** をクリックします。
4.  次に、**[APIとサービス] \> [認証情報]** に移動します。
5.  **[+ 認証情報を作成]** をクリックし、**[API キー]** を選択します。
6.  作成されたAPIキーをコピーしておきます。セキュリティのため、このキーには適切な制限(IPアドレス制限やAPI制限など)をかけることを強く推奨します。

### ステップ2: スクリプトプロパティにAPIキーを設定

APIキーをコードに直接書き込むのは危険です。GASのスクリプトプロパティ機能を使って安全に管理します。

1.  GASのスクリプトエディタ画面で、左側のメニューから**歯車アイコン (プロジェクトの設定)** をクリックします。
2.  「**スクリプト プロパティ**」のセクションで、**[スクリプト プロパティを追加]** をクリックします。
3.  以下の通りに設定し、**[スクリプト プロパティを保存]** をクリックします。
      * **プロパティ:** `VISION_API_KEY`
      * **値:** 先ほどコピーしたAPIキー

-----

## 3\. ウェブアプリとしてデプロイ

スクリプトを外部から呼び出せるように、ウェブアプリとしてデプロイします。

1.  スクリプトエディタの右上にある **[デプロイ]** ボタンをクリックし、**[新しいデプロイ]** を選択します。
2.  種類の選択で**歯車アイコン**をクリックし、**[ウェブアプリ]** を選択します。
3.  以下の通り設定します。
      * **説明:** (任意) Vision API OCR Web App など
      * **次のユーザーとして実行:** **自分**
      * **アクセスできるユーザー:** **全員**
4.  **[デプロイ]** をクリックします。
5.  初回デプロイ時には「承認が必要です」というポップアップが表示されるので、指示に従ってアカウントへのアクセスを許可してください。
6.  デプロイが完了すると「**ウェブアプリのURL**」が表示されるので、このURLをコピーします。これがAPIのエンドポイントになります。

-----

## 4\. 使い方 (curlでのテスト例)

デプロイしたウェブアプリをテストするには、`curl` コマンドや他のAPIテストツール(Postmanなど)が使用できます。

まず、テストしたい画像をBase64形式にエンコードします。macOSやLinuxでは以下のコマンドでエンコードできます。

```bash
base64 -i path/to/your/image.png -o image.txt
```

次に、`curl` を使ってPOSTリクエストを送信します。

```bash
# ウェブアプリのURLをあなたのものに置き換えてください
WEB_APP_URL="ここにデプロイしたウェブアプリのURLを貼り付け"

# Base64エンコードされた画像データをファイルから読み込み、JSONを作成して送信
curl -L -X POST "${WEB_APP_URL}" \
-H "Content-Type: application/json" \
-d '{ "base64Image": "'"$(cat image.txt)"'" }'
```

### 成功時のレスポンス例

```json
{
  "text": "これはテスト用の\n画像です。\nGoogle Cloud Vision\n",
  "rawResponse": {
    "responses": [
      {
        "fullTextAnnotation": {
          "pages": [
            // ... Vision APIからの詳細なレスポンスデータ ...
          ],
          "text": "これはテスト用の\n画像です。\nGoogle Cloud Vision\n"
        },
        "textAnnotations": [
            // ... 各単語の位置情報など ...
        ]
      }
    ]
  }
}
```

App Inventorアプリ

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

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

デザイン編集

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