【ChatGPT】簡単に作れるウェブアプリ:o1-miniを活用した「テキスト編集Webアプリ」をプロンプトだけで開発する【プロンプト付き】
はじめに:シンプルテキストエディタで効率的なテキスト管理を実現!
ChatGPTに搭載されている最新モデル「o1-mini」により、Webアプリを自分で作る」=アプリの民主化が可能となりました。
私たちは、作りたいアプリの種類や機能をモデルに日本語で伝えるだけで、普段使いのアプリをノーコードで作れてしまいます。
以前は、このようなWebアプリを作ってご紹介しました。
今回は、テキスト文書をブラウザで編集できる「シンプルテキストエディタ」をご紹介します。
「メモ帳やメールソフトを開くほどでも無いけど、ちょっと文章を編集したい」というとき、ありますよね。
そんなときに、ちょっと編集できて、文字数も数えてくれて、ワンクリックでテキストファイルとして保存できるアプリです。
このWebアプリケーションは、フロントエンドのみで動作し、サーバーサイドの機能を一切使用しないため、簡単に導入・利用することができます。
モダンでスタイリッシュなデザインと豊富な機能を兼ね備え、日常のテキスト編集作業を快適にサポートします。
基本のプロンプト
役割: あなたはシングルページ Web アプリケーション開発の専 ⾨家です。
指⽰: テキスト編集アプリについてのシングルペー ジ Web アプリケーションのコードを出⼒してください
条件:
• フロントエンドのみで動作する Web アプリケーションにし てください。サーバーサイドの機能は使⽤しません
• スタイリッシュでモダンなデザインにしてください
• htmlには、使⽤⽅法と注意事項を記載してください
• 出⼒内容: htmlコードの中にCSS,JavaScriptコードを含めて ください
• 出⼒形式: コードブロックに出⼒してください
機能:
コピーボタン
クリアボタン
フォント拡大縮小ボタン
記録ボタン
テキストファイルとして出力/自動ダウンロードボタン
主な機能
テキスト編集: 直感的なインターフェースでテキストの入力・編集が可能です。
コピー機能: ワンクリックでテキストをクリップボードにコピーできます。
クリア機能: テキストエリアを一括でクリアすることができます。
フォントサイズ調整: フォントサイズをプラス(拡大)とマイナス(縮小)ボタンで簡単に調整可能。
記録機能: 編集内容を記録し、後日閲覧・管理することができます。
記録の個別削除: 各記録を個別に削除することが可能。
テキストファイルとしてダウンロード: 編集したテキストを簡単にダウンロードできます。
追加機能
ダークモード: ライトモードとダークモードを切り替えます。
文字数カウント: テキストエリアに記載されている文字数を自動的に数えます。
アプリケーションのアクセス方法
オンラインで利用する
以下のURLからアクセスして、お試しいただけます:
HTMLファイルのダウンロード
オフラインで利用したい方や、自分のサーバーにアップロードしたい方のために、HTMLファイルをダウンロードできるリンクもご用意しました:
コードの提供
このアプリケーションのソースコードも公開しています。ぜひ、ご自身のプロジェクトに活用してください!
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Simple Text Editor</title>
<style>
:root {
--background-color: #ffffff;
--text-color: #333333;
--header-background: #ffffff;
--header-text-color: #333333;
--button-color: #6c9edc;
--button-hover-opacity: 0.9;
--modal-background: rgba(0,0,0,0.4);
--modal-content-background: #fefefe;
--footer-background: #f9f9f9;
--footer-text-color: #555555;
--border-color: #cccccc;
--delete-button-color: #e57373;
--record-button-color: #ba68c8;
--download-button-color: #81c784;
--view-records-button-color: #64b5f6;
--increase-font-button-color: #ffb74d;
--decrease-font-button-color: #ffb74d;
}
.dark-mode {
--background-color: #2c2c2c;
--text-color: #f0f0f0;
--header-background: #1f1f1f;
--header-text-color: #f0f0f0;
--button-color: #4a90e2;
--button-hover-opacity: 0.8;
--modal-background: rgba(0,0,0,0.7);
--modal-content-background: #3c3c3c;
--footer-background: #1f1f1f;
--footer-text-color: #cccccc;
--border-color: #555555;
--delete-button-color: #e57373;
--record-button-color: #ba68c8;
--download-button-color: #81c784;
--view-records-button-color: #64b5f6;
--increase-font-button-color: #ffb74d;
--decrease-font-button-color: #ffb74d;
}
* {
box-sizing: border-box;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
display: flex;
flex-direction: column;
height: 100vh;
transition: background-color 0.3s ease, color 0.3s ease;
}
header {
background-color: var(--header-background);
color: var(--header-text-color);
width: 100%;
padding: 10px 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
flex: 0 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.3s ease, color 0.3s ease;
}
header h1 {
margin: 0;
font-size: 24px;
display: flex;
align-items: center;
gap: 10px;
}
#charCount {
font-size: 16px;
color: #555555;
}
.container {
flex: 1 1 auto;
width: 100%;
padding: 10px 20px; /* パディングを減少 */
display: flex;
flex-direction: column;
}
textarea {
/* 縦幅を80vhに設定 */
height: 80vh; /* ビューポートの80%の高さ */
width: 100%;
padding: 10px;
font-size: 16px;
border: 1px solid var(--border-color);
border-radius: 4px;
resize: vertical; /* 垂直方向のみリサイズ可能 */
transition: font-size 0.3s ease, border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease;
overflow: auto; /* 内部スクロールを有効化 */
background-color: var(--background-color);
color: var(--text-color);
/* flexプロパティを削除して固定高さに */
}
.buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
flex: 0 0 auto;
}
.buttons button {
flex: 1;
padding: 10px;
font-size: 18px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease, opacity 0.3s ease;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--button-color);
}
.buttons button:hover {
opacity: var(--button-hover-opacity);
}
/* カスタムボタンカラー */
.buttons .download-btn { background-color: var(--download-button-color); }
.buttons .record-btn { background-color: var(--record-button-color); }
.buttons .view-records-btn { background-color: var(--view-records-button-color); }
.buttons .copy-btn { background-color: var(--button-color); }
.buttons .clear-btn { background-color: var(--delete-button-color); }
.buttons .increase-font-btn { background-color: var(--increase-font-button-color); }
.buttons .decrease-font-btn { background-color: var(--decrease-font-button-color); }
.buttons .dark-mode-toggle-btn { background-color: #757575; } /* ダークモードボタンカラー */
footer {
margin-top: 20px;
padding: 20px;
background-color: var(--footer-background);
width: 100%;
text-align: center;
font-size: 14px;
color: var(--footer-text-color);
flex: 0 0 auto;
transition: background-color 0.3s ease, color 0.3s ease;
}
footer h2 {
margin-top: 0;
}
footer ul {
list-style-type: disc;
padding-left: 20px;
text-align: left;
display: inline-block;
}
/* モーダルスタイル */
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: var(--modal-background);
transition: background-color 0.3s ease;
}
.modal-content {
background-color: var(--modal-content-background);
margin: 5% auto; /* 上下にマージン、中央配置 */
padding: 20px;
border: 1px solid #888;
width: 90%; /* 幅 */
max-height: 80%; /* 高さの最大値 */
overflow-y: auto; /* 縦スクロール */
border-radius: 8px;
transition: background-color 0.3s ease, color 0.3s ease;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
}
.record-item {
border-bottom: 1px solid #ddd;
padding: 10px 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.record-item:last-child {
border-bottom: none;
}
.record-content {
max-width: 60%;
word-wrap: break-word;
}
.action-buttons {
display: flex;
gap: 5px;
}
.action-buttons button {
padding: 5px 10px;
font-size: 14px;
border: none;
border-radius: 4px;
cursor: pointer;
color: #ffffff;
transition: opacity 0.3s ease;
}
.delete-record-btn {
background-color: var(--delete-button-color);
}
.delete-record-btn:hover {
opacity: var(--button-hover-opacity);
}
.load-record-btn {
background-color: var(--view-records-button-color);
}
.load-record-btn:hover {
opacity: var(--button-hover-opacity);
}
/* Responsive Design */
@media (max-width: 600px) {
.buttons button {
flex: 100%;
}
.modal-content {
width: 95%;
margin: 20% auto;
}
header h1 {
font-size: 20px;
}
#charCount {
font-size: 14px;
}
.buttons button {
font-size: 16px;
padding: 8px;
}
.record-content {
max-width: 70%;
}
.delete-record-btn, .load-record-btn {
padding: 4px 8px;
font-size: 12px;
}
textarea {
height: 80vh; /* スマートフォンでも十分な高さを確保 */
}
}
/* フォーカス時のスタイル */
textarea:focus {
border-color: #4a90e2;
outline: none;
box-shadow: 0 0 5px rgba(74, 144, 226, 0.5);
}
.buttons button:focus {
outline: 2px solid #4a90e2;
}
</style>
</head>
<body>
<header>
<h1>Simple Text Editor <span id="charCount">Characters: 0</span></h1>
<button class="dark-mode-toggle-btn" id="darkModeToggleBtn" aria-label="ダークモード切替">🌙 ダークモード</button>
</header>
<div class="container">
<textarea id="textArea" placeholder="ここにテキストを入力してください..."></textarea>
<div class="buttons">
<button class="download-btn" id="downloadBtn">ダウンロード</button>
<button class="record-btn" id="recordBtn">記録</button>
<button class="view-records-btn" id="viewRecordsBtn">記録を表示</button>
<button class="copy-btn" id="copyBtn">コピー</button>
<button class="clear-btn" id="clearBtn">クリア</button>
<button class="increase-font-btn" id="increaseFontBtn">+</button>
<button class="decrease-font-btn" id="decreaseFontBtn">−</button>
</div>
</div>
<!-- モーダルのHTML -->
<div id="recordsModal" class="modal">
<div class="modal-content">
<span class="close" id="closeModal">×</span>
<h2>記録一覧</h2>
<div id="recordsList">
<!-- 記録がここに表示されます -->
</div>
<button class="clear-btn" id="clearRecordsBtn" style="margin-top: 20px;">記録をクリア</button>
</div>
</div>
<footer>
<h2>使い方</h2>
<p>
このテキストエディタを使用して、テキストの編集、コピー、クリア、フォントサイズの調整、記録、およびテキストファイルとしてのダウンロードが可能です。
</p>
<p><strong>注意事項:</strong></p>
<ul>
<li>サーバーサイドの機能は使用していません。すべての操作はブラウザ上で行われます。</li>
<li>重要なデータは定期的に保存してください。クリアボタンを押すとテキストが削除されます。</li>
<li>フォントサイズの調整は読みやすさに応じて行ってください。</li>
</ul>
<p>© 2024 シンプルテキストエディタ</p>
</footer>
<script>
const copyBtn = document.getElementById('copyBtn');
const clearBtn = document.getElementById('clearBtn');
const increaseFontBtn = document.getElementById('increaseFontBtn');
const decreaseFontBtn = document.getElementById('decreaseFontBtn');
const recordBtn = document.getElementById('recordBtn');
const downloadBtn = document.getElementById('downloadBtn');
const viewRecordsBtn = document.getElementById('viewRecordsBtn');
const darkModeToggleBtn = document.getElementById('darkModeToggleBtn');
const textArea = document.getElementById('textArea');
const charCount = document.getElementById('charCount');
// モーダル関連
const recordsModal = document.getElementById('recordsModal');
const closeModal = document.getElementById('closeModal');
const recordsList = document.getElementById('recordsList');
const clearRecordsBtn = document.getElementById('clearRecordsBtn');
// ダークモード関連
const darkModeClass = 'dark-mode';
// 文字数カウント機能
function updateCharCount() {
const count = textArea.value.length;
charCount.textContent = `Characters: ${count}`;
}
// 記録をローカルストレージから取得
function getStoredRecords() {
const stored = localStorage.getItem('textEditorRecords');
return stored ? JSON.parse(stored) : [];
}
// 記録をローカルストレージに保存
function saveRecords(records) {
localStorage.setItem('textEditorRecords', JSON.stringify(records));
}
// 記録一覧を表示
function displayRecords() {
const records = getStoredRecords();
recordsList.innerHTML = '';
if (records.length === 0) {
recordsList.innerHTML = '<p>記録がありません。</p>';
return;
}
records.forEach((record, index) => {
const recordDiv = document.createElement('div');
recordDiv.className = 'record-item';
const recordContentDiv = document.createElement('div');
recordContentDiv.className = 'record-content';
const timestamp = document.createElement('p');
timestamp.innerHTML = `<strong>${index + 1}. ${record.timestamp}</strong>`;
const content = document.createElement('p');
content.textContent = record.content || '(空白)';
recordContentDiv.appendChild(timestamp);
recordContentDiv.appendChild(content);
// アクションボタンのコンテナを作成
const actionButtonsDiv = document.createElement('div');
actionButtonsDiv.className = 'action-buttons';
// 削除ボタン
const deleteBtn = document.createElement('button');
deleteBtn.className = 'delete-record-btn';
deleteBtn.textContent = '削除';
deleteBtn.addEventListener('click', () => {
deleteRecord(index);
});
// 読み込みボタン
const loadBtn = document.createElement('button');
loadBtn.className = 'load-record-btn';
loadBtn.textContent = '読み込み';
loadBtn.addEventListener('click', () => {
loadRecord(index);
});
actionButtonsDiv.appendChild(loadBtn);
actionButtonsDiv.appendChild(deleteBtn);
recordDiv.appendChild(recordContentDiv);
recordDiv.appendChild(actionButtonsDiv);
recordsList.appendChild(recordDiv);
});
}
// 個別の記録を削除
function deleteRecord(index) {
let records = getStoredRecords();
if (index >= 0 && index < records.length) {
if (confirm(`記録${index + 1}を削除しますか?`)) {
records.splice(index, 1);
saveRecords(records);
displayRecords();
alert('記録を削除しました。');
}
}
}
// 個別の記録を読み込む
function loadRecord(index) {
let records = getStoredRecords();
if (index >= 0 && index < records.length) {
if (confirm(`記録${index + 1}をテキストエリアに読み込みますか?`)) {
textArea.value = records[index].content;
updateCharCount();
// テキストエリアにフォーカスを移動
textArea.focus();
// モーダルを閉じる
recordsModal.style.display = 'none';
alert('記録を読み込みました。');
}
}
}
// ダークモードの初期設定
function initializeDarkMode() {
const darkModeEnabled = localStorage.getItem('darkMode') === 'enabled';
if (darkModeEnabled) {
document.body.classList.add(darkModeClass);
darkModeToggleBtn.textContent = '☀️ ライトモード';
} else {
document.body.classList.remove(darkModeClass);
darkModeToggleBtn.textContent = '🌙 ダークモード';
}
}
// ダークモードの切り替え
darkModeToggleBtn.addEventListener('click', () => {
document.body.classList.toggle(darkModeClass);
if (document.body.classList.contains(darkModeClass)) {
darkModeToggleBtn.textContent = '☀️ ライトモード';
localStorage.setItem('darkMode', 'enabled');
} else {
darkModeToggleBtn.textContent = '🌙 ダークモード';
localStorage.setItem('darkMode', 'disabled');
}
});
// 初期文字数カウント
updateCharCount();
// 入力時に文字数カウント
textArea.addEventListener('input', updateCharCount);
// コピー機能
copyBtn.addEventListener('click', () => {
textArea.select();
document.execCommand('copy');
alert('テキストがコピーされました。');
});
// クリア機能
clearBtn.addEventListener('click', () => {
if (confirm('本当にテキストをクリアしますか?')) {
textArea.value = '';
updateCharCount();
}
});
// フォントサイズ拡大
increaseFontBtn.addEventListener('click', () => {
let currentSize = window.getComputedStyle(textArea, null).getPropertyValue('font-size');
let newSize = parseFloat(currentSize) + 2;
textArea.style.fontSize = newSize + 'px';
});
// フォントサイズ縮小
decreaseFontBtn.addEventListener('click', () => {
let currentSize = window.getComputedStyle(textArea, null).getPropertyValue('font-size');
let newSize = parseFloat(currentSize) - 2;
if (newSize >= 12) { // 最小フォントサイズ
textArea.style.fontSize = newSize + 'px';
}
});
// 記録機能
recordBtn.addEventListener('click', () => {
const timestamp = new Date().toLocaleString();
const content = textArea.value;
let records = getStoredRecords();
records.push({ timestamp, content });
saveRecords(records);
alert(`記録しました。合計記録数: ${records.length}`);
console.log('記録一覧:', records);
});
// ダウンロード機能
downloadBtn.addEventListener('click', () => {
const text = textArea.value;
const blob = new Blob([text], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'テキストファイル.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
// モーダルを開く
viewRecordsBtn.addEventListener('click', () => {
displayRecords();
recordsModal.style.display = 'block';
});
// モーダルを閉じる
closeModal.addEventListener('click', () => {
recordsModal.style.display = 'none';
});
// モーダル外をクリックで閉じる
window.addEventListener('click', (event) => {
if (event.target == recordsModal) {
recordsModal.style.display = 'none';
}
});
// 記録をクリアする
clearRecordsBtn.addEventListener('click', () => {
if (confirm('すべての記録をクリアしますか?')) {
saveRecords([]);
displayRecords();
alert('全ての記録をクリアしました。');
}
});
// 初期ダークモード設定
initializeDarkMode();
</script>
</body>
</html>
シンプルで直感的なインターフェース
「シンプルテキストエディタ」は、シンプルでクリーンなデザインを採用しており、ユーザーが迷うことなく直感的に操作できます。複雑な設定や機能は一切なく、必要な機能だけを厳選しています。
フォントサイズの調整が容易
フォントサイズをプラス(拡大)とマイナス(縮小)のボタンで簡単に調整できるため、ユーザーは自分の好みに合わせて表示をカスタマイズできます。視認性が向上し、長時間の作業でも疲れにくくなります。
記録機能で編集履歴を管理
テキストの編集内容を記録する機能を搭載しています。後日、記録を表示することで、過去の編集内容を確認・管理することができます。個別に記録を削除することも可能で、不要なデータを整理することができます。
データの永続化とセキュリティ
記録はブラウザのローカルストレージに保存されるため、ページを再読み込みしてもデータが保持されます。また、外部へのデータ送信は一切行わないため、プライバシーが保護されます。ただし、機密性の高い情報を扱う際は、デバイス自体のセキュリティにも注意が必要です。
簡単な導入とカスタマイズ
フロントエンドのみで動作するため、サーバーの設定やバックエンドの知識がなくても簡単に導入できます。必要に応じてHTMLファイルをダウンロードし、自分のサーバーにアップロードするだけで利用可能です。また、コードが公開されているため、カスタマイズも自由自在です。
まとめ:欲しいアプリは自分で作る時代
Webアプリの民主化=「欲しいアプリは自分で作る時代」です。
このスキルを身につけておかないと、本当に勿体無いと思っています。
システム会社に依頼すると、見積り金額は現在の相場ですと100万円-200万円です。
ちなみに、ChatGPTの見積り:105万円、Feloの見積り:200万円、Claudeの見積り:481万円、です。481万円は大袈裟かもしれませんが、大手のSIerに業務委託すると確かにそのぐらいかかるかもしれません。
大まかな業務内容と、開発期間の見積りは、以下のとおりです。
ヒアリング、契約、要件定義書の作成、設計、実装、テスト、サーバー構築、デプロイ、納品、検収、説明会、マニュアル作成
以上を一通り行うと、3ヶ月から4ヶ月かかりますので、決してぼったくりではありません。
それだけセールスやプロジェクトマネージャーやプロダクトマネージャーやエンジニアが動きますので、実際にかかるコストです。
それが、ChatGPTで30分もあれば無料で作れるようになりました。しかも、その後のデザインや機能の変更も自由自在。
仕様を変更すれば、また別見積もりになり、費用と期間は青天井。
ということは、私たちに必要となるのは、コーディング能力以前の、アプリを使う立場のユーザー目線と、アプリの機能に関するユーザー目線での知識、すなわち要件定義を行う力であったり、アプリを企画・設計する力なわけです。
そしてこの先には、v0, create, cursor, GPT engineer, boltなど、さらに複雑なアプリやバックエンドシステム付きのアプリをプロンプトだけで作れる時代が必ず訪れます。
そのための下準備として、今回のような、ブラウザ=フロントエンドだけで稼働するWebアプリと、プロンプトだけで作れるAIアプリ=GPTsを作ることに慣れておきましょう、という意味合いもあります。
「シンプルテキストエディタ」は、日常のテキスト編集作業を効率化するためのシンプルで強力なツールです。直感的なインターフェースと豊富な機能により、誰でも簡単に利用することができます。ぜひ、以下のリンクからアプリケーションを試してみてください!
オンラインで試す: https://indepa.net/app/textedit.html
HTMLファイルをダウンロード:
ソースコードを確認: 上記のHTMLコードブロックをご参照ください。
【重版出来】AIアプリをプログラミング不要で開発する書籍
ChatGPTでAIアプリを作って仕事で使いたい方と世界に公開したい方へ!