diff --git a/backend/build.gradle b/backend/build.gradle index 3ed75bda..e1596020 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -40,6 +40,8 @@ dependencies { implementation 'org.hibernate.validator:hibernate-validator-cdi:6.2.0.Final' implementation 'io.github.flashvayne:chatgpt-spring-boot-starter:1.0.4' + + implementation 'org.jsoup:jsoup:1.15.3' } tasks.named('test') { diff --git a/backend/src/main/java/hello/aimju/chat/chat_message/domain/ChatType.java b/backend/src/main/java/hello/aimju/chat/chat_message/domain/ChatType.java index 5064a7ac..60593b38 100644 --- a/backend/src/main/java/hello/aimju/chat/chat_message/domain/ChatType.java +++ b/backend/src/main/java/hello/aimju/chat/chat_message/domain/ChatType.java @@ -6,5 +6,6 @@ public enum ChatType { menu, ingredient, recipe, + imageUrl, link } \ No newline at end of file diff --git a/backend/src/main/java/hello/aimju/controller/CrawlingController.java b/backend/src/main/java/hello/aimju/controller/CrawlingController.java new file mode 100644 index 00000000..14f2941c --- /dev/null +++ b/backend/src/main/java/hello/aimju/controller/CrawlingController.java @@ -0,0 +1,25 @@ +package hello.aimju.controller; + +import hello.aimju.image.Crawling.Service.CrawlingService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@RestController +@RequiredArgsConstructor +@RequestMapping(value = "/api") +public class CrawlingController { + + private final CrawlingService crawlingService; + + @GetMapping("/menu-image/{query}") + public String fetchImage(@PathVariable("query") String query) { + try { + return crawlingService.fetchImageUrl(query); + } catch (IOException e) { + e.printStackTrace(); + return "Error: Unable to fetch image"; + } + } +} diff --git a/backend/src/main/java/hello/aimju/image/Crawling/Service/CrawlingService.java b/backend/src/main/java/hello/aimju/image/Crawling/Service/CrawlingService.java new file mode 100644 index 00000000..5cc609c0 --- /dev/null +++ b/backend/src/main/java/hello/aimju/image/Crawling/Service/CrawlingService.java @@ -0,0 +1,42 @@ +package hello.aimju.image.Crawling.Service; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +@Service +public class CrawlingService { + + public String fetchImageUrl(String query) throws IOException { + // 쿼리 인코딩 + String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8.toString()); + System.out.println("Original query: " + query); + System.out.println("Encoded query: " + encodedQuery); + + // URL 생성 + String url = "https://www.10000recipe.com/recipe/list.html?q=" + encodedQuery; + System.out.println("URL: " + url); + + // 페이지 가져오기 + Document doc = Jsoup.connect(url) + .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36") + .get(); + System.out.println("Document fetched"); + + // 이미지 URL 추출 + Element imgElement = doc.select("div.common_sp_thumb img").first(); + if (imgElement != null) { + String imageUrl = imgElement.attr("src"); + System.out.println("Image URL: " + imageUrl); + return imageUrl; + } else { + System.out.println("No image element found"); + return null; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/boards/page.tsx b/frontend/src/app/boards/page.tsx index 64850864..9f901d31 100755 --- a/frontend/src/app/boards/page.tsx +++ b/frontend/src/app/boards/page.tsx @@ -38,15 +38,15 @@ type Board = { export default function Board() { const [currentPage, setCurrentPage] = useState(0); const [totalPages, setTotalPages] = useState(0); - const [searchKeyword, setSearchKeyword] = useState(null); const [boards, setBoards] = useState([]); const {getState} = useUserStore; const ownBoard = useBoardStore((state) => state.ownBoard); + const [searchKeyword, setSearchKeyword] = useState(''); - const getBoards = async (page = 0, searchKeyword = null)=>{ + const getBoards = async (page = 0)=>{ try { let response; - if(searchKeyword === null){ + if(searchKeyword === ''){ response = await axios.get('/api/boards', { params: { page: page, @@ -76,10 +76,10 @@ export default function Board() { } }; - const getOwnBoards = async (page = 0, searchKeyword = null) => { + const getOwnBoards = async (page = 0) => { try { let response; - if (searchKeyword === null) { + if (searchKeyword === '') { response = await axios.get('/api/boards/current-user', { params: { page: page, @@ -119,26 +119,50 @@ export default function Board() { const handlePrevPage = () => { if (ownBoard === 1) { - getOwnBoards(currentPage - 1, searchKeyword); + getOwnBoards(currentPage - 1); } else { - getBoards(currentPage - 1, searchKeyword); + getBoards(currentPage - 1); } setCurrentPage(currentPage - 1); } const handleNextPage = () => { if (ownBoard === 1) { - getOwnBoards(currentPage + 1, searchKeyword); + getOwnBoards(currentPage + 1); } else { - getBoards(currentPage + 1, searchKeyword); + getBoards(currentPage + 1); } setCurrentPage(currentPage + 1); } + const handleSearchChange = (e: React.ChangeEvent) => { + setSearchKeyword(e.target.value); + }; + return ( <>
+
{ + event.preventDefault() + if (ownBoard === 1) { + getOwnBoards(); + } else { + getBoards(); + } + }} className="mb-6 flex items-center justify-center"> + + +
{boards.map((board, index)=>( diff --git a/frontend/src/app/boards/writing/[recipeId]/page.tsx b/frontend/src/app/boards/writing/[recipeId]/page.tsx index ae96abed..d361aaf8 100644 --- a/frontend/src/app/boards/writing/[recipeId]/page.tsx +++ b/frontend/src/app/boards/writing/[recipeId]/page.tsx @@ -15,7 +15,9 @@ export default function PostWriting({ params }: { params: { recipeId: number } } const [selectedRecipe, setSelectedRecipe] = useState(null); // 선택된 레시피 ID useEffect(() => { - if (params.recipeId !== 0) { + const recipeId = Number(params.recipeId); + if (recipeId !== 0) { + console.log(params.recipeId); const fetchRecipeContent = async () => { try { const response = await axios.get(`/api/recipe/${params.recipeId}`); diff --git a/frontend/src/app/chatroom/[chatroomId]/page.tsx b/frontend/src/app/chatroom/[chatroomId]/page.tsx index 52441c4a..85c95528 100644 --- a/frontend/src/app/chatroom/[chatroomId]/page.tsx +++ b/frontend/src/app/chatroom/[chatroomId]/page.tsx @@ -64,12 +64,17 @@ export default function ChatRoom({ params }: { params: { chatroomId: number } }) ...message, content: `${menuString} 검색하기` }; + } else if (message.chatType === "imageUrl") { + return { + ...message, + content: `Chat Image` + }; } else { return message; } }); - setChatMessages(messagesWithLinks); + }) .catch(error => { alert('비정상적인 접근입니다.'); @@ -86,9 +91,16 @@ export default function ChatRoom({ params }: { params: { chatroomId: number } }) {chatMessages.map((message, index) => ( -
+
+ {message.isUser !== 1 && ( + + + ML + + )}
-
+
{message.isUser === 1 && ( @@ -96,12 +108,6 @@ export default function ChatRoom({ params }: { params: { chatroomId: number } }) YU )} - {message.isUser !== 1 && ( - - - ML - - )}
))}
diff --git a/frontend/src/app/favicon.ico b/frontend/src/app/favicon.ico deleted file mode 100644 index 718d6fea..00000000 Binary files a/frontend/src/app/favicon.ico and /dev/null differ diff --git a/frontend/src/app/favicon.png b/frontend/src/app/favicon.png new file mode 100644 index 00000000..32f5c84c Binary files /dev/null and b/frontend/src/app/favicon.png differ diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 3314e478..f29e3637 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -5,8 +5,11 @@ import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "AI 명종원", + description: "당신의 재료로 레시피를 추천합니다.", + icons: { + icon: "/favicon.png", + }, }; export default function RootLayout({ diff --git a/frontend/src/app/recommend/page.tsx b/frontend/src/app/recommend/page.tsx index a2e46641..d46f43fa 100755 --- a/frontend/src/app/recommend/page.tsx +++ b/frontend/src/app/recommend/page.tsx @@ -37,6 +37,8 @@ export default function Recommend() { const [uploadButtonEnabled, setUploadButtonEnabled] = useState(true); const [yesOrNoButtonEnabled, setYesOrNoButtonEnabled] = useState(true); const [recipeButtonEnabled, setRecipeButtonEnabled] = useState(true); + const [imageUrl, setImageUrl] = useState(''); + const router = useRouter(); const handleRoutingMain = () => { @@ -103,6 +105,17 @@ export default function Recommend() { menu: menu, ingredients: middleIngredients.split(', '), }; + + try { + const routingPoint = '/api/menu-image/' + menu + console.log(routingPoint) + const response = await axios.get(routingPoint); // menu 값 사용 + const imageUrl = response.data; + setImageUrl(imageUrl); + console.log(imageUrl); + } catch (error) { + console.error('Error fetching image:', error); + } try { const response = await axios.post('/api/recommendation-recipe-str', request); setRecipeString(response.data); @@ -140,6 +153,9 @@ export default function Recommend() { addChatMessage(`해당 재료로 만들 수 있는 음식은 다음과 같습니다.\n${menus}\n어떤 재료의 음식의 레시피를 보시겠습니까?`, 0, 'message'); addChatMessage(`${menu}`, 1, 'menu'); addChatMessage(`${menu}의 레시피는 다음과 같습니다.`, 0, 'message'); + if (imageUrl !== null && imageUrl !== undefined) { + addChatMessage(imageUrl, 0, 'imageUrl'); + } addChatMessage(`${recipeString}`, 0, 'recipe'); addChatMessage(`${recipeLink}`, 0,'link'); }, [recipeLink]); @@ -403,6 +419,15 @@ export default function Recommend() {
+ {imageUrl && ( +
+
+
+ My Image +
+
+
+ )}

{menu}의 레시피는 다음과 같습니다.

{recipeString}