6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ChatGPT のやり取りを共有したいときに便利な HTML ビューアを作りました

Last updated at Posted at 2025-08-25

社内の誰かに ChatGPT のやり取りを見せたいと思った事はありませんか?
しかし、以下のような問題があると思います:

  • ログイン ID を渡すのはセキュリティ的に微妙、見やすいように添削もできない。
  • 共有機能で共有した内容が「会話にアクセスできないか、見つかりません。この会話が存在する場合は、アカウントを切り替えるか、アクセスをリクエストする必要があります。」と表示される(タームに課金しているせいでしょうか?ワークスペースが違うせいか取引先に共有できません。(泣)
  • チャットをこまめに削除する人は、共有機能で共有したチャットのタイトルに印などで覚えておく工夫が必要であり、消すのにも躊躇する
  • テキストファイルだと読みにくい
  • PDF を作るのは手間

そこで、ChatGPT のやり取りを読みやすいチャット形式で表示できる HTML を作成しました。
ブラウザで開くだけで使えるので、共有したい方はぜひご活用ください。

👉 完成サンプルはこちら(作成した html ファイルをブラウザで開くとこのようになります)


実際のコード

実際のコードは下記の内容です。
作り方は次のセクションで紹介します。
このサイト を使います。

コードを見る
〇〇についての ChatGPT のやり取りの共有です。ブラウザで開いて下さい.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ChatGPTログ抽出(「あなた:」で分割 & 検索)</title>
<style>
  :root{
    --bg:#f6f7f9;
    --card:#ffffff;
    --gray:#e5e7eb;
    --gray-text:#374151;
    --green:#d1fae5;
    --green-text:#065f46;
  }
  body{margin:0;background:var(--bg);font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,"Apple Color Emoji","Segoe UI Emoji";}
  .wrap{max-width:1100px;margin:24px auto;padding:0 16px;transition:max-width .2s ease;}
  .title{font-size:20px;font-weight:700;margin:8px 0 16px;}
  .grid{display:grid;gap:16px;grid-template-columns:1fr;transition:grid-template-columns .2s ease;}
  @media (min-width:1024px){ .grid{grid-template-columns:1.2fr .8fr} }
  .card{background:var(--card);border-radius:16px;box-shadow:0 1px 2px rgba(0,0,0,.06);padding:16px}

  textarea, input[type="text"]{
    width:100%;box-sizing:border-box;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
    border:1px solid #d1d5db;border-radius:12px;padding:10px;background:#fff;outline:none
  }
  textarea{min-height:320px;resize:vertical}
  .controls{display:flex;gap:8px;align-items:center;margin-top:8px;flex-wrap:wrap}
  .btn{
    border:0;border-radius:12px;padding:10px 14px;background:#111827;color:#fff;cursor:pointer;font-weight:600
  }
  .btn.small{padding:6px 10px;line-height:1}
  .btn:disabled{opacity:.6;cursor:not-allowed}
  .muted{color:#6b7280;font-size:12px}

  .chat{display:flex;flex-direction:column;gap:10px}
  .bubble{
    /* 通常時の吹き出し幅(狭め) */
    width: min(320px, calc(100% - 32px));
    padding:10px;
    border-radius:12px;
    word-wrap:break-word;
    white-space:pre-wrap;
    transition: width .2s ease;
  }
  .user{align-self:flex-end;background:var(--green);color:var(--green-text);border-top-right-radius:6px}
  .assistant{align-self:flex-start;background:var(--gray);color:var(--gray-text);border-top-left-radius:6px}
  .role{display:block;font-size:12px;font-weight:700;margin-bottom:4px;opacity:.8}
  .kpi{display:flex;gap:12px;align-items:center;margin:8px 0 0;font-size:12px;color:#6b7280}
  .kpi-right{display:flex;gap:8px;align-items:center}
  .empty{padding:12px;border:1px dashed #d1d5db;border-radius:12px;color:#6b7280;background:#fafafa}
  mark{padding:0 .15em;border-radius:4px}
  details{margin-top:8px}
  .row{display:flex;gap:8px;align-items:center}
  .row > *{flex:1}

  /* === ここから拡大(+)状態のスタイル === */
  body.expand-chat .wrap{max-width:100vw}
  body.expand-chat .grid{grid-template-columns:1fr}
  body.expand-chat .grid .left{display:none}      /* 入力パネルを隠す */
  body.expand-chat .grid .right{min-height:calc(100vh - 120px)} /* 見やすく */
  /* 拡大時は吹き出しもワイドに */
  body.expand-chat .bubble{
    width: min(480px, calc(100% - 32px));
  }
  /* (吹き出しは固定幅なので横には広がりません。行が増えるだけです) */
</style>
</head>
<body>
  <div class="wrap">
    <div class="title">ChatGPTログ抽出ツール(あなた/ChatGPT 形式表示)</div>

    <div class="grid">
      <!-- 左:入力 -->
      <section class="card left">
        <label for="source"><b>① チャット履歴をここに貼り付け</b>(Ctrl+A → Ctrl+C した内容をそのまま)</label>
        <textarea id="source" placeholder="例:
あなた: こんにちは!この前の件ですが…
ChatGPT: こんにちは!もちろん、続きは…
あなた: では○○を△△にして…
ChatGPT: 承知しました。手順は……"></textarea>

        <div class="controls">
          <div class="row" style="flex:1 1 auto">
            <input id="q" type="text" placeholder="② 検索語(この語を含むやり取りだけを抽出)">
          </div>
          <button id="run" class="btn">③ 実行</button>
          <button id="clear" class="btn" style="background:#6b7280">クリア</button>
        </div>
        <details>
          <summary>使い方メモ</summary>
          <div class="muted">
            ・貼り付けたテキストを <b>「あなた:」</b> を境に分割します。各「あなた:」に続く <b>「ChatGPT:」</b> を1セットとして抽出します。<br>
            ・検索語は <b>あなた と ChatGPT の両方</b>の本文に対して部分一致で判定します(大文字小文字は区別)。<br>
            ・一致箇所は <mark>ハイライト</mark> 表示します。
          </div>
        </details>
      </section>

      <!-- 右:出力 -->
      <section class="card right">
        <div style="display:flex;justify-content:space-between;align-items:center">
          <b>結果</b>
          <div class="kpi-right">
            <button id="toggleExpand" class="btn small" aria-pressed="false" title="チャット欄を広げる"></button>
            <div class="kpi"><span id="count">0 件</span></div>
          </div>
        </div>
        <div id="out" class="chat" style="margin-top:8px">
          <div class="empty">ここに抽出結果が表示されます。</div>
        </div>
      </section>
    </div>
  </div>

<script>
(function(){
  const $ = (sel)=>document.querySelector(sel);
  const source = $('#source');
  const q = $('#q');
  const run = $('#run');
  const clearBtn = $('#clear');
  const out = $('#out');
  const count = $('#count');
  const toggleBtn = $('#toggleExpand');

  function escapeHTML(s){
    return s.replace(/[&<>"']/g, c=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[c]));
  }
  function escapeRegExp(s){ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }

  /* 改良版パーサ(末尾のやり取りも取得) */
  function parsePairs(text){
    const t = text.replace(/\r\n?/g, '\n');
    const youRe = /^あなた:\s*/gm;
    const youStarts = [];
    let m;
    while((m = youRe.exec(t)) !== null){
      youStarts.push({ index: m.index, len: m[0].length });
    }
    const pairs = [];
    for(let i = 0; i < youStarts.length; i++){
      const { index, len } = youStarts[i];
      const blockStart = index + len;
      const blockEnd   = (i + 1 < youStarts.length) ? youStarts[i+1].index : t.length;
      const block = t.slice(blockStart, blockEnd);
      const gptMatch = block.match(/^ChatGPT:\s*/m);
      if(!gptMatch) continue;
      const gptIndex = block.search(/^ChatGPT:\s*/m);
      const user = block.slice(0, gptIndex).trim();
      const assistant = block.slice(gptIndex + gptMatch[0].length).trim();
      if(user.length || assistant.length) pairs.push({ user, assistant });
    }
    return pairs;
  }

  function highlight(text, needle){
    if(!needle) return escapeHTML(text);
    try{
      const re = new RegExp(escapeRegExp(needle), 'g');
      return escapeHTML(text).replace(re, m=>`<mark>${escapeHTML(m)}</mark>`);
    }catch(e){
      return escapeHTML(text);
    }
  }

  function render(results, needle){
    out.innerHTML = '';
    if(results.length === 0){
      out.innerHTML = `<div class="empty">一致するやり取りはありませんでした。</div>`;
      count.textContent = '0 件';
      return;
    }
    const frag = document.createDocumentFragment();
    for(const {user, assistant} of results){
      const u = document.createElement('div');
      u.className = 'bubble user';
      u.innerHTML = `<span class="role">あなた:</span>${highlight(user, needle)}`;
      const a = document.createElement('div');
      a.className = 'bubble assistant';
      a.innerHTML = `<span class="role">ChatGPT:</span>${highlight(assistant, needle)}`;
      frag.appendChild(u);
      frag.appendChild(a);
    }
    out.appendChild(frag);
    count.textContent = `${results.length} 件`;
  }

  function runSearch(){
    const text = source.value || '';
    const needle = q.value || '';
    const pairs = parsePairs(text);
    const hits = !needle ? pairs : pairs.filter(p =>
      p.user.includes(needle) || p.assistant.includes(needle)
    );
    render(hits, needle);
  }

  run.addEventListener('click', runSearch);
  q.addEventListener('keydown', (e)=>{ if(e.key === 'Enter'){ runSearch(); }});
  clearBtn.addEventListener('click', ()=>{
    q.value = '';
    runSearch();
    q.focus();
  });

  /* +/− でチャット欄拡大縮小 */
  toggleBtn.addEventListener('click', ()=>{
    const expanded = document.body.classList.toggle('expand-chat');
    toggleBtn.textContent = expanded ? '' : '';
    toggleBtn.title = expanded ? 'チャット欄を元に戻す' : 'チャット欄を広げる';
    toggleBtn.setAttribute('aria-pressed', expanded ? 'true' : 'false');
  });

  /* デモ用データ */
  source.value =
`あなた: ここにやり取りを貼り付けて下さい。
ChatGPT: 開くとやり取りをチャット形式で見る事ができます。
あなた: ChatGPT のチャット欄で Ctrl + A で全選択して Ctrl + C でコピーする事で、やり取りを簡単にコピーできます。
ChatGPT: あなたがする事は html ファイルをこの内容で作成して、コピーした内容を貼り付けて保存するだけです。
あなた: 見せたくない内容や不要な文は手動で削除して下さい。
ChatGPT: 見る人が見やすくなります。`;
runSearch();
const expanded = document.body.classList.toggle('expand-chat');
    toggleBtn.textContent = expanded ? '' : '';
    toggleBtn.title = expanded ? 'チャット欄を元に戻す' : 'チャット欄を広げる';
    toggleBtn.setAttribute('aria-pressed', expanded ? 'true' : 'false');
})();
</script>
</body>
</html>

使い方

  1. ChatGPT で見せたい会話を Ctrl + A → Ctrl + C でコピー
  2. このサイト にアクセス
  3. コピーした内容をテキストボックスに貼り付ける
  4. index.html をダウンロード ボタンを押す
  5. ダウンロードした html を共有する。見る際は html ファイルをブラウザで開く事で、あなた / ChatGPT 形式 で見やすく表示されます

右上の「ー」を押す事で検索機能もあるので、特定のキーワードを含むやり取りだけを抽出できます。


まとめ

  • この HTML を渡せば、相手は ブラウザで開くだけ でやり取りを閲覧できます
  • バックアップ用途にも便利なので、履歴を削除する前に保存しておくのがおすすめです

ぜひ試してみてください。
閲覧ありがとうございました!


追記

この記事は ChatGPT で添削しています。
生成 AI による添削が苦手な方は申し訳ありません。

6
5
3

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
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?