画像からWebフォームを自動生成!AIとVueForm Builderで実現する爆速フォーム作成
はじめに
紙の申込書やアンケートをWebフォーム化する作業、面倒ですよね。。
「この紙のフォーム、Webフォームにしてください」
こんな依頼を受けるたびに、一つ一つ項目を見ながら手動でフォームを作成していませんか?項目が多いフォームだと、30分以上かかることもザラです。
そんな悩みを解決するため、画像をアップロードするだけでWebフォームが自動生成される仕組みを作ってみました。AIとVueForm Builderを組み合わせたら、想像以上に実用的なものができたので共有します!
Vueform Builderとは
今回の実装の要となるVueform Builderについて、まず簡単に説明します。
Vueform Builderは、Vue.jsベースの高機能なフォームビルダーライブラリです。ドラッグ&ドロップでフォームを作成できるビジュアルエディタと、JSONスキーマからの動的フォーム生成機能を提供しています。
主な特徴
- ビジュアルエディタ: ドラッグ&ドロップで直感的にフォーム作成
- JSONスキーマ対応: JSONからフォームを動的に生成・復元
- 豊富なフィールドタイプ: テキスト、日付、選択、グリッドなど多様な入力形式
- バリデーション機能: 組み込みの検証ルール
- レスポンシブ対応: モバイル・デスクトップ両対応
料金について
Vueform Builderは一部有償サービスです。具体的な料金プランについては、公式の料金ページで最新情報をご確認ください。
今回の実装では、JSONスキーマからフォームを生成する機能を活用しています。
ライセンスコストはかかりますが、開発効率の向上と保守性を考えると十分にペイする投資だと感じています。特に、生成されたフォームを後からビジュアルエディタで調整できる点が、実運用において非常に便利です。
作ったもの
こんな感じで動きます:
- 📸 紙のフォームを撮影(または画像をアップロード)
- 🤖 AIが画像を解析してフォーム構造を認識
- 📝 VueForm Builderで即座にWebフォーム化
- ✏️ 必要に応じて微調整して完成!
処理時間は画像アップロードから約1分。手動だと30分かかっていた作業が、あっという間に終わります。
こんな場面で使える
実際に以下のようなケースで活用できます:
- 📄 紙のアンケート用紙をWebフォーム化
- 📝 既存の申込書や応募用紙のデジタル化
- 🔄 古い紙資料を参考に新しいフォームを作成
特に「紙文化」が根強い組織でのDX推進には効果的です。
技術スタック
今回使用した技術:
- フロントエンド: Vue 3 + Vue Form Builder
- バックエンド: Supabase Edge Functions
- AI処理: Claude Sonnet 4(画像解析)
- 画像処理: Canvas API(Base64エンコード)
Vue Form Builderを選んだ理由は、動的フォーム生成が得意で、JSONスキーマからフォームを自動生成できるからです。
実際のAI処理部分はSupabase Edge Functionsで実装しています。サーバーレスで運用コストも抑えられるので、小規模プロジェクトにも導入しやすいのがメリットです。
処理の流れ
シンプルに以下の4ステップです:
画像アップロード → Base64変換 → AI解析 → Vue Form Builder用JSON生成 → 完成!
それぞれのステップを詳しく見ていきましょう。
実装
1. 画像のアップロードとBase64変換
まずはフロントエンドで画像を受け取り、AIに送信するためにBase64形式に変換します。
<script setup>
import { ref } from 'vue';
import VueFormGenerator from 'vue-form-generator';
const formSchema = ref(null);
const formModel = ref({});
const formOptions = {
validateAfterLoad: true,
validateAfterChanged: true
};
// 画像をBase64に変換
async function toBase64(file) {
// ファイルサイズチェック(5MB制限)
if (file.size > 5 * 1024 * 1024) {
throw new Error('ファイルサイズは5MB以下にしてください');
}
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = e => resolve(e.target.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// ファイルアップロード処理
async function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
try {
// Loading表示
showLoading();
// Base64変換
const base64 = await toBase64(file);
// バックエンドのAPIを呼び出し
const formData = await generateFormFromImage(base64, file.name, file.type);
// Vue Form Builder用に変換
formSchema.value = formData;
} catch (error) {
console.error('エラー:', error);
alert('フォーム生成に失敗しました');
} finally {
hideLoading();
}
}
</script>
<template>
<div class="form-generator">
<div class="upload-area" @drop="handleDrop" @dragover.prevent>
<input
type="file"
@change="handleFileUpload"
accept="image/*,.pdf"
ref="fileInput"
>
<p>画像をドラッグ&ドロップ または クリックして選択</p>
</div>
<!-- 生成されたフォーム -->
<div v-if="formSchema" class="generated-form">
<vue-form-generator
:schema="formSchema"
:model="formModel"
:options="formOptions"
/>
</div>
</div>
</template>
2. バックエンドAPIの実装
バックエンド側の処理は概念的にはこんな感じです(実際はSupabase Edge Functionsで動いています):
// API エンドポイントの概念的な実装
async function generateFormFromImage(base64Image, fileName, fileType) {
// APIリクエストの送信
const response = await fetch('/api/generate-form', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiToken}`
},
body: JSON.stringify({
fileBase64: base64Image,
fileName: fileName,
fileType: fileType,
workspaceId: currentWorkspaceId
})
});
if (!response.ok) {
throw new Error('フォーム生成に失敗しました');
}
return response.json();
}
AIプロンプトエンジニアリング - 精度向上の秘訣
🎯 構造化されたプロンプトの活用
プロンプトの書き方で精度が大きく変わります。私たちの実装では、XMLライクなタグを使った構造化プロンプトを採用しています。これにより、AIへの指示が明確になり、出力の一貫性が格段に向上しました。
// 実際のプロンプト構造(簡略版)
function createOptimizedPrompt() {
return `
画像からVueFormBuilderスキーマを生成してください。
<thinking_mode>
**Extended Thinking**を使用して、以下の手順で慎重に分析してください:
1. 画像の構造分析
- 画像内のすべての要素を識別
- 要素間の階層関係を把握
- レイアウトパターンの認識
2. VueFormBuilder要素へのマッピング
- 各視覚要素を適切なVueFormBuilderタイプに変換
- 必要なプロパティの決定
</thinking_mode>
<rules>
1. 出力形式:VueFormBuilderスキーマ(JSON Schemaではない)
2. 各エレメントは個別のキー名を持つオブジェクトとして定義
3. テーブル形式のデータは個別フィールドに分解
4. 利用可能なエレメントタイプのみ使用
</rules>
<element_types>
- テキスト系: "h1", "h2", "h3", "p"
- 入力系: "text", "textarea", "number", "email", "date"
- 選択系: "select", "radiogroup", "checkboxgroup"
- レイアウト系: "group", "divider", "grid"
</element_types>
<formatting_rules>
1. 見出し:type: "h1"/"h2"/"h3" + content プロパティ
2. 説明文:type: "p" + content プロパティ
3. 横並び:container + schema プロパティ内に子要素
4. 選択肢:items プロパティで配列指定
</formatting_rules>
<output_structure>
必ず以下の構造で出力:
{
"schema": {
"要素キー1": {
"type": "エレメントタイプ",
"name": "要素キー1",
"label": "ラベル",
"builder": { "type": "エレメントタイプ", "label": "ラベル" }
}
},
"theme": {},
"form": { "nesting": true },
"export": { "output": "inline", "api": "options", "theme": "none" },
"builder": { "name": "GeneratedForm", "elements": [] }
}
</output_structure>
`;
}
タグを使うメリット:
- 明確な境界: 各セクションの役割が明確になる
- 優先順位の明示: AIが処理すべき順序を指定できる
- エラー削減: 構造化により出力の一貫性が向上
🚀 ファインチューニング風の学習例提供
さらに重要なのが、実際のフォーム構造を例として提供することです。これは厳密なファインチューニングではありませんが、Few-shot学習の効果を狙った実装です。
// 実際のフォーム構造例(1つを紹介、実際は10個程度用意)
const outputExample = {
schema: {
title: {
type: "h1",
name: "title",
content: "顧客満足度アンケート",
align: "center",
builder: { type: "h1", label: "見出し1" }
},
customer_name: {
type: "text",
name: "customer_name",
label: "お名前",
placeholder: "山田 太郎",
builder: { type: "text", label: "テキスト" }
},
email: {
type: "email",
name: "email",
label: "メールアドレス",
placeholder: "example@email.com",
builder: { type: "email", label: "メールアドレス" }
},
satisfaction: {
type: "radiogroup",
name: "satisfaction",
label: "満足度",
items: [
{ value: "very_satisfied", label: "とても満足" },
{ value: "satisfied", label: "満足" },
{ value: "neutral", label: "普通" },
{ value: "dissatisfied", label: "不満" },
{ value: "very_dissatisfied", label: "とても不満" }
],
builder: { type: "radiogroup", label: "ラジオボタン" }
},
comments: {
type: "textarea",
name: "comments",
label: "ご意見・ご感想",
rows: 4,
placeholder: "自由にご記入ください",
builder: { type: "textarea", label: "テキストエリア" }
}
},
theme: {},
form: { nesting: true },
export: { output: "inline", api: "options", theme: "none" },
builder: { name: "GeneratedForm", elements: [] }
};
実際の実装では、このような例を10個程度用意しています:
これらの例をプロンプトに含めることで、AIは正しい出力形式を学習し、一貫性のある結果を生成できるようになります。
📊 実際の効果
この「ファインチューニング風」アプローチの効果は絶大でした:
こちらのアプローチを試す前は、name/builder属性の欠落により、正しくvueformの形式を作成できないことが頻発していましたが、今ではほぼ欠落した形式がレスポンスとして変えることはなくなりました。
🔄 スキーマ検証とリトライ処理
それでも完璧ではないので、検証とリトライの仕組みも実装しています。レスポンスを再度AIに投げることで、より精度の高い結果を得ることができます。:
// スキーマ構造の検証
function validateSchemaStructure(schema) {
const errors = [];
// 必須プロパティのチェック
const requiredProps = ["schema", "theme", "form", "export", "builder"];
for (const prop of requiredProps) {
if (!(prop in schema)) {
errors.push(`Missing required property: ${prop}`);
}
}
// schema内の各要素の検証
if (schema.schema) {
for (const [key, element] of Object.entries(schema.schema)) {
// Vue Form Builder必須属性のチェック
if (!element.type) {
errors.push(`Element ${key} missing 'type' property`);
}
if (!element.name) {
errors.push(`Element ${key} missing 'name' property`);
}
if (!element.builder) {
errors.push(`Element ${key} missing 'builder' property`);
}
}
}
return { isValid: errors.length === 0, errors };
}
// 検証に失敗した場合の修正プロンプト
function createValidationPrompt(invalidResponse) {
return `
<validation_task>
以下のレスポンスを検証し、正しいVueFormBuilderスキーマ形式に修正してください。
<response_to_fix>
${invalidResponse}
</response_to_fix>
<errors_found>
- name属性が欠落している要素があります
- builder属性が欠落している要素があります
- 必須プロパティが不足しています
</errors_found>
<required_fixes>
1. 各要素にname属性を追加(キー名と同じ値)
2. 各要素にbuilder属性を追加
3. 不足している必須プロパティを追加
</required_fixes>
正しい形式で出力してください。
</validation_task>
`;
}
プロンプトエンジニアリングで精度を上げるその他のコツ
🎯 フィールドタイプの判定精度向上
const fieldTypeRules = `
<field_type_detection>
優先順位順の判定ルール:
1. メールアドレス、E-mail → type: "email"
2. 電話番号、TEL → type: "text", inputType: "tel"
3. 生年月日、日付 → type: "date"
4. □(四角) → type: "checkbox"
5. ○(丸) → type: "radiogroup"
6. ▼や「選択してください」 → type: "select"
7. 複数行の大きな枠 → type: "textarea"
8. その他 → type: "text"
</field_type_detection>
`;
🔧 日本語特有の表記ゆれ対策
const normalizeRules = `
<normalization_rules>
以下は同じ意味として扱い、英語のname属性に統一:
- 氏名、名前、お名前 → "name"
- メール、Email、メールアドレス → "email"
- 電話、TEL、連絡先 → "phone"
- 住所、所在地 → "address"
- 備考、その他、コメント → "remarks"
</normalization_rules>
`;
実装時のハマりポイントと対策
⚠️ Vue Form Builder特有の注意点
Vue Form Builderでは、各要素にname属性とbuilder属性が必須です。これを忘れるとフォームが正しく動作しません:
// ❌ NG: name属性とbuilder属性がない
{
"text_field": {
"type": "text",
"label": "テキストフィールド"
}
}
// ✅ OK: 必須属性を含む
{
"text_field": {
"type": "text",
"label": "テキストフィールド",
"name": "text_field", // 必須!
"builder": { // 必須!
"type": "text",
"label": "テキストフィールド"
}
}
}
実際に使ってみた効果
📊 ビフォーアフター
項目 | Before(手動) | After(AI生成) |
---|---|---|
作成時間 | 20〜30分 | 1〜3分 |
入力ミス | たまに発生 | 作成されたフォームをカスタマイズしたい場合は、作業が発生 |
精神的負担 | 単調作業でつらい | 楽しい! |
まとめ
画像からWebフォームを自動生成する仕組みを作ってみました。
ポイントをまとめると:
- 🚀 開発時間を大きく削減できた
- 🏷️ タグ構造のプロンプトで指示を明確化
- 📚 実例提供によるファインチューニング風学習で精度向上
- 🔧 Vue Form Builderとの相性が抜群
- 💡 完璧じゃなくても実用レベルには十分
特に、構造化されたプロンプトと実例の提供により、AIの出力精度が格段に向上したのは大きな発見でした。
AIを使った開発支援ツールは、もはや「あったら便利」から「なくてはならない」レベルになってきていると感じます。
紙のフォームをデジタル化する需要は意外と多いので、同じような悩みを持っている方の参考になれば嬉しいです!
応募待っています
WEBエンジニア募集中です!医療業界での経験や3Dの知見は問いません。Berryの考え方や製品に少しでも興味が持てた方はお気軽に応募下さい。
Discussion