export default { async fetch(request, env, ctx) { // 1. CORS対応 (フロントエンドからのアクセスを許可) const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }; if (request.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } if (request.method !== "POST") { return new Response("Method not allowed", { status: 405, headers: corsHeaders }); } // 2. URLパスの確認 (/analyze のみ許可) const url = new URL(request.url); if (url.pathname !== "/analyze") { return new Response("Not found", { status: 404, headers: corsHeaders }); } try { const requestData = await request.json(); // 3. 利用するモデルの指定 (ここでエラーを回避します) // v1beta の gemini-2.5-flash を優先。または gemini-1.5-flash を指定。 const apiVersion = "v1beta"; const modelName = "gemini-2.5-flash"; const geminiApiUrl = `https://generativelanguage.googleapis.com/${apiVersion}/models/${modelName}:generateContent?key=${env.GEMINI_API_KEY}`; // プロンプトの組み立て const prompt = `Analyze this English speech transcript. Topic: "${requestData.topic}" User Speech: "${requestData.text}" WPM: ${requestData.pace} Return EXACTLY in this JSON format: { "score": 0-100, "ideal": "An ideal 1-minute speech based on user's content, optimized for 140-150 WPM.", "eval": { "pronunciation": "日本語で具体的に。音声認識の不自然な変換をチェックし、日本人特有の発音的傾向を分析し、どの部分がおかしいか詳しく。100〜150文字程度。", "grammar": "日本語で具体的に。文法的な間違いがあれば、実際の箇所を抜き出して詳しく。100〜150文字程度。", "phrasing": "日本語で具体的に。不自然な表現があれば実際の箇所を明記し、より洗練された言い回しを詳しく。100〜150文字程度。" }, "advice": ["Tip 1 in JP", "Tip 2 in JP", "Tip 3 in JP"] }`; // 4. Google Gemini APIへのリクエスト const response = await fetch(geminiApiUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }], generationConfig: { responseMimeType: "application/json" } }) }); const responseData = await response.json(); // 5. 混雑エラー (High demand) などのハンドリング if (!response.ok) { // Google API が 503 (混雑) または 429 (制限) を返した場合、 // そのままフロントに伝播させることで、先ほど組み込んだフロント側の // `fetchWithRetry` が自動的に数秒待って再リクエストしてくれます。 const isBusyError = response.status === 503 || response.status === 429; const statusCode = isBusyError ? response.status : 500; return new Response(JSON.stringify({ error: responseData.error?.message || "Google API Error" }), { status: statusCode, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } // 成功した場合、AIからのJSON文字列を取り出してフロントへ返す const aiText = responseData.candidates[0].content.parts[0].text; return new Response(aiText, { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } catch (error) { return new Response(JSON.stringify({ error: error.message }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } } };