import { useState, useEffect } from 'react' import styled, { keyframes } from 'styled-components' import { searchRecipes, listRecipes, recommendDbSingle, recommendDbMulti } from './services/api' import { Search, ChefHat, ArrowLeft, Utensils, Sparkles, SlidersHorizontal, HelpCircle, Zap, BookOpen, ChevronDown, ChevronUp, Check, X, Home } from 'lucide-react' import { motion, AnimatePresence } from 'framer-motion' // ========================= // ๐ŸŽจ ์Šคํƒ€์ผ ์ •์˜ // ========================= const Container = styled.div` max-width: 900px; margin: 0 auto; padding: 1.5rem; min-height: 100vh; @media (max-width: 768px) { padding: 1rem; } ` const Header = styled.header` margin-bottom: 2rem; cursor: pointer; text-align: center; ` const Title = styled.h1` font-size: 2.5rem; margin: 0; background: linear-gradient(135deg, #3b82f6, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; display: flex; align-items: center; justify-content: center; gap: 12px; @media (max-width: 768px) { font-size: 1.8rem; } ` const Subtitle = styled.p` font-size: 1.1rem; color: #64748b; margin-top: 0.5rem; ` const Card = styled(motion.div)` background: white; border-radius: 20px; padding: 1.5rem; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); margin-bottom: 1.5rem; @media (max-width: 768px) { padding: 1.2rem; border-radius: 16px; } ` const SectionTitle = styled.h3` font-size: 1rem; margin: 0 0 1rem 0; color: #334155; display: flex; align-items: center; gap: 8px; ` const SearchBar = styled.div` display: flex; gap: 10px; position: relative; ` const Input = styled.input` width: 100%; padding: 0.9rem 3rem 0.9rem 1rem; border-radius: 12px; border: 2px solid #e2e8f0; font-size: 1rem; font-family: inherit; transition: all 0.2s; &:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1); } ` const SearchBtn = styled.button` position: absolute; right: 6px; top: 50%; transform: translateY(-50%); background: #3b82f6; border: none; color: white; cursor: pointer; padding: 0.5rem; border-radius: 8px; display: flex; align-items: center; justify-content: center; &:hover { background: #2563eb; } ` const RecipeList = styled.div` display: grid; gap: 0.6rem; margin-top: 1rem; max-height: 400px; overflow-y: auto; ` const RecipeItem = styled(motion.div)` background: ${props => props.selected ? '#eff6ff' : '#f8fafc'}; padding: 1rem 1.2rem; border-radius: 12px; border: 2px solid ${props => props.selected ? '#3b82f6' : 'transparent'}; cursor: pointer; display: flex; justify-content: space-between; align-items: center; transition: all 0.2s; &:hover { border-color: #3b82f6; background: #eff6ff; } ` const RecipeId = styled.span` font-size: 0.75rem; color: #94a3b8; background: #f1f5f9; padding: 0.2rem 0.5rem; border-radius: 6px; ` const IngredientGrid = styled.div` display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; ` const IngredientChip = styled.button` background: ${props => props.selected ? '#3b82f6' : '#f1f5f9'}; color: ${props => props.selected ? 'white' : '#475569'}; border: none; padding: 0.4rem 0.8rem; border-radius: 999px; font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; gap: 4px; &:hover { background: ${props => props.selected ? '#2563eb' : '#e2e8f0'}; } ` const ActionButton = styled.button` width: 100%; margin-top: 1.2rem; padding: 0.9rem; border-radius: 12px; background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: white; font-size: 1rem; border: none; cursor: pointer; font-family: inherit; font-weight: 600; display: flex; align-items: center; justify-content: center; gap: 8px; &:disabled { background: #cbd5e1; cursor: not-allowed; } &:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); } ` const SliderContainer = styled.div` margin-top: 1.2rem; padding: 1rem; background: #f8fafc; border-radius: 12px; ` const SliderRow = styled.div` display: flex; align-items: center; gap: 0.8rem; margin-bottom: 0.8rem; &:last-child { margin-bottom: 0; } ` const SliderLabel = styled.div` min-width: 100px; font-size: 0.85rem; color: #475569; display: flex; align-items: center; gap: 4px; ` const Slider = styled.input` flex: 1; -webkit-appearance: none; height: 5px; border-radius: 3px; background: #e2e8f0; outline: none; &::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: #3b82f6; cursor: pointer; } ` const SliderValue = styled.span` min-width: 35px; text-align: right; font-weight: 600; color: #3b82f6; font-size: 0.9rem; ` const ResultCard = styled(motion.div)` background: white; border: 1px solid #e2e8f0; border-radius: 14px; padding: 1.2rem; margin-bottom: 0.8rem; ` const ResultHeader = styled.div` display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.8rem; ` const ResultName = styled.span` font-size: 1.1rem; font-weight: 700; color: #1e293b; ` const ScoreBadge = styled.span` background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: white; padding: 0.25rem 0.5rem; border-radius: 12px; font-weight: 600; font-size: 0.8rem; ` const ResultGrid = styled.div` display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.8rem; margin-top: 0.8rem; ` const CompactResultCard = styled(motion.div)` background: white; border: 1px solid #e2e8f0; border-radius: 12px; padding: 0.8rem 1rem; cursor: pointer; transition: all 0.2s; &:hover { border-color: #3b82f6; box-shadow: 0 2px 8px rgba(59, 130, 246, 0.1); } ` const CompactHeader = styled.div` display: flex; justify-content: space-between; align-items: center; ` const MedalIcon = styled.span` font-size: 1.2rem; margin-right: 4px; ` const ScoreBarMini = styled.div` display: flex; gap: 2px; margin-top: 0.5rem; ` const ScoreSegment = styled.div` height: 4px; flex: 1; border-radius: 2px; background: ${props => props.color}; opacity: ${props => props.value > 0.3 ? 1 : 0.3}; ` const TabPills = styled.div` display: flex; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 1rem; ` const TabPill = styled.button` padding: 0.4rem 0.8rem; border-radius: 999px; border: 2px solid ${props => props.active ? '#3b82f6' : '#e2e8f0'}; background: ${props => props.active ? '#eff6ff' : 'white'}; color: ${props => props.active ? '#3b82f6' : '#64748b'}; font-weight: 500; font-size: 0.85rem; cursor: pointer; transition: all 0.2s; &:hover { border-color: #3b82f6; } ` const ScoreRow = styled.div` display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.4rem; ` const ScoreLabel = styled.span` min-width: 90px; font-size: 0.8rem; color: #64748b; ` const ProgressBar = styled.div` flex: 1; height: 6px; background: #e2e8f0; border-radius: 3px; overflow: hidden; ` const ProgressFill = styled.div` height: 100%; background: ${props => props.color || '#3b82f6'}; width: ${props => props.value}%; transition: width 0.5s ease; ` const Tooltip = styled.div` position: relative; display: inline-flex; cursor: help; &:hover > div { display: block; } ` const TooltipContent = styled.div` display: none; position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: #1e293b; color: white; padding: 0.4rem 0.6rem; border-radius: 6px; font-size: 0.75rem; white-space: nowrap; z-index: 100; margin-bottom: 4px; ` const InfoBox = styled.div` background: ${props => props.variant === 'purple' ? '#f5f3ff' : '#eff6ff'}; border-left: 4px solid ${props => props.variant === 'purple' ? '#8b5cf6' : '#3b82f6'}; padding: 1rem; border-radius: 0 10px 10px 0; margin: 0.8rem 0; font-size: 0.85rem; color: ${props => props.variant === 'purple' ? '#5b21b6' : '#1e40af'}; line-height: 1.6; ` const pulse = keyframes` 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } ` const LoadingText = styled.span` animation: ${pulse} 1.5s ease-in-out infinite; ` const BackButton = styled.button` background: none; border: none; padding: 0; color: #64748b; display: flex; align-items: center; gap: 5px; cursor: pointer; font-size: 0.9rem; &:hover { color: #3b82f6; } ` const NavButtons = styled.div` display: flex; gap: 1rem; margin-bottom: 1rem; ` const HomeButton = styled.button` background: #f1f5f9; border: none; padding: 0.5rem 0.8rem; color: #64748b; display: flex; align-items: center; gap: 5px; cursor: pointer; font-size: 0.9rem; border-radius: 8px; &:hover { background: #e2e8f0; color: #3b82f6; } ` const ToggleHeader = styled.div` display: flex; align-items: center; justify-content: space-between; cursor: pointer; padding: 0.5rem 0; ` const TabContainer = styled.div` display: flex; gap: 0.5rem; margin-bottom: 1rem; ` const Tab = styled.button` flex: 1; padding: 0.7rem; border-radius: 10px; border: 2px solid ${props => props.active ? '#3b82f6' : '#e2e8f0'}; background: ${props => props.active ? '#eff6ff' : 'white'}; color: ${props => props.active ? '#3b82f6' : '#64748b'}; font-weight: 600; font-size: 0.9rem; cursor: pointer; transition: all 0.2s; &:hover { border-color: #3b82f6; } ` const MultiResultSection = styled.div` margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid #e2e8f0; &:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; } ` const TargetLabel = styled.div` font-size: 1rem; font-weight: 600; color: #1e293b; margin-bottom: 0.8rem; display: flex; align-items: center; gap: 6px; ` // ========================= // ๐Ÿง  ๋ฉ”์ธ ์•ฑ ์ปดํฌ๋„ŒํŠธ // ========================= function App() { const [step, setStep] = useState('main') // main, detail, result const [query, setQuery] = useState('') const [searchResults, setSearchResults] = useState([]) const [allRecipes, setAllRecipes] = useState([]) const [totalRecipes, setTotalRecipes] = useState(0) const [selectedRecipe, setSelectedRecipe] = useState(null) const [selectedIngs, setSelectedIngs] = useState([]) // ๋‹ค์ค‘ ์„ ํƒ const [recommendations, setRecommendations] = useState([]) const [multiRecommendations, setMultiRecommendations] = useState([]) // ๋‹ค์ค‘ ๋Œ€์ฒด ์กฐํ•ฉ ๊ฒฐ๊ณผ const [loading, setLoading] = useState(false) const [showWeights, setShowWeights] = useState(false) const [showAlgorithm, setShowAlgorithm] = useState(false) const [activeTab, setActiveTab] = useState('search') // search, browse const [activeResultTab, setActiveResultTab] = useState(0) // ๋‹ค์ค‘ ์žฌ๋ฃŒ ๊ฒฐ๊ณผ ํƒญ const [expandedCard, setExpandedCard] = useState(null) // ์ ์ˆ˜ ์ƒ์„ธ ๋ณด๊ธฐ // ๊ฐ€์ค‘์น˜ ์ƒํƒœ const [weights, setWeights] = useState({ w2v: 0.5, d2v: 0.5, method: 0.0, cat: 0.0 }) useEffect(() => { // ์ดˆ๊ธฐ ๋ ˆ์‹œํ”ผ ๋ชฉ๋ก ๋กœ๋“œ listRecipes(30, 0).then(res => { setAllRecipes(res.recipes || []) setTotalRecipes(res.total || 0) }) }, []) const handleSearch = async (e) => { e?.preventDefault() if (!query.trim()) return setLoading(true) const res = await searchRecipes(query) setSearchResults(res) setLoading(false) } const handleSelectRecipe = (recipe) => { setSelectedRecipe(recipe) setStep('detail') setSelectedIngs([]) setRecommendations([]) setMultiRecommendations([]) } const toggleIngredient = (ing) => { if (selectedIngs.includes(ing)) { setSelectedIngs(selectedIngs.filter(i => i !== ing)) } else { setSelectedIngs([...selectedIngs, ing]) } } const handleRecommend = async () => { if (!selectedRecipe || selectedIngs.length === 0) return setLoading(true) if (selectedIngs.length === 1) { // ๋‹จ์ผ ์ถ”์ฒœ const res = await recommendDbSingle( selectedRecipe.id, selectedIngs[0], weights.w2v, weights.d2v, weights.method, weights.cat ) setRecommendations(res) setMultiRecommendations([]) } else { // ๋‹ค์ค‘ ์ถ”์ฒœ - Beam Search ๊ธฐ๋ฐ˜ Multi API ์‚ฌ์šฉ const res = await recommendDbMulti( selectedRecipe.id, selectedIngs, weights.w2v, weights.d2v, weights.method, weights.cat ) setMultiRecommendations(res) setRecommendations([]) } setLoading(false) setStep('result') } const goBack = () => { if (step === 'result') setStep('detail') else if (step === 'detail') setStep('main') } const resetAll = () => { setStep('main') setSearchResults([]) setQuery('') setSelectedRecipe(null) setSelectedIngs([]) setRecommendations([]) setMultiRecommendations([]) } const loadMoreRecipes = async () => { const res = await listRecipes(30, allRecipes.length) setAllRecipes([...allRecipes, ...(res.recipes || [])]) } return (
<ChefHat size={32} color="#3b82f6" /> K-Recipe2Vec AI๊ฐ€ ์ถ”์ฒœํ•˜๋Š” ์ตœ์ ์˜ ๋Œ€์ฒด ์žฌ๋ฃŒ
{/* ========== ๋ฉ”์ธ ํ™”๋ฉด ========== */} {step === 'main' && ( {/* ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ค๋ช… ์„น์…˜ */} setShowAlgorithm(!showAlgorithm)}> ์ด ์„œ๋น„์Šค๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”? {showAlgorithm ? : } {showAlgorithm && ( ๐Ÿง  K-Recipe2Vec์ด๋ž€?
์•ฝ 8๋งŒ๊ฐœ์˜ ํ•œ์‹ ๋ ˆ์‹œํ”ผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•™์Šต๋œ AI ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค. Word2Vec๊ณผ Doc2Vec์„ ํ™œ์šฉํ•˜์—ฌ ์žฌ๋ฃŒ ๊ฐ„์˜ ์˜๋ฏธ์  ์œ ์‚ฌ๋„์™€ ๋ ˆ์‹œํ”ผ ๋ฌธ๋งฅ์—์„œ์˜ ์ƒํ˜ธ ๋Œ€์ฒด ๊ฐ€๋Šฅ์„ฑ์„ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Š ์ ์ˆ˜ ๊ตฌ์„ฑ ์š”์†Œ:

  • ์žฌ๋ฃŒ ์œ ์‚ฌ๋„ (W2V): Word2Vec์œผ๋กœ ํ•™์Šตํ•œ ์žฌ๋ฃŒ ๊ฐ„ ์˜๋ฏธ์  ๊ฑฐ๋ฆฌ. ์˜ˆ) ๋ผ์ง€๊ณ ๊ธฐ โ†” ์†Œ๊ณ ๊ธฐ
  • ๋ฌธ๋งฅ ์œ ์‚ฌ๋„ (D2V): Doc2Vec์œผ๋กœ ํ•™์Šตํ•œ ๋ ˆ์‹œํ”ผ ๋ฌธ๋งฅ. ๊ฐ™์€ ์š”๋ฆฌ์—์„œ ํ•จ๊ป˜ ์“ฐ์ด๋Š” ๋นˆ๋„ ๋ฐ˜์˜
  • ์กฐ๋ฆฌ๋ฒ• ์ ํ•ฉ (Method): ์ฐœ, ๋ณถ์Œ, ๊ตฌ์ด ๋“ฑ ๊ฐ™์€ ์กฐ๋ฆฌ๋ฒ•์—์„œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์ •๋„
  • ์นดํ…Œ๊ณ ๋ฆฌ ์ ํ•ฉ (Category): ์ฐŒ๊ฐœ, ๋ฐ˜์ฐฌ ๋“ฑ ๊ฐ™์€ ์š”๋ฆฌ ์ข…๋ฅ˜์—์„œ์˜ ์‚ฌ์šฉ ๋นˆ๋„

โš™๏ธ ๊ณ ๊ธ‰ ์„ค์ •์—์„œ ๊ฐ ์ ์ˆ˜์˜ ๊ฐ€์ค‘์น˜๋ฅผ ์กฐ์ ˆํ•˜์—ฌ ์›ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ถ”์ฒœ ๊ฒฐ๊ณผ๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

)}
{/* ๋ ˆ์‹œํ”ผ ์„ ํƒ */} setActiveTab('search')}> ์š”๋ฆฌ๋ช… ๊ฒ€์ƒ‰ setActiveTab('browse')}> ์ „์ฒด ๋ ˆ์‹œํ”ผ {activeTab === 'search' && ( <>
setQuery(e.target.value)} />
{loading && ๐Ÿ” ๊ฒ€์ƒ‰ ์ค‘...} {searchResults.map(recipe => ( handleSelectRecipe(recipe)} whileTap={{ scale: 0.98 }} >
{recipe.name} #{recipe.id}
์žฌ๋ฃŒ {recipe.ingredients.length}๊ฐœ
))} {searchResults.length === 0 && !loading && query && (
๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค
)}
)} {activeTab === 'browse' && ( <>
์ „์ฒด {totalRecipes.toLocaleString()}๊ฐœ ๋ ˆ์‹œํ”ผ ์ค‘ {allRecipes.length}๊ฐœ ํ‘œ์‹œ
{allRecipes.map(recipe => ( handleSelectRecipe(recipe)} whileTap={{ scale: 0.98 }} >
{recipe.name} #{recipe.id}
์žฌ๋ฃŒ {recipe.ingredients.length}๊ฐœ
))}
{allRecipes.length < totalRecipes && ( ๋” ๋ถˆ๋Ÿฌ์˜ค๊ธฐ )} )}
)} {/* ========== ์žฌ๋ฃŒ ์„ ํƒ ๋‹จ๊ณ„ ========== */} {step === 'detail' && selectedRecipe && ( ๋’ค๋กœ ํ™ˆ์œผ๋กœ

{selectedRecipe.name}

#{selectedRecipe.id}

๋Œ€์ฒดํ•  ์žฌ๋ฃŒ๋ฅผ ์„ ํƒํ•˜์„ธ์š” (์—ฌ๋Ÿฌ ๊ฐœ ์„ ํƒ ๊ฐ€๋Šฅ)

{selectedRecipe.ingredients.map((ing, idx) => ( toggleIngredient(ing)} > {selectedIngs.includes(ing) && } {ing} ))} {selectedIngs.length > 0 && (
์„ ํƒ๋œ ์žฌ๋ฃŒ ({selectedIngs.length}๊ฐœ):
{selectedIngs.map((ing, idx) => ( toggleIngredient(ing)} > {ing} ))}
)} {/* ๊ฐ€์ค‘์น˜ ์„ค์ • */} setShowWeights(!showWeights)}> ๊ณ ๊ธ‰ ์„ค์ • (๊ฐ€์ค‘์น˜ ์กฐ์ ˆ) {showWeights ? : } {showWeights && ( ์žฌ๋ฃŒ ๊ฐ„ ์˜๋ฏธ์  ์œ ์‚ฌ๋„ ์žฌ๋ฃŒ ์œ ์‚ฌ๋„ setWeights({ ...weights, w2v: parseFloat(e.target.value) })} /> {weights.w2v.toFixed(1)} ๋ ˆ์‹œํ”ผ ๋ฌธ๋งฅ ์œ ์‚ฌ๋„ ๋ฌธ๋งฅ ์œ ์‚ฌ๋„ setWeights({ ...weights, d2v: parseFloat(e.target.value) })} /> {weights.d2v.toFixed(1)} ์กฐ๋ฆฌ ๋ฐฉ๋ฒ• ์ ํ•ฉ๋„ ์กฐ๋ฆฌ๋ฒ• ์ ํ•ฉ setWeights({ ...weights, method: parseFloat(e.target.value) })} /> {weights.method.toFixed(1)} ์š”๋ฆฌ ์นดํ…Œ๊ณ ๋ฆฌ ์ ํ•ฉ๋„ ์นดํ…Œ๊ณ ๋ฆฌ ์ ํ•ฉ setWeights({ ...weights, cat: parseFloat(e.target.value) })} /> {weights.cat.toFixed(1)} )} {loading ? ( ๋ถ„์„ ์ค‘... ) : ( <> {selectedIngs.length > 0 ? `${selectedIngs.length}๊ฐœ ์žฌ๋ฃŒ ๋Œ€์ฒด ์ถ”์ฒœ๋ฐ›๊ธฐ` : '์žฌ๋ฃŒ๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”'} )}
)} {/* ========== ๊ฒฐ๊ณผ ๋‹จ๊ณ„ ========== */} {step === 'result' && ( ๋’ค๋กœ ํ™ˆ์œผ๋กœ

์ด๋Ÿฐ ์žฌ๋ฃŒ๋กœ ๋Œ€์ฒดํ•ด๋ณด์„ธ์š”

{selectedRecipe.name} (#{selectedRecipe.id})

{/* ๋‹จ์ผ ์žฌ๋ฃŒ ๊ฒฐ๊ณผ - ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ */} {recommendations.length > 0 && ( <> "{selectedIngs[0]}" โ†’ {expandedCard !== null && recommendations[expandedCard] ? {recommendations[expandedCard]['๋Œ€์ฒด์žฌ๋ฃŒ']} : '๋Œ€์ฒด ์ถ”์ฒœ'} {recommendations.map((rec, idx) => ( setExpandedCard(expandedCard === idx ? null : idx)} > {idx === 0 && '๐Ÿฅ‡'} {idx === 1 && '๐Ÿฅˆ'} {idx === 2 && '๐Ÿฅ‰'} {idx > 2 && `${idx + 1}.`} {rec['๋Œ€์ฒด์žฌ๋ฃŒ']} {(rec['์ตœ์ข…์ ์ˆ˜'] * 100).toFixed(0)}์  {expandedCard === idx && ( ์žฌ๋ฃŒ ์œ ์‚ฌ๋„ {((rec['W2V'] || 0) * 100).toFixed(0)}% ๋ฌธ๋งฅ ์œ ์‚ฌ๋„ {((rec['D2V'] || 0) * 100).toFixed(0)}% ์กฐ๋ฆฌ๋ฒ• ์ ํ•ฉ {((rec['Method'] || 0) * 100).toFixed(0)}% ์นดํ…Œ๊ณ ๋ฆฌ ์ ํ•ฉ {((rec['Category'] || 0) * 100).toFixed(0)}% )} ))}

์นด๋“œ๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ƒ์„ธ ์ ์ˆ˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”

)} {/* ๋‹ค์ค‘ ์žฌ๋ฃŒ ๊ฒฐ๊ณผ - Beam Search ์กฐํ•ฉ ํ‘œ์‹œ */} {multiRecommendations.length > 0 && ( <> {selectedIngs.join(' + ')} โ†’ {expandedCard !== null && multiRecommendations[expandedCard] ? {multiRecommendations[expandedCard].substitutes.join(' + ')} : '์ตœ์  ๋Œ€์ฒด ์กฐํ•ฉ'}
{multiRecommendations.map((combo, idx) => ( setExpandedCard(expandedCard === idx ? null : idx)} >
{idx === 0 && '๐Ÿฅ‡'} {idx === 1 && '๐Ÿฅˆ'} {idx === 2 && '๐Ÿฅ‰'} ์กฐํ•ฉ {idx + 1}
{(combo.score * 100).toFixed(0)}์ 
{selectedIngs.map((origIng, i) => (
{origIng} โ†’ {combo.substitutes[i]}
))}
{expandedCard === idx && (
์ถ”์ฒœ ๊ธฐ์ค€
ํ‰๊ท  ์œ ์‚ฌ๋„ ์ ์ˆ˜ {(combo.score * 100).toFixed(1)}%
์กฐํ•ฉ ์ˆœ์œ„ {idx + 1}์œ„ / {multiRecommendations.length}๊ฐœ
Beam Search๊ฐ€ ๊ฐ ์žฌ๋ฃŒ์˜ W2V, D2V, Method, Category ์ ์ˆ˜๋ฅผ ์ข…ํ•ฉํ•˜์—ฌ ์ตœ์ ์˜ ์กฐํ•ฉ์„ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.
)}
))}

์นด๋“œ๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ƒ์„ธ ์ •๋ณด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”

)} {recommendations.length === 0 && multiRecommendations.length === 0 && (
์ถ”์ฒœ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์žฌ๋ฃŒ๋ฅผ ์„ ํƒํ•ด ์ฃผ์„ธ์š”.
)}
)}
) } export default App