0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Google Apps Scriptとブックマークレットで、ChatGPTとの会話をチームで共有する

Last updated at Posted at 2023-06-10

メンバーがChatGPTをどう使いこなしているのか、チームで共有するのに便利なツールをご紹介します。Google Apps Scriptとブックマークレットを活用します。

  • メンバーごとにGoogleドキュメントを自動生成して、ChatGPTとの会話を追記していきます。Googleドキュメントの一覧は、Googleスプレッドシートに表示します。
  • ChatGPTのウェブ画面 https://chat.openai.com 上でブックマークレットを起動するだけで追記できます。Markdown形式で記録します。以下を流用しています。

事前準備が必要です。誰か一人がApps Scriptでウェブアプリを作成し、利用者は各自でブラウザーにブックマークレットを設定して使います。

事前準備

Google Apps Script

誰か一人だけ作業すればOKです。

  1. Google Drive上に、会話履歴を保存するフォルダーを作成し、フォルダーのURLをメモしておきます。
  2. Googleスプレッドシートを作成し、Doc IDシートを作ります。
  3. スプレッドシートの拡張機能から、Apps Scriptを起動し、以下の2つのコードコード.gsIndex.htmlを記述します。コード.gsには、上記でメモしたフォルダーのURLを記載してください。
  4. Apps Scriptのデプロイで、ウェブアプリとして公開します。ウェブアプリのURLをメモしておきます。
コード.gs
const DOC_ID_SHEET_NAME = 'Doc ID';
const FOLDER_URL = 'Google Drive上に作った会話履歴を保存するフォルダーのURLをここに記載します';

function getFolderId_(url) {
  const regex = /https:\/\/drive\.google\.com\/drive\/folders\/([^\/]+)/;
  const match = url.match(regex);
  return match ? match[1] : null;
}

function findDocByEmail(email) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(DOC_ID_SHEET_NAME);
  const values = sheet.getDataRange().getValues();

  const emailColumnIndex = 0;
  const docIdColumnIndex = 1;

  const foundRow = values.find(row => row[emailColumnIndex] === email);
  if (foundRow) return DriveApp.getFileById(foundRow[docIdColumnIndex]);

  const folderId = getFolderId_(FOLDER_URL);
  const newDoc = DocumentApp.create(email);
  const newDocId = newDoc.getId();

  const file = DriveApp.getFileById(newDocId);
  const folder = DriveApp.getFolderById(folderId);
  file.moveTo(folder);

  sheet.appendRow([email, newDocId]);
  console.log('new docId', email, newDocId);
  return newDoc;
}

function getDiff(text, lastParagraph) {
  const i = text.indexOf(lastParagraph);
  if (i === -1) return text.trim();
  return text.slice(i + lastParagraph.length).trim();
}

function appendTitleAndText(docId, title, text) {
  const doc = DocumentApp.openById(docId);
  const body = doc.getBody();
  const paragraphs = body.getParagraphs();
  const lastParagraph = paragraphs[paragraphs.length - 1].getText().replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();

  // Replace multiple types of line breaks with '\n'
  text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
  
  const diff = getDiff(text, lastParagraph);
  if (!diff) return;
  
  const dateString = Utilities.formatDate(new Date(), 'JST', 'yyyy/MM/dd');
  const header = body.appendParagraph(`# ${dateString} ${title}`);
  header.setHeading(DocumentApp.ParagraphHeading.HEADING1);

  body.appendParagraph(diff);
}

function createResponse_(obj) {
  const output = ContentService.createTextOutput(JSON.stringify(obj));
  output.setMimeType(ContentService.MimeType.JSON);
  return output;
}

function doPost(e) {
  const params = JSON.parse(e.postData.contents);
  const { title, text } = params;

  const email = Session.getActiveUser().getEmail();
  const docId = findDocByEmail(email).getId();

  if (!docId) {
    return createResponse_({ 'result': 'error', 'message': 'No document found for this user.' });
  }

  appendTitleAndText(docId, title, text);
  return createResponse_({ 'result': 'success', 'message': 'Successfully appended data.' });
}

function doGet() {
  const email = Session.getActiveUser().getEmail();
  const docUrl = findDocByEmail(email).getUrl();
  const template = HtmlService.createTemplateFromFile('Index');
  template.docUrl = docUrl;
  const output = template.evaluate();
  return output.setTitle('ChatGPT Log');
}

Index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <a href="<?= docUrl ?>" target="_blank">Your ChatGPT log</a>
  </body>
</html>

ブックマークレット

利用者がそれぞれ作業します。ウェブアプリのURLが必要です。

  1. 以下のブックマークレットのコードを、ブラウザーのブックマークレットとして保存します。上記でメモしたウェブアプリのURLを記載してください。
bookmarklet
javascript:(function() {
  const URL = 'Apps Scriptで作ったウェブアプリのURLをここに記載します';
  function post(title, text) {
    fetch(URL, {
      method: 'post',
      mode: 'no-cors',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({title, text})
    })
    .then(res => console.log(res))
    .catch(err => console.error(err));
  }
  if (!document.URL.startsWith('https://chat.openai.com')) {
    alert('Please use on https://chat.openai.com');
    return;
  }
  const sanitize = html => {
    return String(html).replace(/&/g,'&amp;')
      .replace(/</g,'&lt;')
      .replace(/>/g,'&gt;')
      .replace(/"/g,'&quot;');
  };
  const table = element => {
    const rows = [...element.rows].map(row => [...row.cells].map(cell => cell.innerText));
    const header = rows.shift();
    const separator = header.map(() => '---');
    const body = rows.map(row => `| ${row.join(' | ')} |`);
    return `| ${header.join(' | ')} |\n|${separator.join('|')}|\n${body.join('\n')}`;
  };
  const escapeTags = text => {
    return String(text).replace(/<(\/?[a-zA-Z]+)>/g, '`<$1>`');
  };
 const content = element => {
    const tag = element.tagName;
    if (tag === 'OL') return [...element.querySelectorAll('li')].map((li, i) => `${i+1}. ${li.innerText}`).join('\n');
    if (tag === 'UL') return [...element.querySelectorAll('li')].map(li => `- ${li.innerText}`).join('\n');
    if (tag === 'PRE') {
      const lang = element.querySelector('span').innerText;
      return '```' + lang + '\n' + element.querySelector('code').innerText + '```';
    }
    if (tag === 'TABLE') return table(element);
    return escapeTags(element.innerText);
  };
  const talks = [...document.querySelectorAll('.text-base')];
  const title = talks.length % 2 === 0 ? '' : sanitize(`# ${talks.shift().innerText}\n\n`);
  const text = talks.map((talk, i) => {
    const who = i % 2 === 0 ? 'あなた' : 'ChatGPT';
    const elements = talk.querySelectorAll('p,ol,ul,pre,table');
    const c = elements.length === 0 ? talk.innerText : [...elements].map(e => content(e)).join('\n\n');
    return `## ${who}:\n${c}`;
  }).join('\n\n');
  post(title, text);
  const win = window.open(URL, 'target', 'width=200,height=150'); 
})();
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?