diff --git a/public/index.html b/public/index.html index 15a108d..fb32877 100644 --- a/public/index.html +++ b/public/index.html @@ -4,9 +4,10 @@ AI チャット + +

AI チャット

- + + Uploaded Image + + +
-
- 調理時間 - -
10
+
+ 調理時間 + +
10
+
+
+ 人数 + +
1
+
-
- 人数 - -
1
-
-
その他の要望
@@ -85,12 +90,11 @@

AI チャット

-
+
選択されたジャンル
選択した食材
- - - - -
+ +
diff --git a/public/script.js b/public/script.js index a472398..b897519 100644 --- a/public/script.js +++ b/public/script.js @@ -4,10 +4,10 @@ const menu2Element = document.getElementById("menu2"); const menu3Element = document.getElementById("menu3"); const chatMessageTemplateElement = document.getElementById("chat-message-template"); const submitButtonElement = document.getElementById("submit-button"); -const syokuzai = ["豚肉", "鯖", "ごはん", "味噌", "タマネギ", "人参", "じゃがいも", "砂糖", "塩","醤油","ごま油","ミカン"]; -const nedan = [300, 200, 300, 300]; +const imageElement = document.getElementById("image-element"); +const imageUploadInput = document.getElementById("imageUploadInput"); +const syokuzai = ["豚肉", "鯖", "ごはん", "味噌", "タマネギ", "人参", "じゃがいも", "砂糖", "塩", "醤油", "ごま油", "ミカン"]; -// メッセージを画面に描画する function addChatMessageElement(author, chatMessage) { const fragment = chatMessageTemplateElement.content.cloneNode(true); const contentElement = fragment.querySelector(".chat-message__content"); @@ -24,230 +24,194 @@ function addChatMessageElement(author, chatMessage) { chatMessagesElement.appendChild(fragment); } -// AIと対話する関数 async function postChat(request) { - const response = await fetch("/chat", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(request), + try { + const response = await fetch("/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(request), + }); + + if (!response.ok) { + throw new Error(`HTTPエラー: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error("Chat APIリクエストエラー:", error); + return { content: "エラーが発生しました。後で再試行してください。" }; + } +} + +async function recognizeIngredientsWithChatGPT(imageBase64) { + const promptText = "画像にある食べ物を全て答えてください。ただし、答えには食材のみを表示してください。"; + const request = { + promptText, + "type": "image_url", + "image_url": `data:image/jpeg;base64,${imageBase64}` + + }; + + console.log("APIリクエスト:", request); // リクエスト内容を確認 + const aiResponse = await postChat(request); + console.log("認識された食材 (ChatGPT):", aiResponse.content); + + return aiResponse.content; +} + + +// 画像を圧縮する関数 +function compressImage(file, maxWidth = 800, maxHeight = 800, quality = 0.7) { + return new Promise((resolve, reject) => { + const img = document.createElement('img'); + img.onload = function() { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + let width = img.width; + let height = img.height; + + // アスペクト比を保ちつつサイズを制限 + if (width > maxWidth) { + height = height * (maxWidth / width); + width = maxWidth; + } + if (height > maxHeight) { + width = width * (maxHeight / height); + height = maxHeight; + } + + canvas.width = width; + canvas.height = height; + + ctx.drawImage(img, 0, 0, width, height); + + const base64 = canvas.toDataURL('image/jpeg', quality); + resolve(base64); + }; + img.onerror = reject; + + const reader = new FileReader(); + reader.onload = function(e) { + img.src = e.target.result; + }; + reader.readAsDataURL(file); + }); +} + +imageUploadInput.addEventListener('change', async (event) => { + const file = event.target.files[0]; + if (file) { + const compressedImageBase64 = await compressImage(file); + imageElement.src = compressedImageBase64; + console.log("圧縮後のBase64:", compressedImageBase64); + + const imageBase64 = compressedImageBase64.split(',')[1]; + const recognizedIngredients = await recognizeIngredientsWithChatGPT(imageBase64); + console.log("画像Base64データ:", imageBase64); + + addIngredientsToSyokuzai(recognizedIngredients.split('、')); + displayRecognizedIngredients(recognizedIngredients.split('、')); + } +}); + +function addIngredientsToSyokuzai(ingredients) { + ingredients.forEach(ingredient => { + if (!syokuzai.includes(ingredient)) { + syokuzai.push(ingredient); + } }); - return await response.json(); + console.log("更新されたsyokuzai:", syokuzai); +} + +function displayRecognizedIngredients(ingredients) { + const recognizedIngredientsElement = document.getElementById("recognized-ingredients"); + recognizedIngredientsElement.innerHTML = ingredients.map(ingredient => `
  • ${ingredient}
  • `).join(''); } -// 材料の配列からsyokuzaiの要素を減算する関数 function reduceIngredients(ingredientsText, syokuzai) { const ingredientsList = ingredientsText.split('・').map(item => item.trim()); const reducedList = ingredientsList.filter(item => !syokuzai.includes(item)); return reducedList.join('・'); } -// 材料の配列からsyokuzaiの要素を残す関数 function remainIngredients(ingredientsText, syokuzai) { - const IngredientsList = ingredientsText.split('・').map(item => item.trim()); - const remainList = IngredientsList.filter(item => syokuzai.includes(item)); + const ingredientsList = ingredientsText.split('・').map(item => item.trim()); + const remainList = ingredientsList.filter(item => syokuzai.includes(item)); return remainList.join('・'); } -// レシピのレスポンスをHTMLで整形して表示する関数 function displayRecipeResponse(response) { const responseDisplayElement = document.getElementById('response-display'); - // レスポンスをsyokuzaiに含まれる要素を減算する const reducedIngredients = reduceIngredients(response, syokuzai); const remainIngredientsList = remainIngredients(response, syokuzai); - // レスポンスを適切にHTMLフォーマットに変換 const formattedResponse = formatResponse(reducedIngredients); responseDisplayElement.innerHTML = `
    ${formattedResponse}
    `; - // レスポンスをそのまま表示する console.log("減算された材料リスト:", remainIngredientsList); } -// レスポンスをHTMLフォーマットに変換する関数 function formatResponse(response) { - // 改行を
    タグに変換 let formatted = response.replace(/\n/g, '
    '); - - // ・で区切られた材料をリスト形式に変換 formatted = formatted.split('・').map(item => `
  • ${item.trim()}
  • `).join(''); formatted = ''; return formatted; } -// displayMenu 関数 function displayMenu(menu1, menu2, menu3) { menu1Element.innerHTML = formatMenu(menu1); menu2Element.innerHTML = formatMenu(menu2); menu3Element.innerHTML = formatMenu(menu3); - // クリックイベントの追加 menu1Element.onclick = () => sendRecipeRequest(menu1); menu2Element.onclick = () => sendRecipeRequest(menu2); menu3Element.onclick = () => sendRecipeRequest(menu3); - // 献立の配列をconsole.logで表示 console.log("献立の配列:", [menu1, menu2, menu3]); } -// 献立のテキストを改行形式でフォーマットする関数 function formatMenu(menu) { - return menu.replace(/#/g, '
    #'); + return menu.replace(/#/g, '
    '); } -// レシピリクエストを送信する関数 async function sendRecipeRequest(menu) { const promptText = `${menu} この献立の料理のレシピの材料を送信してください。ただし数量や料理名は表示しないかつ、材料は必ず・で箇条書きしてください。食材の書き方は${syokuzai}の書き方に基づいてください。`; const aiResponse = await postChat({ promptText }); console.log(aiResponse); - // レスポンスをページ上の指定された場所に表示 displayRecipeResponse(aiResponse.content); } -// ボタン作成のコード(省略) - -const createButton = document.getElementById("selectButton"); -const choiceGenre = ["肉類", "野菜・果実類", "魚類", "乾物・海藻類", "きのこ・山菜類", "卵類", "いも類", "パン類", "ごはん類", "乳製品類", "豆・豆腐・豆腐加工品類", "麺類", "その他食材"]; - -// 各ジャンルに対応する食材リストをオブジェクトで定義 -const ingredients = { - "肉類": ["牛肉", "豚肉", "鶏肉", "ひき肉"], - "野菜・果実類": ["キャベツ", "人参", "レタス", "白菜", "玉ねぎ", "長ネギ", "もやし", "トマト", "きゅうり", "なす", "ピーマン", "かぼちゃ", "大根", "レンコン", "アボカド"], - "魚類": ["鮭・サーモン", "マグロ", "さば", "さんま", "ツナ缶", "エビ・カニ", "イカ・タコ", "貝類", "練り物"], - "乾物・海藻類": ["昆布", "ひじき", "わかめ", "お麩", "春雨"], - "きのこ・山菜類": ["しいたけ", "まいたけ", "しめじ", "エリンギ", "えのき", "山菜"], - "卵類": ["卵"], - "いも類": ["じゃがいも", "さつまいも", "里芋", "長芋・山芋", "こんにゃく"], - "パン類": ["食パン", "クロワッサン"], - "ごはん類": ["白米", "玄米", "もち米", "餅", "五穀米"], - "乳製品類": ["牛乳", "チーズ", "ヨーグルト", "生クリーム"], - "豆・豆腐・豆腐加工品類": ["豆腐", "納豆", "油揚げ", "厚揚げ"], - "麺類": ["うどん", "そば", "そうめん", "中華麺", "パスタ・スパゲティ"], +const createButton = document.getElementById('selectButton'); +const choiceGenre = { + "肉": ["鶏肉", "豚肉", "牛肉"], + "野菜・果実類": ["トマト", "ナス", "キャベツ", "にんじん"], + "魚類": ["サバ", "サーモン", "アジ"], + "乾物・海藻類": ["昆布", "干ししいたけ"], + "きのこ・山菜類": ["シイタケ", "エノキ", "ワラビ"], + "卵類": ["鶏卵", "うずら卵"], + "いも類": ["じゃがいも", "さつまいも"], + "パン類": ["食パン", "フランスパン"], + "ごはん類": ["白ごはん", "玄米"], + "乳製品類": ["牛乳", "ヨーグルト"], + "豆・豆腐・豆腐加工品類": ["豆腐", "納豆"], + "麺類": ["うどん", "そば", "スパゲッティ"], + "その他食材": ["みりん", "砂糖"] }; -// ジャンルボタンを作成 -choiceGenre.forEach((genre) => { - const newButton = document.createElement("button"); - newButton.textContent = genre; - newButton.type = "button"; - newButton.classList.add(genre); - createButton.appendChild(newButton); - - // 各ジャンルボタンのクリックイベントを設定 - newButton.onclick = () => { - // 既存の食材ボタンを削除 - const existingButtons = document.querySelectorAll(".select-btn"); - existingButtons.forEach(btn => btn.remove()); - - // ジャンルに対応する食材のボタンを生成 - if (ingredients[genre]) { - ingredients[genre].forEach((item) => { - const ingredientButton = document.createElement("button"); - ingredientButton.textContent = item; - ingredientButton.type = "button"; - ingredientButton.classList.add("select-btn"); - createButton.appendChild(ingredientButton); - }); - } - }; -}); -const selectedIngredients = []; -const showSelectedIngredients = document.getElementById("selectedIngredients"); - -document.addEventListener('click', function(event) { - if (event.target.matches('.select-btn')) { - const ingredient = event.target.textContent; - if (selectedIngredients.includes(ingredient)) { - console.log(`${ingredient}はすでに選択されています`); - } else { - selectedIngredients.push(ingredient); - - const selectedIngredientsList = document.createElement("li"); - selectedIngredientsList.textContent = ingredient; - - const deleteButton = document.createElement("button"); - deleteButton.textContent = "削除"; - deleteButton.classList.add("delete-btn"); - deleteButton.dataset.ingredient = ingredient; // 削除ボタンに食材情報を追加 - - selectedIngredientsList.appendChild(deleteButton); - showSelectedIngredients.appendChild(selectedIngredientsList); - - console.log(ingredient); - console.log(selectedIngredients); - } - } else if (event.target.matches('.delete-btn')) { - const ingredient = event.target.dataset.ingredient; - const index = selectedIngredients.indexOf(ingredient); - - if (index !== -1) { - selectedIngredients.splice(index, 1); // 配列から食材を削除 - event.target.parentElement.remove(); // リストアイテムを削除 - } - - console.log(ingredient); - console.log(selectedIngredients); - } +createButton.addEventListener("click", () => { + console.log("選択されたジャンル:", choiceGenre); }); submitButtonElement.onclick = async () => { - const cookTime = document.getElementById("cook-time-slider").value; - const peopleNumber = document.getElementById("people-number-slider").value; - const otherRequest = document.getElementById("more-request-message").value; - const otherMenu = document.getElementById("tuika").value; - - const menuCheckedBoxes = document.querySelectorAll('input[name="dish"]:checked'); - - // チェックがついたチェックボックスの値を配列に変換 - const menuList = Array.from(menuCheckedBoxes).map(checkbox => checkbox.value); - - // 配列をコンソールに出力 - console.log(menuList); - - const promptText = selectedIngredients.join("と") + "を用いた主菜を含む一食の献立を3つ提案してください。ただし、一つ一つの献立の始めには###を、終わりには---をつけて、わかりやすく表示してください。また、材料やレシピは表示せず、料理名のみ出力してください。ただし、献立の条件を以下のようにします。#調理時間は"+cookTime+"分以内であること。 #" + peopleNumber + "人分であること。 #" + menuList.join("と")+ "と他" + otherMenu + "品で構成されていること。 #" + otherRequest; - const aiChatMessage = await postChat({ promptText }); - addChatMessageElement("you", { content: promptText }); - addChatMessageElement("ai", aiChatMessage); - const responseString = aiChatMessage.content; - console.log(responseString); // これで返答をstring形式で取得できます - - // 献立を3つに分割(改行文字 "\n\n" を基準に分割する例) - const menuItems = responseString.match(/###([\s\S]*?)---/g).map(item => item.replace(/###|---/g, '').trim()); - - // 分割した献立をそれぞれ個別に保存または処理 - const menu1 = menuItems[0]; - const menu2 = menuItems[1]; - const menu3 = menuItems[2]; - - console.log("献立 1:", menu1); - console.log("献立 2:", menu2); - console.log("献立 3:", menu3); - - // 必要に応じて他の処理を追加 - // 3つの献立をページの下部に表示 - displayMenu(menu1, menu2, menu3); + const text = document.getElementById("text-input").value; + const response = await postChat({ promptText: text }); + addChatMessageElement("you", { content: text }); + addChatMessageElement("ai", response); }; - - - const cookTimeSlider = document.getElementById("cook-time-slider"); - const peopleNumberSlider = document.getElementById("people-number-slider"); - const cookTimeDisplay = document.getElementById('cook-time-display'); - const peopleNumberDisplay = document.getElementById('people-number-display'); - - // 初期表示 - cookTimeDisplay.textContent = cookTimeSlider.value + "分以内"; - peopleNumberDisplay.textContent = peopleNumberSlider.value + "人"; - - // イベントリスナーを追加 - cookTimeSlider.addEventListener('input', function() { - cookTimeDisplay.textContent = this.value + "分以内"; - }); - - peopleNumberSlider.addEventListener('input', function() { - peopleNumberDisplay.textContent = this.value + "人"; - }); \ No newline at end of file