はじめに
生成AIを利用したコード生成について、HelloWorldのような簡単な例の紹介記事が多いため、より実務的なコード生成について検証します。プロンプトからどのようなコードが生成されるのかをステップバイステップで見ていきます。
今回はHTMLとJavaScriptのコードを生成してみます。生成AIはCursorエディタ経由でChatGPTを利用します。他の言語については以下の記事を参照ください。
Cursorエディタについて
ChatGPTと統合されたVSCodeライクな次世代AIエディタです。特徴は以下の通り。
生成AIを利用したHTMLプログラミング例
ユーザー登録画面の作成
良くあるユーザー登録画面を作成してみましょう。氏名や電話番号などの入力項目とチェック処理の作成を依頼してみます。
HTMLで以下の画面を作成してください。
■機能概要
ユーザー登録画面
■共通仕様
・HTMLはHTML5 Living Standardで作成する
・画面の入力チェックにはJavaScriptを使用する。
チェックエラー時はalertでメッセージを表示し、
エラー発生箇所にフォーカスを移動する。
・企業の業務システムをイメージした落ち着いたスタイル
■画面項目
・画面入力項目は以下のフォーマットで記述します。
フォーマット:ラベル名/要素タイプ/入力可能文字/その他のチェック仕様
・画面入力項目
1.氏名(漢字)/テキストボックス/全角文字/最大20桁、入力必須
2.氏名(カナ)/テキストボックス/全角文字/最大20桁、入力必須
3.郵便番号/テキストボックス/半角数字/7桁のみ許可、入力必須
4.住所1/テキストボックス/全角文字/最大20桁、入力必須
5.住所2/テキストボックス/全角文字/最大20桁
5.電話番号/テキストボックス/半角文字/最大10桁
6.性別/ラジオボタン/なし/必須入力
・画面下部エリアにOK、戻るボタンを配置
少し意図と違う形で出力されましたが、雰囲気はできています。チェック処理も少ない指示でしたがエラーメッセージも適切に実装されています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ユーザー登録</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 20px;
}
.container {
background-color: #ffffff;
border-radius: 5px;
padding: 20px;
max-width: 600px;
margin: 0 auto;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
label {
display: inline-block;
width: 150px;
margin-bottom: 10px;
}
input[type="text"], input[type="tel"] {
width: 200px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 3px;
}
.buttons {
text-align: center;
margin-top: 20px;
}
button {
padding: 10px 20px;
margin: 0 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
button[type="button"] {
background-color: #f44336;
}
</style>
<script>
function validateForm() {
var nameKanji = document.getElementById("nameKanji").value;
var nameKana = document.getElementById("nameKana").value;
var postalCode = document.getElementById("postalCode").value;
var address1 = document.getElementById("address1").value;
var address2 = document.getElementById("address2").value;
var phoneNumber = document.getElementById("phoneNumber").value;
var gender = document.querySelector('input[name="gender"]:checked');
var errorMessage = "";
// 氏名(漢字)チェック
if (nameKanji === "" || !/^[\u3000-\u9FFF]{1,20}$/.test(nameKanji)) {
errorMessage += "氏名(漢字)は全角文字で20文字以内で入力してください。\n";
document.getElementById("nameKanji").focus();
return false;
}
// 氏名(カナ)チェック
if (nameKana === "" || !/^[\u3000-\u9FFF]{1,20}$/.test(nameKana)) {
errorMessage += "氏名(カナ)は全角文字で20文字以内で入力してください。\n";
document.getElementById("nameKana").focus();
return false;
}
// 郵便番号チェック
if (postalCode === "" || !/^\d{7}$/.test(postalCode)) {
errorMessage += "郵便番号は7桁の半角数字で入力してください。\n";
document.getElementById("postalCode").focus();
return false;
}
// 住所1チェック
if (address1 === "" || !/^[\u3000-\u9FFF]{1,20}$/.test(address1)) {
errorMessage += "住所1は全角文字で20文字以内で入力してください。\n";
document.getElementById("address1").focus();
return false;
}
// 住所2チェック(任意)
if (address2 !== "" && !/^[\u3000-\u9FFF]{1,20}$/.test(address2)) {
errorMessage += "住所2は全角文字で20文字以内で入力してください。\n";
document.getElementById("address2").focus();
return false;
}
// 電話番号チェック(任意)
if (phoneNumber !== "" && !/^\d{1,10}$/.test(phoneNumber)) {
errorMessage += "電話番号は半角数字で10桁以内で入力してください。\n";
document.getElementById("phoneNumber").focus();
return false;
}
// 性別チェック
if (!gender) {
errorMessage += "性別を選択してください。\n";
document.getElementById("genderMale").focus();
return false;
}
if (errorMessage !== "") {
alert(errorMessage);
return false;
}
return true;
}
</script>
</head>
<body>
<div class="container">
<h1>ユーザー登録</h1>
<form onsubmit="return validateForm()">
<div>
<label for="nameKanji">氏名(漢字):</label>
<input type="text" id="nameKanji" name="nameKanji" required maxlength="20">
</div>
<div>
<label for="nameKana">氏名(カナ):</label>
<input type="text" id="nameKana" name="nameKana" required maxlength="20">
</div>
<div>
<label for="postalCode">郵便番号:</label>
<input type="text" id="postalCode" name="postalCode" required maxlength="7">
</div>
<div>
<label for="address1">住所1:</label>
<input type="text" id="address1" name="address1" required maxlength="20">
</div>
<div>
<label for="address2">住所2:</label>
<input type="text" id="address2" name="address2" maxlength="20">
</div>
<div>
<label for="phoneNumber">電話番号:</label>
<input type="tel" id="phoneNumber" name="phoneNumber" maxlength="10">
</div>
<div>
<label>性別:</label>
<input type="radio" id="genderMale" name="gender" value="male" required>
<label for="genderMale">男性</label>
<input type="radio" id="genderFemale" name="gender" value="female" required>
<label for="genderFemale">女性</label>
</div>
<div class="buttons">
<button type="submit">OK</button>
<button type="button" onclick="history.back()">戻る</button>
</div>
</form>
</div>
</body>
</html>
コード修正:氏名を姓名で入力可能にする
もともとの指示も良くなかったので氏名について姓名が別々の入力項目になっていませんでした。分離を指示します。
氏名(漢字)と氏名(カナ)は性と名で入力可能なようにしてください。
意図通り姓名を分割してくれました。感心したのが姓名の分割と一緒に各入力欄にプレースホルダーを追加し、ユーザーが何を入力すべきかわかりやすくしてくれたことです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ユーザー登録</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 20px;
}
.container {
background-color: #ffffff;
border-radius: 5px;
padding: 20px;
max-width: 600px;
margin: 0 auto;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
label {
display: inline-block;
width: 150px;
margin-bottom: 10px;
}
input[type="text"], input[type="tel"] {
width: 200px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 3px;
}
.name-input {
width: 95px;
}
.buttons {
text-align: center;
margin-top: 20px;
}
button {
padding: 10px 20px;
margin: 0 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
button[type="button"] {
background-color: #f44336;
}
</style>
<script>
function validateForm() {
var lastNameKanji = document.getElementById("lastNameKanji").value;
var firstNameKanji = document.getElementById("firstNameKanji").value;
var lastNameKana = document.getElementById("lastNameKana").value;
var firstNameKana = document.getElementById("firstNameKana").value;
var postalCode = document.getElementById("postalCode").value;
var address1 = document.getElementById("address1").value;
var address2 = document.getElementById("address2").value;
var phoneNumber = document.getElementById("phoneNumber").value;
var gender = document.querySelector('input[name="gender"]:checked');
var errorMessage = "";
// 氏名(漢字)チェック
if (lastNameKanji === "" || firstNameKanji === "" || !/^[\u3000-\u9FFF]{1,10}$/.test(lastNameKanji) || !/^[\u3000-\u9FFF]{1,10}$/.test(firstNameKanji)) {
errorMessage += "氏名(漢字)は姓名それぞれ全角文字で10文字以内で入力してください。\n";
document.getElementById("lastNameKanji").focus();
return false;
}
// 氏名(カナ)チェック
if (lastNameKana === "" || firstNameKana === "" || !/^[\u30A0-\u30FF]{1,10}$/.test(lastNameKana) || !/^[\u30A0-\u30FF]{1,10}$/.test(firstNameKana)) {
errorMessage += "氏名(カナ)は姓名それぞれ全角カタカナで10文字以内で入力してください。\n";
document.getElementById("lastNameKana").focus();
return false;
}
// 郵便番号チェック
if (postalCode === "" || !/^\d{7}$/.test(postalCode)) {
errorMessage += "郵便番号は7桁の半角数字で入力してください。\n";
document.getElementById("postalCode").focus();
return false;
}
// 住所1チェック
if (address1 === "" || !/^[\u3000-\u9FFF]{1,20}$/.test(address1)) {
errorMessage += "住所1は全角文字で20文字以内で入力してください。\n";
document.getElementById("address1").focus();
return false;
}
// 住所2チェック(任意)
if (address2 !== "" && !/^[\u3000-\u9FFF]{1,20}$/.test(address2)) {
errorMessage += "住所2は全角文字で20文字以内で入力してください。\n";
document.getElementById("address2").focus();
return false;
}
// 電話番号チェック(任意)
if (phoneNumber !== "" && !/^\d{1,10}$/.test(phoneNumber)) {
errorMessage += "電話番号は半角数字で10桁以内で入力してください。\n";
document.getElementById("phoneNumber").focus();
return false;
}
// 性別チェック
if (!gender) {
errorMessage += "性別を選択してください。\n";
document.getElementById("genderMale").focus();
return false;
}
if (errorMessage !== "") {
alert(errorMessage);
return false;
}
return true;
}
</script>
</head>
<body>
<div class="container">
<h1>ユーザー登録</h1>
<form onsubmit="return validateForm()">
<div>
<label>氏名(漢字):</label>
<input type="text" id="lastNameKanji" name="lastNameKanji" class="name-input" required maxlength="10" placeholder="姓">
<input type="text" id="firstNameKanji" name="firstNameKanji" class="name-input" required maxlength="10" placeholder="名">
</div>
<div>
<label>氏名(カナ):</label>
<input type="text" id="lastNameKana" name="lastNameKana" class="name-input" required maxlength="10" placeholder="セイ">
<input type="text" id="firstNameKana" name="firstNameKana" class="name-input" required maxlength="10" placeholder="メイ">
</div>
<div>
<label for="postalCode">郵便番号:</label>
<input type="text" id="postalCode" name="postalCode" required maxlength="7">
</div>
<div>
<label for="address1">住所1:</label>
<input type="text" id="address1" name="address1" required maxlength="20">
</div>
<div>
<label for="address2">住所2:</label>
<input type="text" id="address2" name="address2" maxlength="20">
</div>
<div>
<label for="phoneNumber">電話番号:</label>
<input type="tel" id="phoneNumber" name="phoneNumber" maxlength="10">
</div>
<div>
<label>性別:</label>
<input type="radio" id="genderMale" name="gender" value="male" required>
<label for="genderMale">男性</label>
<input type="radio" id="genderFemale" name="gender" value="female" required>
<label for="genderFemale">女性</label>
</div>
<div class="buttons">
<button type="submit">OK</button>
<button type="button" onclick="history.back()">戻る</button>
</div>
</form>
</div>
</body>
</html>
機能追加:郵便番号から住所を自動入力する機能を追加
良くある郵便番号から住所を自動入力する機能を追加してみたいと思います。以下のように問い合わせてみました。
住所1は郵便番号から自動反映することはできますか?
AIから以下の応答があり、HTMLの修正案を提示してくれました。日本郵便のAPIは業務でも利用するものなので提案内容はバッチリです。インターネットにつながっている環境であれば以下のHTMLで実際に郵便番号からの自動入力機能が利用できます。
はい、郵便番号から住所1を自動反映することは可能です。日本郵便が提供している郵便番号検索APIを使用することで実現できます。以下に、その実装方法を示します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ユーザー登録</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 20px;
}
.container {
background-color: #ffffff;
border-radius: 5px;
padding: 20px;
max-width: 600px;
margin: 0 auto;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
label {
display: inline-block;
width: 150px;
margin-bottom: 10px;
}
input[type="text"], input[type="tel"] {
width: 200px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 3px;
}
.name-input {
width: 95px;
}
.buttons {
text-align: center;
margin-top: 20px;
}
button {
padding: 10px 20px;
margin: 0 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
button[type="button"] {
background-color: #f44336;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/fetch-jsonp@1.1.3/build/fetch-jsonp.min.js"></script>
<script>
function validateForm() {
var lastNameKanji = document.getElementById("lastNameKanji").value;
var firstNameKanji = document.getElementById("firstNameKanji").value;
var lastNameKana = document.getElementById("lastNameKana").value;
var firstNameKana = document.getElementById("firstNameKana").value;
var postalCode = document.getElementById("postalCode").value;
var address1 = document.getElementById("address1").value;
var address2 = document.getElementById("address2").value;
var phoneNumber = document.getElementById("phoneNumber").value;
var gender = document.querySelector('input[name="gender"]:checked');
var errorMessage = "";
// 氏名(漢字)チェック
if (lastNameKanji === "" || firstNameKanji === "" || !/^[\u3000-\u9FFF]{1,10}$/.test(lastNameKanji) || !/^[\u3000-\u9FFF]{1,10}$/.test(firstNameKanji)) {
errorMessage += "氏名(漢字)は姓名それぞれ全角文字で10文字以内で入力してください。\n";
document.getElementById("lastNameKanji").focus();
return false;
}
// 氏名(カナ)チェック
if (lastNameKana === "" || firstNameKana === "" || !/^[\u30A0-\u30FF]{1,10}$/.test(lastNameKana) || !/^[\u30A0-\u30FF]{1,10}$/.test(firstNameKana)) {
errorMessage += "氏名(カナ)は姓名それぞれ全角カタカナで10文字以内で入力してください。\n";
document.getElementById("lastNameKana").focus();
return false;
}
// 郵便番号チェック
if (postalCode === "" || !/^\d{7}$/.test(postalCode)) {
errorMessage += "郵便番号は7桁の半角数字で入力してください。\n";
document.getElementById("postalCode").focus();
return false;
}
// 住所1チェック
if (address1 === "" || !/^[\u3000-\u9FFF]{1,20}$/.test(address1)) {
errorMessage += "住所1は全角文字で20文字以内で入力してください。\n";
document.getElementById("address1").focus();
return false;
}
// 住所2チェック(任意)
if (address2 !== "" && !/^[\u3000-\u9FFF]{1,20}$/.test(address2)) {
errorMessage += "住所2は全角文字で20文字以内で入力してください。\n";
document.getElementById("address2").focus();
return false;
}
// 電話番号チェック(任意)
if (phoneNumber !== "" && !/^\d{1,10}$/.test(phoneNumber)) {
errorMessage += "電話番号は半角数字で10桁以内で入力してください。\n";
document.getElementById("phoneNumber").focus();
return false;
}
// 性別チェック
if (!gender) {
errorMessage += "性別を選択してください。\n";
document.getElementById("genderMale").focus();
return false;
}
if (errorMessage !== "") {
alert(errorMessage);
return false;
}
return true;
}
function searchAddress() {
const postalCode = document.getElementById('postalCode').value;
const url = `https://zipcloud.ibsnet.co.jp/api/search?zipcode=${postalCode}`;
fetchJsonp(url)
.then(response => response.json())
.then(data => {
if (data.results) {
const result = data.results[0];
const address = `${result.address1}${result.address2}${result.address3}`;
document.getElementById('address1').value = address;
} else {
alert('該当する住所が見つかりませんでした。');
}
})
.catch(error => {
console.error('Error:', error);
alert('住所の取得中にエラーが発生しました。');
});
}
</script>
</head>
<body>
<div class="container">
<h1>ユーザー登録</h1>
<form onsubmit="return validateForm()">
<div>
<label>氏名(漢字):</label>
<input type="text" id="lastNameKanji" name="lastNameKanji" class="name-input" required maxlength="10" placeholder="姓">
<input type="text" id="firstNameKanji" name="firstNameKanji" class="name-input" required maxlength="10" placeholder="名">
</div>
<div>
<label>氏名(カナ):</label>
<input type="text" id="lastNameKana" name="lastNameKana" class="name-input" required maxlength="10" placeholder="セイ">
<input type="text" id="firstNameKana" name="firstNameKana" class="name-input" required maxlength="10" placeholder="メイ">
</div>
<div>
<label for="postalCode">郵便番号:</label>
<input type="text" id="postalCode" name="postalCode" required maxlength="7">
<button type="button" onclick="searchAddress()">住所検索</button>
</div>
<div>
<label for="address1">住所1:</label>
<input type="text" id="address1" name="address1" required maxlength="20">
</div>
<div>
<label for="address2">住所2:</label>
<input type="text" id="address2" name="address2" maxlength="20">
</div>
<div>
<label for="phoneNumber">電話番号:</label>
<input type="tel" id="phoneNumber" name="phoneNumber" maxlength="10">
</div>
<div>
<label>性別:</label>
<input type="radio" id="genderMale" name="gender" value="male" required>
<label for="genderMale">男性</label>
<input type="radio" id="genderFemale" name="gender" value="female" required>
<label for="genderFemale">女性</label>
</div>
<div class="buttons">
<button type="submit">OK</button>
<button type="button" onclick="history.back()">戻る</button>
</div>
</form>
</div>
</body>
</html>
素晴らしいなと思ったのは上記の修正内容について以下のように説明もしてくれた点です。
この実装では以下の変更を行いました:
1. fetch-jsonpライブラリを追加しました。これは、郵便番号検索APIがJSONPを使用しているため必要です。
2. searchAddress関数を追加しました。この関数は郵便番号から住所を検索し、結果を住所1の入力欄に反映します。
3. 郵便番号の入力欄の横に「住所検索」ボタンを追加しました。このボタンをクリックするとsearchAddress関数が呼び出されます。
この実装により、ユーザーは郵便番号を入力して「住所検索」ボタンをクリックすることで、住所1の欄に自動的に住所が入力されるようになります。
ただし、以下の点に注意してください:
・APIの利用制限や利用規約を確認し、遵守してください。
・エラー処理を適切に行い、APIが利用できない場合やネットワークエラーが発生した場合にも対応できるようにしてください。
・住所1の文字数制限(20文字)を超える可能性があるため、必要に応じて調整してください。
これらの点を考慮しながら、実際の運用環境に合わせて適切に実装してください。
画面デザインの見直しを指示
画面デザインが少しさみしいので、見直しを指示してみます。
・企業の落ち着いた画面イメージに変更
・各項目のラベルと入力項目は1行で表示
落ち着いたデザインに変更され、ボタンサイズも統一されました。素晴らしい!
感想
HTMLやJavaScriptのコード生成能力は非常に高く、実用的に使えると感じました。実際の業務ではデザインガイドなどに従って実装するため、もう少し手間がかかるかもしれませんが、これらのデザインやガイドを学習させたり、指示を与えたりすれば問題ないと思います。
特にデザイン面では、自分でデザインするよりも優れた結果が得られる可能性があります。私もそうですが、プログラマーの中にはデザインが苦手な人も多いので、非常に役立つのではないでしょうか。
以上