TEXTBOOK SECTION / AI LEARNING

自動生成結果をGoogle Docsに書き出す

AI活用概論の「GAS × 無料AI APIでつくる自動コンテンツ生成アプリ入門」より、自動生成結果をGoogle Docsに書き出すを解説。生成AI、AI活用、DX、業務改善を実践しながら学べるオンライン教材です。

8GAS × 無料AI APIでつくる自動コンテンツ生成アプリ入門概論 / AI活用 / ChatGPT / Gemini / Claude / 基礎から学ぶ

OVERVIEW

この節で学べること

概要を表示する
項目内容
教材名AI活用概論
GAS × 無料AI APIでつくる自動コンテンツ生成アプリ入門
自動生成結果をGoogle Docsに書き出す
カテゴリ概論 / AI活用 / ChatGPT / Gemini / Claude / 基礎から学ぶ
学習内容生成AI、AI活用、DX、業務改善を実践しながら理解するための教材です。

TABLE OF CONTENTS

目次

CONTENT

ここから

生成した企画書をドキュメント化して提出しやすくする

この教材では、Google Apps ScriptのWebアプリで、AIが作った文章をGoogle Docsに書き出す仕組みを作ります。

前回までは、企画書、画像プロンプト、動画台本、LP構成をWeb画面に表示し、スプレッドシートに保存してきました。

今回は、その結果を1つのGoogleドキュメントにまとめます。

企画情報を入力する
↓
AIが提出用まとめを作る
↓
Google Docsを自動作成する
↓
提出しやすいURLができる

AIが作った文章は、画面に出るだけでは少しもったいないです。

Google Docsに書き出せるようになると、提出物として見せやすくなります。


このページで完成するもの

今回作るのは、次のようなアプリです。

項目内容
入力企画タイトル、ターゲット、企画書、画像案、動画案、LP構成
AI生成提出用のまとめ文章
Docs作成Google Docsに自動書き出し
保存スプレッドシートに履歴保存
出力Google DocsのURLを表示

今回のゴールはこれです。

AIで作った内容を、
提出できるドキュメントに変える

1. なぜGoogle Docsに書き出すのか

Web画面に表示されたAIの回答は、その場では便利です。

でも、提出するときには少し困ります。

画面を閉じると見つけにくい
コピーし忘れることがある
先生に共有しにくい
あとから見返しにくい

そこで、Google Docsにまとめます。

Google Docsにする
↓
URLで提出できる
↓
後から編集できる
↓
発表にも使いやすい

AIの結果を「その場の答え」で終わらせず、作品として残すための回です。


2. 今回作るファイル

今回も、使うファイルは2つだけです。

Code.gs
index.html
ファイル役割
Code.gsGemini APIを呼び出し、Google Docsを作成する
index.html入力画面と結果表示を作る

3. 事前準備

Googleスプレッドシートを作ります。

https://sheets.google.com/

ファイル名は次にします。

AI提出ドキュメントメーカー

スプレッドシートからApps Scriptを開きます。

拡張機能
↓
Apps Script

プロジェクト名も、次にします。

AI提出ドキュメントメーカー

4. APIキーを確認する

前回と同じ GEMINI_API_KEY を使います。

Apps Scriptの左側にある歯車アイコンを開きます。

プロジェクトの設定
↓
スクリプト プロパティ

次の名前で保存されていればOKです。

プロパティ
GEMINI_API_KEY自分のGemini APIキー

APIキーは人に見せないでください。

コードに直接書かず、スクリプトプロパティに保存します。


5. Code.gsを貼り付ける

Code.gs の中身をすべて消して、次のコードを貼り付けます。

const SHEET_NAME = 'Docs書き出し履歴';
const MODEL_NAME = 'gemini-2.5-flash';

