From 928036c25bd8e798269c72322760a7d8af01f6af Mon Sep 17 00:00:00 2001
From: Tsurubara-UTcode <168950066+Tsurubara-UTcode@users.noreply.github.com>
Date: Sat, 31 Aug 2024 23:38:25 +0900
Subject: [PATCH 1/3] easy image recognition
---
public/script.js | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/public/script.js b/public/script.js
index a472398..8a01276 100644
--- a/public/script.js
+++ b/public/script.js
@@ -109,6 +109,27 @@ async function sendRecipeRequest(menu) {
displayRecipeResponse(aiResponse.content);
}
+// 食材認識の関数
+async function recognizeIngredients(imageElement) {
+ const model = await cocoSsd.load();
+ const predictions = await model.detect(imageElement);
+
+ const recognizedIngredients = predictions.map(prediction => prediction.class);
+ console.log("認識された食材:", recognizedIngredients);
+
+ return recognizedIngredients;
+}
+
+function addIngredientsToSyokuzai(ingredients) {
+ ingredients.forEach(ingredient => {
+ if (!syokuzai.includes(ingredient)) {
+ syokuzai.push(ingredient);
+ }
+ });
+ console.log("更新されたsyokuzai:", syokuzai);
+}
+
+
// ボタン作成のコード(省略)
const createButton = document.getElementById("selectButton");
From a44f28057def5a0eae294cc0f01cfc6366cd2f82 Mon Sep 17 00:00:00 2001
From: Tsurubara-UTcode <168950066+Tsurubara-UTcode@users.noreply.github.com>
Date: Sun, 1 Sep 2024 10:59:07 +0900
Subject: [PATCH 2/3] =?UTF-8?q?=E7=B0=A1=E6=98=93=E7=9A=84=E3=81=AA?=
=?UTF-8?q?=E7=94=BB=E5=83=8F=E8=AA=8D=E8=AD=98=E3=81=A8=E9=A3=9F=E6=9D=90?=
=?UTF-8?q?=E3=81=AE=E9=85=8D=E5=88=97=E3=81=AB=E8=BF=BD=E5=8A=A0=E3=81=99?=
=?UTF-8?q?=E3=82=8B=E6=A9=9F=E8=83=BD=E3=82=92=E5=AE=9F=E8=A3=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
public/index.html | 7 +++++
public/script.js | 70 +++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 72 insertions(+), 5 deletions(-)
diff --git a/public/index.html b/public/index.html
index 15a108d..90b648d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,6 +4,8 @@
AI チャット
+
+
AI チャット
-
-
-
-
+
+
+
-
+ 選択されたジャンル
選択した食材
-
-
@@ -109,14 +106,12 @@
AI チャット
-
-
-
-
+
+
diff --git a/public/script.js b/public/script.js
index ad10206..b897519 100644
--- a/public/script.js
+++ b/public/script.js
@@ -4,15 +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 imageUploadInput = document.getElementById("image-upload-input");
-const imageElement = document.getElementById("image-element"); // 画像を表示する要素
+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");
@@ -29,306 +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);
});
- return await response.json();
}
-// 材料の配列からsyokuzaiの要素を減算する関数
+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);
+ }
+ });
+ console.log("更新されたsyokuzai:", syokuzai);
+}
+
+function displayRecognizedIngredients(ingredients) {
+ const recognizedIngredientsElement = document.getElementById("recognized-ingredients");
+ recognizedIngredientsElement.innerHTML = ingredients.map(ingredient => `${ingredient}`).join('');
+}
+
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);
}
-async function recognizeIngredients(imageElement) {
- const model = await cocoSsd.load(); // coco-ssdモデルのロード
- const predictions = await model.detect(imageElement); // 画像から予測
-
- const recognizedIngredients = predictions.map(prediction => prediction.class); // 認識されたクラス(食材名)を取得
- console.log("認識された食材:", recognizedIngredients);
-
- return recognizedIngredients; // 認識された食材を返す
-}
-
-// 食材認識の関数
-imageUploadInput.addEventListener('change', async (event) => {
- const file = event.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = async function(e) {
- imageElement.src = e.target.result; // 画像を表示
- await processImageRecognition(imageElement); // 画像を認識して処理する
- };
- reader.readAsDataURL(file);
- }
-});
-
-function addIngredientsToSyokuzai(ingredients) {
- ingredients.forEach(ingredient => {
- if (!syokuzai.includes(ingredient)) {
- syokuzai.push(ingredient);
- }
- });
- console.log("更新されたsyokuzai:", syokuzai);
-}
-
-// 画像認識と翻訳、追加の処理を行う関数
-async function processImageRecognition(imageElement) {
- // 食材を認識
- const recognizedIngredients = await recognizeIngredients(imageElement);
-
- // 認識された食材を日本語に変換(必要に応じて)
- const translatedIngredients = recognizedIngredients.map(ingredient => translateToJapanese(ingredient));
-
- // 認識された食材をsyokuzai配列に追加
- addIngredientsToSyokuzai(translatedIngredients);
-
- // 追加した食材を表示
- displayRecognizedIngredients(translatedIngredients);
-}
-
-// 英語の食材名を日本語に変換する簡易関数(実際の翻訳マッピングは詳細に設定する必要があります)
-function translateToJapanese(ingredient) {
- const translationMap = {
- "pork": "豚肉",
- "chicken": "鶏肉",
- "beef": "牛肉",
- "onion": "タマネギ",
- "carrot": "人参",
- "potato": "じゃがいも",
- "salmon": "サーモン",
- "eggplant": "なす",
- "broccoli": "ブロッコリー",
- "cucumber": "きゅうり",
- // 必要に応じて他の翻訳を追加
- };
-
- return translationMap[ingredient.toLowerCase()] || ingredient; // 翻訳が見つからない場合は元の名前を返す
-}
-
-
-
-// 認識された食材を表示する関数
-function displayRecognizedIngredients(ingredients) {
- const recognizedIngredientsElement = document.getElementById("recognized-ingredients");
- recognizedIngredientsElement.innerHTML = ingredients.map(ingredient => `${ingredient}`).join('');
-}
-
-
-
-// ボタン作成のコード(省略)
-
-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