- Cursor AIを使ってLive2D + ChatGPT APIのWebアプリを1日で完成
- TypeScript + Vite + Vercelのモダンスタックで実装
- Wikipedia APIとOpenAI APIを組み合わせたAIアシスタント機能
- レスポンシブ対応でPC/スマホ両対応
- 実装で躓いたポイントとCursor AIの活用法を詳細解説
はじめに
最近話題のCursor AIを使って、「どこまで短時間で実用的なアプリが作れるのか」を検証してみました。題材はLive2Dキャラクターがユーザーと対話しながらWikipediaを案内するWebアプリです。
結果として、1日(約12時間)で本格的なAIアプリが完成しました。この記事では、実装の詳細とCursor AIがどれだけ開発を加速してくれたかを共有します。
🚀 完成したアプリの機能
主要機能
✅ Live2Dキャラクターのリアルタイム描画(WebGL)
✅ ChatGPT APIによるAI応答(キャラクター設定付き)
✅ Wikipedia記事の自動要約
✅ 自然言語による記事検索・移動
✅ ランダム記事探索
✅ レスポンシブUI(PC/タブレット/スマホ対応)
デモ画面
【Cursor AI × Live2D】1日でWikipedia案内アプリを爆速開発!(アプリ動画)
🏗️ 技術スタック
フロントエンド
// 主要技術
- TypeScript 5.8+
- Vite 6.3+(ビルドツール)
- Live2D Cubism SDK for Web
- WebGL(キャラクター描画)
- CSS Grid + Flexbox(レスポンシブ)
バックエンド
// Vercelサーバーレス関数
- Node.js
- OpenAI API (gpt-4o-mini)
- Wikipedia API
- CORS対応
開発環境
# 開発支援ツール
- Cursor AI(メイン開発環境)
- ESLint + Prettier(コード品質)
- TypeScript(型安全性)
- Git(バージョン管理)
📝 実装の詳細解説
1. Live2D統合とWebGL設定
Live2Dフレームワークの初期化
// Framework/src/live2dcubismframework.ts
export class CubismFramework {
public static startUp(option: Option = null): boolean {
if (s_isStarted) {
CubismLogInfo('CubismFramework.startUp() is already done.');
return s_isStarted;
}
s_option = option;
if (s_option != null) {
Live2DCubismCore.Logging.csmSetLogFunction(s_option.logFunction);
}
s_isStarted = true;
return s_isStarted;
}
}
WebGLコンテキストの管理
// Samples/TypeScript/Demo/src/lappview.ts
export class LAppView {
public initialize(subdelegate: LAppSubdelegate): void {
this._subdelegate = subdelegate;
const { width, height } = subdelegate.getCanvas();
// デバイス座標からスクリーン座標の変換行列
this._deviceToScreen = new CubismMatrix44();
this._viewMatrix = new CubismViewMatrix();
// 表示範囲とスケール設定
const ratio: number = width / height;
this._viewMatrix.setScreenRect(-ratio, ratio,
LAppDefine.ViewLogicalLeft,
LAppDefine.ViewLogicalRight);
this._viewMatrix.scale(LAppDefine.ViewScale, LAppDefine.ViewScale);
}
}
2. ChatGPT API統合(サーバーレス関数)
Vercel API関数の実装
// api/chatgpt.js
import fetch from 'node-fetch';
// タイムアウト付きfetch
function fetchWithTimeout(resource, options = {}) {
const { timeout = 10000 } = options;
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
return fetch(resource, {
...options,
signal: controller.signal
}).finally(() => clearTimeout(id));
}
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method Not Allowed' });
}
const { message } = req.body;
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
return res.status(500).json({ error: 'APIキーが設定されていません' });
}
try {
const response = await fetchWithTimeout('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: 'あなたは明るくて優しい、かわいらしい女の子のキャラクターです。語尾や表現を柔らかくし、絵文字も交えて親しみやすく日本語で回答してください。'
},
{ role: 'user', content: message }
]
}),
timeout: 10000
});
const data = await response.json();
res.status(200).json({ answer: data.choices[0].message.content });
} catch (e) {
if (e.name === 'AbortError') {
res.status(504).json({ error: 'OpenAI APIリクエストがタイムアウトしました' });
} else {
res.status(500).json({ error: 'OpenAI APIリクエストでエラーが発生しました' });
}
}
}
3. Wikipedia API統合と記事処理
記事要約機能の実装
// フロントエンド:記事要約処理
const summaryBtn = document.querySelector('.chat-btn[要約]');
summaryBtn.addEventListener('click', async () => {
const iframe = document.getElementById('right-iframe');
const url = iframe.src;
// URLから記事タイトルを抽出
const match = url.match(/\/wiki\/([^#?]+)/);
if (!match) {
alert('記事タイトルの抽出に失敗しました');
return;
}
const title = decodeURIComponent(match[1]);
// Wikipedia APIで記事本文を取得
const apiUrl = `https://ja.wikipedia.org/w/api.php?action=query&prop=extracts&explaintext&format=json&titles=${encodeURIComponent(title)}&origin=*`;
try {
const res = await fetch(apiUrl);
const data = await res.json();
const pages = data.query.pages;
const page = Object.values(pages)[0];
const extract = page.extract;
if (!extract || extract.trim() === '') {
throw new Error('記事本文が取得できませんでした');
}
// ChatGPTで要約生成
const response = await fetch('/api/chatgpt', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: `以下のWikipedia記事を3〜4文程度で要約してください。\n\n${extract}`
})
});
const result = await response.json();
displayAnswer(result.answer);
} catch (error) {
console.error('要約処理エラー:', error);
displayAnswer('ごめんなさい、要約中にエラーが発生しました💦');
}
});
4. レスポンシブUI設計
CSS Grid + Flexboxによるレイアウト
/* メインレイアウト */
#container-horizontal {
display: flex;
flex: 1;
background: #f8f8f8;
min-height: 0;
height: 100%;
}
#live2d-area, #right-area {
border-radius: 20px;
margin: 16px 8px 8px 16px;
box-shadow: 0 4px 16px #d0d0d0a0;
background: #fff;
overflow: hidden;
height: 100%;
display: flex;
flex-direction: column;
}
#live2d-area {
flex: 2; /* Live2Dエリアは2/5の幅 */
}
#right-area {
flex: 3; /* Wikipediaエリアは3/5の幅 */
}
/* タブレット対応 */
@media (max-width: 900px) {
#container-horizontal {
flex-direction: column;
}
#live2d-area, #right-area {
margin: 12px 8px 0 8px;
border-radius: 16px;
min-height: 200px;
height: 40vh;
max-height: 50vh;
}
}
/* スマホ対応 */
@media (max-width: 600px) {
#live2d-area, #right-area {
margin: 8px 2vw 0 2vw;
border-radius: 12px;
min-height: 120px;
height: 32vh;
max-height: 40vh;
}
#chat-area {
flex-direction: column;
gap: 8px;
padding: 8px 2vw;
}
}
🤖 Cursor AIの活用ポイント
1. コード生成の精度が高い
prompt: "Live2D Cubism SDKを使ってWebGLでキャラクターを描画するTypeScriptコードを生成して"
結果: 完全に動作するLAppViewクラスとWebGL初期化コードが生成された
2. API統合のベストプラクティスを提案
prompt: "ChatGPT APIとWikipedia APIを組み合わせてサーバーレス関数を作成"
結果:
- 適切なエラーハンドリング
- タイムアウト設定
- CORS対応
- 環境変数の安全な管理
3. デバッグとトラブルシューティング
エラー: "WebGL context lost"
Cursor AIの提案:
- キャンバスサイズの動的変更処理
- メモリリークの回避策
- デバイス固有の制限への対応
⚡ パフォーマンス最適化
1. Live2D描画の最適化
// フレームレート制御
class LAppDelegate {
private _frameRate: number = 60;
public run(): void {
const loop = (): void => {
// フレーム制御でCPU使用率を抑制
setTimeout(() => {
this.update();
requestAnimationFrame(loop);
}, 1000 / this._frameRate);
};
loop();
}
}
2. API呼び出しの最適化
// デバウンス処理でAPI呼び出し頻度を制御
let debounceTimer;
function debouncedAPICall(callback, delay = 500) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(callback, delay);
}
// キャッシュによる重複リクエスト回避
const cache = new Map();
async function fetchWithCache(url) {
if (cache.has(url)) {
return cache.get(url);
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, data);
return data;
}
3. バンドルサイズの最適化
// vite.config.mts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'live2d': ['@framework/live2dcubismframework'],
'vendor': ['node-fetch', 'dotenv']
}
}
}
}
});
🔧 開発中に遭遇した課題と解決策
1. WebGL初期化の問題
問題: モバイルデバイスでWebGLコンテキストの取得に失敗
// 解決策: フォールバック処理を追加
const canvas = document.getElementById('live2d-canvas') as HTMLCanvasElement;
const gl = canvas.getContext('webgl2') ||
canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl');
if (!gl) {
throw new Error('WebGLがサポートされていません');
}
2. CORS問題
問題: Wikipedia APIからのレスポンスがブロックされる
// 解決策: origin=*パラメータを追加
const apiUrl = `https://ja.wikipedia.org/w/api.php?action=query&format=json&origin=*&...`;
3. ChatGPT APIのレート制限
問題: 連続リクエストでAPI制限に達する
// 解決策: リクエスト間隔制御とキューイング
class APIQueue {
private queue: Array<() => Promise<any>> = [];
private processing = false;
async add<T>(request: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await request();
resolve(result);
} catch (error) {
reject(error);
}
});
this.process();
});
}
private async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
const request = this.queue.shift()!;
await request();
await new Promise(resolve => setTimeout(resolve, 1000)); // 1秒間隔
}
this.processing = false;
}
}
4. TypeScript型定義の問題
問題: Live2D SDKの型定義が不完全
// 解決策: 型定義を拡張
declare global {
const Live2DCubismCore: {
Version: {
csmGetVersion(): number;
};
Logging: {
csmSetLogFunction(func: (message: string) => void): void;
csmGetLogFunction(): ((message: string) => void) | null;
};
Memory: {
initializeAmountOfMemory(size: number): void;
};
};
}
📊 開発効率の分析
Cursor AI使用前後の比較
従来の開発手法:
├── 要件定義・設計: 2時間
├── Live2D統合: 4時間 ⚠️
├── API実装: 3時間
├── UI作成: 2時間
├── デバッグ: 3時間
└── 合計: 14時間
Cursor AI使用:
├── 要件定義・設計: 1時間
├── Live2D統合: 2時間 ✅
├── API実装: 2時間 ✅
├── UI作成: 1時間 ✅
├── デバッグ: 1時間 ✅
└── 合計: 7時間(50%短縮)
特に効果的だった場面
- 複雑なAPI統合: Wikipedia + ChatGPT APIの組み合わせ
- WebGL初期化: Live2D SDKの複雑な設定
- TypeScript型定義: 外部ライブラリとの型安全な統合
- レスポンシブCSS: 複数デバイス対応のレイアウト
🚀 デプロイとCI/CD
Vercelでの自動デプロイ設定
// vercel.json
{
"functions": {
"api/chatgpt.js": {
"maxDuration": 30
}
},
"env": {
"OPENAI_API_KEY": "@openai_api_key"
},
"build": {
"env": {
"OPENAI_API_KEY": "@openai_api_key"
}
}
}
package.json scripts
{
"scripts": {
"dev": "vite --host",
"build": "tsc --noEmit && vite build",
"preview": "vite preview",
"deploy": "vercel --prod",
"lint": "eslint src/**/*.ts",
"type-check": "tsc --noEmit"
}
}
💡 学んだベストプラクティス
1. Cursor AIとの効果的な協働
🔥 効果的な使い方:
- 具体的な機能要求を明確に伝える
- エラーメッセージと一緒にコンテキストを提供
- 段階的に機能を追加していく
❌ 避けるべき使い方:
- 曖昧な指示
- 大量のコードを一度に生成させる
- 生成されたコードを理解せずに使用
2. API設計のポイント
// ✅ Good: 適切なエラーハンドリング
try {
const response = await fetch('/api/chatgpt', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message }),
signal: AbortSignal.timeout(10000) // タイムアウト設定
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('API Error:', error);
throw new Error('APIリクエストに失敗しました');
}
3. Live2D統合のコツ
// リソース管理を確実に行う
class ResourceManager {
private resources: Map<string, any> = new Map();
async loadModel(modelPath: string): Promise<CubismModel> {
if (this.resources.has(modelPath)) {
return this.resources.get(modelPath);
}
const model = await this.createModel(modelPath);
this.resources.set(modelPath, model);
return model;
}
dispose(): void {
for (const [key, resource] of this.resources) {
if (resource.release) {
resource.release();
}
}
this.resources.clear();
}
}
📈 今後の改善点と拡張案
短期的な改善
// 1. パフォーマンス監視の追加
class PerformanceMonitor {
static measureAPICall(apiName: string, fn: Function) {
const start = performance.now();
const result = fn();
const end = performance.now();
console.log(`${apiName}: ${end - start}ms`);
return result;
}
}
// 2. エラー報告システム
class ErrorReporter {
static report(error: Error, context: string) {
// 本番環境では外部サービスに送信
console.error(`[${context}]`, error);
}
}
長期的な拡張
- 多言語対応: i18n導入
- 音声合成: Web Speech API統合
- プッシュ通知: Service Worker実装
- オフライン対応: PWA化
- ユーザー認証: 個人設定の永続化
まとめ
この実験を通じて、Cursor AIが現代の開発において強力な相棒であることを実感しました。特に:
🎯 Cursor AIの強み
- コード生成の精度: 複雑なAPI統合も一発で動作するコードを生成
- コンテキスト理解: プロジェクト全体を理解した適切な提案
- 学習効率: 新しい技術スタックの習得を大幅に加速
🚀 開発速度の向上
- 従来の50%の時間で同等の機能を実装
- エラー解決時間の大幅短縮
- ベストプラクティスの自動適用
🎨 品質の向上
- 型安全性: TypeScriptの恩恵を最大化
- パフォーマンス: 最適化されたコードの自動生成
- 保守性: 構造化された読みやすいコード
Live2D × ChatGPT API × TypeScriptという比較的複雑な技術構成でも、Cursor AIのサポートにより1日でプロダクションレディなアプリを完成させることができました。
これからもAI支援開発の可能性を探求し、より効率的で創造的な開発手法を追求していきたいと思います。
🔗 参考リンク
- Live2D Cubism SDK for Web
- OpenAI API Documentation
- Wikipedia API Documentation
- Vercel Documentation
- Cursor AI
Tags: #TypeScript
#Live2D
#ChatGPT
#WebGL
#Vercel
#CursorAI
#AIアシスタント