function doGet() {
  setupSheet_();

  return HtmlService
    .createHtmlOutputFromFile('index')
    .setTitle('AI提出ドキュメントメーカー')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

function createSubmissionDocument(formData) {
  validateFormData_(formData);
  setupSheet_();

  const prompt = buildSubmissionPrompt_(formData);
  const summaryText = callGemini_(prompt);
  const docInfo = createGoogleDoc_(formData, summaryText);

  saveHistory_(formData, summaryText, docInfo.url);

  return {
    ok: true,
    text: summaryText,
    docUrl: docInfo.url,
    docTitle: docInfo.title,
    message: 'Google Docsを作成しました。'
  };
}

function buildSubmissionPrompt_(formData) {
  return `
あなたは、学生の提出資料を整えるやさしい編集者です。

次の情報をもとに、授業で提出しやすいドキュメント用の文章を作ってください。

【企画タイトル】
${formData.title}

【ターゲット】
${formData.target}

【企画書】
${formData.plan}

【画像プロンプト・画像案】
${formData.imagePrompt || '未入力'}

【15秒動画台本】
${formData.videoScript || '未入力'}

【LP構成】
${formData.lpPlan || '未入力'}

【今日できるようになったこと】
${formData.reflection || '未入力'}

以下の形式で出してください。

1. 企画タイトル
2. ターゲット
3. 企画のコンセプト
4. 画像制作の方針
5. 15秒動画の流れ
6. LP構成の概要
7. 今日できるようになったこと
8. 発表で使える一言

条件:
・提出用として読みやすく整える
・難しい言葉を使わない
・各項目は短めにする
・学生が自分の成果として説明しやすい文章にする
・できたことを前向きに表現する
・未入力の項目は、無理に作りすぎず自然に補う
`;
}

function callGemini_(prompt) {
  const apiKey = PropertiesService
    .getScriptProperties()
    .getProperty('GEMINI_API_KEY');

  if (!apiKey) {
    throw new Error('GEMINI_API_KEY が設定されていません。');
  }

  const url =
    'https://generativelanguage.googleapis.com/v1beta/models/' +
    MODEL_NAME +
    ':generateContent?key=' +
    encodeURIComponent(apiKey);

  const payload = {
    contents: [
      {
        role: 'user',
        parts: [{ text: prompt }]
      }
    ],
    generationConfig: {
      temperature: 0.6,
      maxOutputTokens: 1600
    }
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  const response = UrlFetchApp.fetch(url, options);
  const statusCode = response.getResponseCode();
  const responseText = response.getContentText();

  if (statusCode !== 200) {
    throw new Error('Gemini APIエラー:' + responseText);
  }

  const json = JSON.parse(responseText);
  const text = json.candidates?.[0]?.content?.parts?.[0]?.text;

  if (!text) {
    throw new Error('AIの回答を取得できませんでした。');
  }

  return text;
}

function createGoogleDoc_(formData, summaryText) {
  const now = Utilities.formatDate(
    new Date(),
    Session.getScriptTimeZone(),
    'yyyyMMdd_HHmm'
  );

  const safeTitle = sanitizeTitle_(formData.title);
  const docTitle = `提出資料_${safeTitle}_${now}`;

  const doc = DocumentApp.create(docTitle);
  const body = doc.getBody();

  body.clear();

  body.appendParagraph('AI企画制作 提出資料')
    .setHeading(DocumentApp.ParagraphHeading.TITLE);

  body.appendParagraph('企画タイトル')
    .setHeading(DocumentApp.ParagraphHeading.HEADING1);
  body.appendParagraph(formData.title);

  body.appendParagraph('ターゲット')
    .setHeading(DocumentApp.ParagraphHeading.HEADING1);
  body.appendParagraph(formData.target);

  body.appendParagraph('提出用まとめ')
    .setHeading(DocumentApp.ParagraphHeading.HEADING1);
  appendTextAsParagraphs_(body, summaryText);

  body.appendParagraph('元データ')
    .setHeading(DocumentApp.ParagraphHeading.HEADING1);

  body.appendParagraph('企画書')
    .setHeading(DocumentApp.ParagraphHeading.HEADING2);
  appendTextAsParagraphs_(body, formData.plan);

  if (formData.imagePrompt) {
    body.appendParagraph('画像プロンプト・画像案')
      .setHeading(DocumentApp.ParagraphHeading.HEADING2);
    appendTextAsParagraphs_(body, formData.imagePrompt);
  }

  if (formData.videoScript) {
    body.appendParagraph('15秒動画台本')
      .setHeading(DocumentApp.ParagraphHeading.HEADING2);
    appendTextAsParagraphs_(body, formData.videoScript);
  }

  if (formData.lpPlan) {
    body.appendParagraph('LP構成')
      .setHeading(DocumentApp.ParagraphHeading.HEADING2);
    appendTextAsParagraphs_(body, formData.lpPlan);
  }

  if (formData.reflection) {
    body.appendParagraph('振り返り')
      .setHeading(DocumentApp.ParagraphHeading.HEADING2);
    appendTextAsParagraphs_(body, formData.reflection);
  }

  doc.saveAndClose();

  return {
    title: docTitle,
    url: doc.getUrl()
  };
}

function appendTextAsParagraphs_(body, text) {
  const lines = String(text || '')
    .split('\n')
    .map((line) => line.trim())
    .filter((line) => line !== '');

  if (lines.length === 0) {
    body.appendParagraph('未入力');
    return;
  }

  lines.forEach((line) => {
    body.appendParagraph(line);
  });
}

function saveHistory_(formData, summaryText, docUrl) {
  const sheet = SpreadsheetApp
    .getActiveSpreadsheet()
    .getSheetByName(SHEET_NAME);

  sheet.appendRow([
    new Date(),
    formData.title,
    formData.target,
    formData.plan,
    formData.imagePrompt || '',
    formData.videoScript || '',
    formData.lpPlan || '',
    formData.reflection || '',
    summaryText,
    docUrl
  ]);
}

function getRecentDocuments() {
  setupSheet_();

  const sheet = SpreadsheetApp
    .getActiveSpreadsheet()
    .getSheetByName(SHEET_NAME);

  const lastRow = sheet.getLastRow();

  if (lastRow <= 1) {
    return [];
  }

  const values = sheet.getRange(2, 1, lastRow - 1, 10).getValues();

  return values
    .map((row) => {
      return {
        createdAt: formatDate_(row[0]),
        title: row[1],
        target: row[2],
        docUrl: row[9]
      };
    })
    .reverse()
    .slice(0, 5);
}

function setupSheet_() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName(SHEET_NAME);

  if (!sheet) {
    sheet = ss.insertSheet(SHEET_NAME);
  }

  if (sheet.getLastRow() === 0) {
    sheet.appendRow([
      '作成日時',
      '企画タイトル',
      'ターゲット',
      '企画書',
      '画像プロンプト',
      '動画台本',
      'LP構成',
      '振り返り',
      'AI提出用まとめ',
      'Google Docs URL'
    ]);
  }
}

function validateFormData_(formData) {
  if (!formData) {
    throw new Error('入力データがありません。');
  }

  const requiredFields = [
    ['title', '企画タイトル'],
    ['target', 'ターゲット'],
    ['plan', '企画書']
  ];

  requiredFields.forEach(([key, label]) => {
    if (!formData[key] || String(formData[key]).trim() === '') {
      throw new Error(label + 'を入力してください。');
    }
  });
}

function sanitizeTitle_(title) {
  return String(title || '企画')
    .replace(/[\\/:*?"<>|]/g, '')
    .replace(/\s+/g, '_')
    .slice(0, 40);
}

function formatDate_(date) {
  if (!date) return '';

  return Utilities.formatDate(
    new Date(date),
    Session.getScriptTimeZone(),
    'yyyy/MM/dd HH:mm'
  );
}

6. index.htmlを作る

Apps Scriptの左側にある「+」を押します。

+
↓
HTML

ファイル名は次にします。

index

index.html の中身をすべて消して、次のコードを貼り付けます。

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <base target="_top" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AI提出ドキュメントメーカー</title>

    <style>
      :root {
        --bg: #f8fafc;
        --card: #ffffff;
        --text: #111827;
        --muted: #6b7280;
        --line: #dbe3ee;
        --primary: #10b981;
        --primary-dark: #059669;
        --soft: #ecfdf5;
        --success: #16a34a;
        --danger: #dc2626;
      }

      * {
        box-sizing: border-box;
      }

      body {
        margin: 0;
        background: linear-gradient(180deg, #f0fdf4 0%, #eef2f8 100%);
        color: var(--text);
        font-family:
          system-ui,
          -apple-system,
          BlinkMacSystemFont,
          "Segoe UI",
          sans-serif;
        line-height: 1.7;
      }

      .page {
        width: min(1120px, calc(100% - 32px));
        margin: 0 auto;
        padding: 32px 0 48px;
      }

      .hero {
        padding: 28px;
        margin-bottom: 20px;
        border: 1px solid var(--line);
        border-radius: 24px;
        background: var(--card);
        box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
      }

      .badge {
        display: inline-flex;
        padding: 6px 12px;
        border-radius: 999px;
        background: var(--soft);
        color: var(--primary-dark);
        font-size: 13px;
        font-weight: 800;
      }

      h1 {
        margin: 18px 0 8px;
        font-size: clamp(28px, 5vw, 44px);
        line-height: 1.15;
        letter-spacing: -0.04em;
      }

      .lead {
        margin: 0;
        color: var(--muted);
      }

      .grid {
        display: grid;
        grid-template-columns: 0.9fr 1.1fr;
        gap: 20px;
      }

      .card {
        border: 1px solid var(--line);
        border-radius: 24px;
        background: var(--card);
        box-shadow: 0 14px 32px rgba(15, 23, 42, 0.06);
        overflow: hidden;
      }

      .card-header {
        padding: 20px 22px;
        border-bottom: 1px solid var(--line);
        background: #fbfdff;
      }

      .card-header h2 {
        margin: 0;
        font-size: 20px;
      }

      .card-header p {
        margin: 4px 0 0;
        color: var(--muted);
        font-size: 14px;
      }

      form {
        padding: 22px;
      }

      label {
        display: block;
        margin-bottom: 14px;
        font-weight: 800;
      }

      .hint {
        display: block;
        margin: 2px 0 6px;
        color: var(--muted);
        font-size: 13px;
        font-weight: 500;
      }

      input,
      textarea {
        width: 100%;
        border: 1px solid var(--line);
        border-radius: 14px;
        padding: 12px 14px;
        background: #ffffff;
        color: var(--text);
        font: inherit;
        outline: none;
      }

      textarea {
        min-height: 92px;
        resize: vertical;
      }

      textarea.large {
        min-height: 130px;
      }

      input:focus,
      textarea:focus {
        border-color: var(--primary);
        box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.13);
      }

      .actions {
        display: flex;
        flex-wrap: wrap;
        gap: 12px;
        margin-top: 20px;
      }

      button,
      .link-button {
        border: 0;
        border-radius: 14px;
        padding: 12px 18px;
        cursor: pointer;
        font: inherit;
        font-weight: 900;
        text-decoration: none;
        display: inline-flex;
        align-items: center;
        justify-content: center;
      }

      button:disabled {
        opacity: 0.6;
        cursor: not-allowed;
      }

      .primary {
        background: var(--primary);
        color: #ffffff;
      }

      .primary:hover {
        background: var(--primary-dark);
      }

      .secondary {
        background: #eef2f7;
        color: #111827;
      }

      .message {
        margin: 0 22px 22px;
        padding: 12px 14px;
        border-radius: 14px;
        display: none;
        font-weight: 800;
      }

      .message.success {
        display: block;
        background: #ecfdf5;
        color: var(--success);
      }

      .message.error {
        display: block;
        background: #fef2f2;
        color: var(--danger);
      }

      .loading {
        display: none;
        padding: 14px;
        margin: 0 22px 22px;
        border-radius: 14px;
        background: #fffbeb;
        color: #92400e;
        font-weight: 800;
      }

      .loading.show {
        display: block;
      }

      .result {
        padding: 22px;
      }

      .doc-box {
        display: none;
        padding: 16px;
        margin-bottom: 16px;
        border-radius: 18px;
        background: #ecfdf5;
        border: 1px solid #bbf7d0;
      }

      .doc-box.show {
        display: block;
      }

      .doc-title {
        margin: 0 0 8px;
        font-weight: 900;
      }

      .result-box {
        min-height: 360px;
        padding: 20px;
        border-radius: 18px;
        background: #111827;
        color: #f9fafb;
        white-space: pre-wrap;
        overflow-wrap: anywhere;
        font-size: 15px;
      }

      .result-empty {
        color: #9ca3af;
      }

      .recent {
        padding: 0 22px 22px;
      }

      .recent-item {
        padding: 14px 0;
        border-top: 1px solid var(--line);
      }

      .recent-item:first-child {
        border-top: 0;
      }

      .recent-title {
        margin: 0;
        font-weight: 900;
      }

      .recent-meta {
        margin: 2px 0 0;
        color: var(--muted);
        font-size: 13px;
      }

      .empty {
        color: var(--muted);
        font-size: 14px;
      }

      @media (max-width: 920px) {
        .grid {
          grid-template-columns: 1fr;
        }

        .hero {
          padding: 22px;
        }
      }
    </style>
  </head>

  <body>
    <main class="page">
      <section class="hero">
        <span class="badge">6-8 Google Docs書き出し</span>
        <h1>AIの結果を<br />提出資料にする</h1>
        <p class="lead">
          企画書、画像案、動画台本、LP構成をまとめて、Google Docsに書き出します。
        </p>
      </section>

      <section class="grid">
        <div class="card">
          <div class="card-header">
            <h2>提出資料の材料を入力</h2>
            <p>必須は、企画タイトル・ターゲット・企画書です。</p>
          </div>

          <form id="docForm">
            <label>
              企画タイトル
              <span class="hint">例:放課後カフェマップ</span>
              <input
                id="title"
                type="text"
                placeholder="企画タイトル"
                required
              />
            </label>

            <label>
              ターゲット
              <span class="hint">例:学校帰りに寄れる場所を探している学生</span>
              <input
                id="target"
                type="text"
                placeholder="誰に届けたいか"
                required
              />
            </label>

            <label>
              企画書
              <span class="hint">6-4で作った企画書を貼り付けます</span>
              <textarea
                id="plan"
                class="large"
                placeholder="企画書を貼り付ける"
                required
              ></textarea>
            </label>

            <label>
              画像プロンプト・画像案
              <span class="hint">6-5で作った内容を貼り付けます</span>
              <textarea
                id="imagePrompt"
                placeholder="画像プロンプトや画像案"
              ></textarea>
            </label>

            <label>
              15秒動画台本
              <span class="hint">6-6で作った内容を貼り付けます</span>
              <textarea
                id="videoScript"
                placeholder="動画台本"
              ></textarea>
            </label>

            <label>
              LP構成
              <span class="hint">6-7で作った内容を貼り付けます</span>
              <textarea
                id="lpPlan"
                placeholder="LP構成"
              ></textarea>
            </label>

            <label>
              今日できるようになったこと
              <span class="hint">例:AIで企画を提出資料にまとめられた</span>
              <textarea
                id="reflection"
                placeholder="振り返り"
              ></textarea>
            </label>

            <div class="actions">
              <button class="primary" type="submit" id="createButton">
                Google Docsを作る
              </button>
              <button class="secondary" type="button" id="sampleButton">
                サンプル入力
              </button>
              <button class="secondary" type="button" id="clearButton">
                クリア
              </button>
            </div>
          </form>

          <div id="loadingBox" class="loading">
            AIが提出用に整え、Google Docsを作成しています。少し待ってください。
          </div>

          <p id="messageBox" class="message"></p>
        </div>

        <div class="card">
          <div class="card-header">
            <h2>作成結果</h2>
            <p>DocsのURLを提出に使えます。</p>
          </div>

          <div class="result">
            <div id="docBox" class="doc-box">
              <p id="docTitle" class="doc-title">Google Docsを作成しました。</p>
              <a id="docLink" class="link-button primary" href="#" target="_blank">
                Google Docsを開く
              </a>
            </div>

            <div id="resultBox" class="result-box">
              <span class="result-empty">ここに提出用まとめが表示されます。</span>
            </div>

            <div class="actions">
              <button class="secondary" type="button" id="copyButton">
                まとめをコピー
              </button>
            </div>
          </div>

          <div class="card-header">
            <h2>最近作ったDocs</h2>
            <p>最新5件を表示します。</p>
          </div>

          <div class="recent" id="recentDocuments">
            <p class="empty">まだ履歴がありません。</p>
          </div>
        </div>
      </section>
    </main>

    <script>
      const form = document.getElementById('docForm');
      const createButton = document.getElementById('createButton');
      const sampleButton = document.getElementById('sampleButton');
      const clearButton = document.getElementById('clearButton');
      const copyButton = document.getElementById('copyButton');
      const loadingBox = document.getElementById('loadingBox');
      const messageBox = document.getElementById('messageBox');
      const resultBox = document.getElementById('resultBox');
      const docBox = document.getElementById('docBox');
      const docTitle = document.getElementById('docTitle');
      const docLink = document.getElementById('docLink');

      const fields = {
        title: document.getElementById('title'),
        target: document.getElementById('target'),
        plan: document.getElementById('plan'),
        imagePrompt: document.getElementById('imagePrompt'),
        videoScript: document.getElementById('videoScript'),
        lpPlan: document.getElementById('lpPlan'),
        reflection: document.getElementById('reflection')
      };

      form.addEventListener('submit', (event) => {
        event.preventDefault();

        const formData = getFormData();

        setLoading(true);
        showMessage('', '');
        docBox.classList.remove('show');

        google.script.run
          .withSuccessHandler((result) => {
            setLoading(false);

            resultBox.textContent = result.text;
            docTitle.textContent = result.docTitle;
            docLink.href = result.docUrl;
            docBox.classList.add('show');

            showMessage(result.message || 'Google Docsを作成しました。', 'success');
            loadRecentDocuments();
          })
          .withFailureHandler((error) => {
            setLoading(false);
            showMessage(error.message || '作成に失敗しました。', 'error');
          })
          .createSubmissionDocument(formData);
      });

      sampleButton.addEventListener('click', () => {
        fields.title.value = '放課後カフェマップ';
        fields.target.value = '学校帰りに友達と寄れる場所を探している学生';
        fields.plan.value =
          '放課後に使えるカフェや居場所を分かりやすく紹介する企画。勉強にも休憩にも使える場所を、写真や短い説明で見つけやすくする。';
        fields.imagePrompt.value =
          '夕方のカフェ、ノート、スマホ、ドリンク、柔らかい自然光、16:9、文字なし、ロゴなし。';
        fields.videoScript.value =
          '0〜3秒:放課後どこ寄る? 3〜7秒:カフェを探せる 7〜12秒:勉強も休憩もできる 12〜15秒:気になる場所を見てみよう。';
        fields.lpPlan.value =
          'ファーストビュー、企画説明、魅力3つ、画像・動画、最後の案内で構成する。';
        fields.reflection.value =
          'AIを使って、企画から提出資料までまとめられるようになった。';

        showMessage('サンプルを入力しました。自由に書き換えてください。', 'success');
      });

      clearButton.addEventListener('click', () => {
        form.reset();
        resultBox.innerHTML =
          '<span class="result-empty">ここに提出用まとめが表示されます。</span>';
        docBox.classList.remove('show');
        showMessage('', '');
      });

      copyButton.addEventListener('click', async () => {
        const text = resultBox.textContent.trim();

        if (!text || text === 'ここに提出用まとめが表示されます。') {
          showMessage('コピーする文章がありません。', 'error');
          return;
        }

        try {
          await navigator.clipboard.writeText(text);
          showMessage('提出用まとめをコピーしました。', 'success');
        } catch (error) {
          showMessage('コピーできませんでした。手動で選択してコピーしてください。', 'error');
        }
      });

      function getFormData() {
        return {
          title: fields.title.value.trim(),
          target: fields.target.value.trim(),
          plan: fields.plan.value.trim(),
          imagePrompt: fields.imagePrompt.value.trim(),
          videoScript: fields.videoScript.value.trim(),
          lpPlan: fields.lpPlan.value.trim(),
          reflection: fields.reflection.value.trim()
        };
      }

      function setLoading(isLoading) {
        createButton.disabled = isLoading;
        createButton.textContent = isLoading
          ? '作成中...'
          : 'Google Docsを作る';
        loadingBox.classList.toggle('show', isLoading);
      }

      function showMessage(text, type) {
        if (!text) {
          messageBox.textContent = '';
          messageBox.className = 'message';
          return;
        }

        messageBox.textContent = text;
        messageBox.className = 'message ' + type;
      }

      function loadRecentDocuments() {
        google.script.run
          .withSuccessHandler(renderRecentDocuments)
          .withFailureHandler(() => {
            document.getElementById('recentDocuments').innerHTML =
              '<p class="empty">履歴を読み込めませんでした。</p>';
          })
          .getRecentDocuments();
      }

      function renderRecentDocuments(docs) {
        const container = document.getElementById('recentDocuments');

        if (!docs || docs.length === 0) {
          container.innerHTML =
            '<p class="empty">まだ履歴がありません。</p>';
          return;
        }

        container.innerHTML = docs
          .map((item) => {
            return `
              <div class="recent-item">
                <p class="recent-title">${escapeHtml(item.title)}</p>
                <p class="recent-meta">
                  ${escapeHtml(item.createdAt)}${escapeHtml(item.target)}
                </p>
                <a class="link-button secondary" href="${escapeHtml(item.docUrl)}" target="_blank">
                  Docsを開く
                </a>
              </div>
            `;
          })
          .join('');
      }

      function escapeHtml(value) {
        return String(value || '')
          .replaceAll('&', '&amp;')
          .replaceAll('<', '&lt;')
          .replaceAll('>', '&gt;')
          .replaceAll('"', '&quot;')
          .replaceAll("'", '&#039;');
      }

      loadRecentDocuments();
    </script>
  </body>
</html>

7. 保存する

コードを貼り付けたら保存します。

Command + S

Windowsの場合は、

Ctrl + S

です。


8. Webアプリとして公開する

右上の「デプロイ」を押します。

デプロイ
↓
新しいデプロイ

種類は、次を選びます。

ウェブアプリ

設定は次の通りです。

項目設定
説明6-8 AI提出ドキュメントメーカー
次のユーザーとして実行自分
アクセスできるユーザー自分のみ

デプロイ後に表示されるURLを開くと、提出ドキュメントメーカーが表示されます。


9. 権限許可について

今回は、Google Docsを作成するため、初回に権限確認が出ます。

表示されたら、次の流れで進めます。

権限を確認
↓
自分のGoogleアカウントを選ぶ
↓
詳細
↓
安全ではないページに移動
↓
許可

自分で作ったGASなので、この教材では許可して進めます。

不安な場合は、先生に確認してください。


10. 動かしてみる

画面が開いたら、まず「サンプル入力」を押します。

そのあと、次のボタンを押します。

Google Docsを作る

少し待つと、右側に提出用まとめが表示されます。

さらに、Google Docsのリンクが表示されます。

Google Docsを開く

このリンクを開いて、ドキュメントが作成されていれば成功です。


11. 生成結果の見方

作成されたGoogle Docsでは、まずこの3つを確認します。

企画タイトルが入っているか
企画内容が読みやすく整理されているか
提出用として見せられる形になっているか

この回では、完璧な文章を目指さなくて大丈夫です。

大切なのは、AIで作ったものを提出できる形に整えることです。


12. うまくいかない時

Docsが作られない

権限許可ができていない可能性があります。

もう一度、Apps Script側で実行して、権限を許可してください。

エラーが出る

GEMINI_API_KEY を確認してください。

プロジェクトの設定
↓
スクリプト プロパティ
↓
GEMINI_API_KEY

Google DocsのURLが出ない

createGoogleDoc_() の中に DocumentApp.create() があるか確認します。

内容が長すぎる

buildSubmissionPrompt_() の条件に、次を追加します。

・全体を短めにまとめる
・1項目は2〜3行以内にする

13. 今日の提出物

この回の提出物は、次の3つです。

1. WebアプリURL
2. 作成されたGoogle DocsのURL
3. スプレッドシートの履歴スクリーンショット

最低限は、これでOKです。

AIの生成結果をGoogle Docsに書き出せた

14. チェックリスト

チェック内容
スプレッドシートを作った
Apps Scriptを開いた
GEMINI_API_KEYを確認した
Code.gsを貼り付けた
index.htmlを作った
index.htmlを貼り付けた
Webアプリとしてデプロイした
WebアプリURLを開いた
サンプル入力を押した
Google Docsを作るを押した
Google DocsのURLが表示された
Docsを開いて内容を確認した
スプレッドシートに履歴が保存された

まとめ

今回は、AIで作った内容をGoogle Docsに書き出すアプリを作りました。

これで、AIの結果はただの画面表示ではなくなります。

企画書
↓
画像案
↓
動画台本
↓
LP構成
↓
Google Docs

最後にDocsとして残ると、企画は提出できる資料になります。

AIは、答えを出すだけの道具ではありません。

自分の考えを整理して、誰かに届ける形にするための相棒にもなります。

ここまでできれば、次はいよいよ仕上げです。

作ったツールを使って、今日中に1つの企画を提出できる形にまとめていきます。

FAQ

よくある質問

自動生成結果をGoogle Docsに書き出すは医療関係者向けだけの内容ですか。
医療分野の例が含まれる場合もありますが、医療関係者だけに限定した内容ではありません。生成AI、AI活用、DX、業務改善、プロトタイプ開発など、一般的なAI学習の事例として読める内容です。
AI初心者でも読めますか。
はい。AIをこれから学ぶ方、数学が苦手な方、仕事でAIを使いたい方にも読み進めやすいように、教材の章と節の流れに沿って整理しています。
サムネイル画像は必ず表示されますか。
はい。教材にcoverUrlが設定されている場合はその画像を表示し、未設定の場合は代替サムネイル画像を表示します。
AI活用概論のほかの章も読めますか。
はい。教材トップから章立てを確認でき、前後の節へもページ下部のナビゲーションから移動できます。