IDEA-DESIGN / app.py
ginipick's picture
Create app.py
16f04d2 verified
raw
history blame
50.8 kB
import os
import json
import re
import logging
import requests
import markdown
import time
import io
import random
import hashlib
from datetime import datetime
from dataclasses import dataclass
from itertools import combinations, product
from typing import Iterator
import streamlit as st
import pandas as pd
import PyPDF2 # For handling PDF files
from collections import Counter
from openai import OpenAI # OpenAI 라이브러리
from gradio_client import Client
from kaggle.api.kaggle_api_extended import KaggleApi
import tempfile
import glob
import shutil
# ─── μΆ”κ°€λœ 라이브러리(μ ˆλŒ€ λˆ„λ½ κΈˆμ§€) ───────────────────────────────
import pyarrow.parquet as pq
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# ─────────────────────────────── Environment Variables / Constants ─────────────────────────
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
BRAVE_KEY = os.getenv("SERPHOUSE_API_KEY", "") # Brave Search API
KAGGLE_USERNAME = os.getenv("KAGGLE_USERNAME", "")
KAGGLE_KEY = os.getenv("KAGGLE_KEY", "")
KAGGLE_API_KEY = KAGGLE_KEY
if not (KAGGLE_USERNAME and KAGGLE_KEY):
raise RuntimeError("⚠️ KAGGLE_USERNAMEκ³Ό KAGGLE_KEY ν™˜κ²½λ³€μˆ˜λ₯Ό λ¨Όμ € μ„€μ •ν•˜μ„Έμš”.")
os.environ["KAGGLE_USERNAME"] = KAGGLE_USERNAME
os.environ["KAGGLE_KEY"] = KAGGLE_KEY
BRAVE_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"
IMAGE_API_URL = "http://211.233.58.201:7896" # μ˜ˆμ‹œ 이미지 μƒμ„±μš© API
MAX_TOKENS = 7999
# ─────────────────────────────── Logging ───────────────────────────────
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
# ─────────────────────────────── ꡰ사(밀리터리) μ „μˆ  데이터셋 λ‘œλ“œ ─────────────────
@st.cache_resource
def load_military_dataset():
"""
mil.parquet (index, scenario_description, attack_reasoning, defense_reasoning)
"""
path = os.path.join(os.path.dirname(__file__), "mil.parquet")
if not os.path.exists(path):
logging.warning("mil.parquet not found – military support disabled.")
return None
try:
df = pq.read_table(path).to_pandas()
return df
except Exception as e:
logging.error(f"Failed to read mil.parquet: {e}")
return None
MIL_DF = load_military_dataset()
def is_military_query(text: str) -> bool:
"""ꡰ사/μ „μˆ  κ΄€λ ¨ ν‚€μ›Œλ“œκ°€ λ“±μž₯ν•˜λ©΄ True λ°˜ν™˜"""
kw = [
"ꡰ사", "μ „μˆ ", "μ „νˆ¬", "μ „μŸ", "μž‘μ „", "무기", "병λ ₯",
"military", "tactic", "warfare", "battle", "operation"
]
return any(k.lower() in text.lower() for k in kw)
def military_search(query: str, top_k: int = 3):
"""
mil.parquet의 scenario_description μ—΄κ³Ό 코사인 μœ μ‚¬λ„ λΆ„μ„ν•˜μ—¬
query와 κ°€μž₯ μœ μ‚¬ν•œ μƒμœ„ μ‹œλ‚˜λ¦¬μ˜€λ₯Ό λ°˜ν™˜
"""
if MIL_DF is None:
return []
try:
corpus = MIL_DF["scenario_description"].tolist()
vec = TfidfVectorizer().fit_transform([query] + corpus)
sims = cosine_similarity(vec[0:1], vec[1:]).flatten()
top_idx = sims.argsort()[-top_k:][::-1]
return MIL_DF.iloc[top_idx][[
"scenario_description",
"attack_reasoning",
"defense_reasoning"
]].to_dict("records")
except Exception as e:
logging.error(f"military_search error: {e}")
return []
# ─────────────────────────────── Kaggle Datasets ────────────────────────
KAGGLE_DATASETS = {
"general_business": {
"ref": "mohammadgharaei77/largest-2000-global-companies",
"title": "Largest 2000 Global Companies",
"subtitle": "Comprehensive data about the world's largest companies",
"url": "https://www.kaggle.com/datasets/mohammadgharaei77/largest-2000-global-companies",
"keywords": ["business", "company", "corporation", "enterprise", "global", "λΉ„μ¦ˆλ‹ˆμŠ€", "κΈ°μ—…", "νšŒμ‚¬", "κΈ€λ‘œλ²Œ", "κΈ°μ—…κ°€μΉ˜"]
},
"global_development": {
"ref": "michaelmatta0/global-development-indicators-2000-2020",
"title": "Global Development Indicators (2000-2020)",
"subtitle": "Economic and social indicators for countries worldwide",
"url": "https://www.kaggle.com/datasets/michaelmatta0/global-development-indicators-2000-2020",
"keywords": ["development", "economy", "global", "indicators", "social", "경제", "λ°œμ „", "μ§€ν‘œ", "μ‚¬νšŒ", "κ΅­κ°€", "κΈ€λ‘œλ²Œ"]
},
"startup_ideas": {
"ref": "rohitsahoo/100-startup-ideas",
"title": "Startup Idea Generator Dataset",
"subtitle": "A variety of startup ideas",
"url": "https://www.kaggle.com/datasets/rohitsahoo/100-startup-ideas",
"keywords": ["startup", "innovation", "business idea", "entrepreneurship", "μŠ€νƒ€νŠΈμ—…", "μ°½μ—…", "ν˜μ‹ ", "아이디어", "κΈ°μ—…κ°€"]
},
"legal_terms": {
"ref": "gu05087/korean-legal-terms",
"title": "Korean Legal Terms",
"subtitle": "Database of Korean legal terminology",
"url": "https://www.kaggle.com/datasets/gu05087/korean-legal-terms",
"keywords": ["legal", "law", "terms", "korean", "legislation", "법λ₯ ", "법적", "ν•œκ΅­", "μš©μ–΄", "규제"]
},
"billionaires": {
"ref": "vincentcampanaro/forbes-worlds-billionaires-list-2024",
"title": "Forbes World's Billionaires List 2024",
"subtitle": "Comprehensive data on the world's wealthiest individuals",
"url": "https://www.kaggle.com/datasets/vincentcampanaro/forbes-worlds-billionaires-list-2024",
"keywords": ["billionaire", "wealth", "rich", "forbes", "finance", "λΆ€μž", "μ–΅λ§Œμž₯자", "포브슀", "λΆ€", "μž¬ν…Œν¬"]
},
"financial_news": {
"ref": "thedevastator/uncovering-financial-insights-with-the-reuters-2",
"title": "Reuters Financial News Insights",
"subtitle": "Financial news and market analysis from Reuters",
"url": "https://www.kaggle.com/datasets/thedevastator/uncovering-financial-insights-with-the-reuters-2",
"keywords": ["finance", "market", "stock", "investment", "news", "금육", "μ‹œμž₯", "주식", "투자", "λ‰΄μŠ€"]
},
"ecommerce": {
"ref": "oleksiimartusiuk/80000-products-e-commerce-data-clean",
"title": "80,000 Products E-Commerce Data",
"subtitle": "Clean dataset of e-commerce products information",
"url": "https://www.kaggle.com/datasets/oleksiimartusiuk/80000-products-e-commerce-data-clean",
"keywords": ["ecommerce", "product", "retail", "shopping", "online", "이컀머슀", "μ œν’ˆ", "μ†Œλ§€", "μ‡Όν•‘", "온라인"]
},
"world_development_indicators": {
"ref": "georgejdinicola/world-bank-indicators",
"title": "World Development Indicators",
"subtitle": "Long-run socio-economic indicators for 200+ countries",
"url": "https://www.kaggle.com/datasets/georgejdinicola/world-bank-indicators",
"keywords": [
"wdi", "macro", "economy", "gdp", "population",
"κ°œλ°œμ§€ν‘œ", "κ±°μ‹œκ²½μ œ", "세계은행", "κ²½μ œμ§€ν‘œ", "인ꡬ"
]
},
"commodity_prices": {
"ref": "debashish311601/commodity-prices",
"title": "Commodity Prices (2000-2023)",
"subtitle": "Daily prices for crude oil, gold, grains, metals, etc.",
"url": "https://www.kaggle.com/datasets/debashish311601/commodity-prices",
"keywords": [
"commodity", "oil", "gold", "raw material", "price",
"μ›μžμž¬", "μœ κ°€", "금", "가격", "μ‹œμž₯"
]
},
"world_trade": {
"ref": "muhammadtalhaawan/world-export-and-import-dataset",
"title": "World Export & Import Dataset",
"subtitle": "34-year historical trade flows by country & product",
"url": "https://www.kaggle.com/datasets/muhammadtalhaawan/world-export-and-import-dataset",
"keywords": [
"trade", "export", "import", "commerce", "flow",
"무역", "수좜", "μˆ˜μž…", "κ΅­μ œκ΅μ—­", "κ΄€μ„Έ"
]
},
"us_business_reports": {
"ref": "census/business-and-industry-reports",
"title": "US Business & Industry Reports",
"subtitle": "Key monthly economic indicators from the US Census Bureau",
"url": "https://www.kaggle.com/datasets/census/business-and-industry-reports",
"keywords": [
"us", "economy", "retail sales", "construction", "manufacturing",
"λ―Έκ΅­", "κ²½μ œμ§€ν‘œ", "μ†Œλ§€νŒλ§€", "산업생산", "건섀"
]
},
"us_industrial_production": {
"ref": "federalreserve/industrial-production-index",
"title": "Industrial Production Index (US)",
"subtitle": "Monthly Fed index for manufacturing, mining & utilities",
"url": "https://www.kaggle.com/datasets/federalreserve/industrial-production-index",
"keywords": [
"industry", "production", "index", "fed", "us",
"산업생산", "μ œμ‘°μ—…", "λ―Έκ΅­", "κ²½κΈ°", "μ§€μˆ˜"
]
},
"us_stock_market": {
"ref": "borismarjanovic/price-volume-data-for-all-us-stocks-etfs",
"title": "Huge Stock Market Dataset",
"subtitle": "Historical prices & volumes for all US stocks and ETFs",
"url": "https://www.kaggle.com/datasets/borismarjanovic/price-volume-data-for-all-us-stocks-etfs",
"keywords": [
"stock", "market", "finance", "equity", "price",
"주식", "λ―Έκ΅­μ¦μ‹œ", "μ‹œμ„Έ", "ETF", "데이터"
]
},
"company_financials": {
"ref": "rish59/financial-statements-of-major-companies2009-2023",
"title": "Financial Statements of Major Companies (2009-2023)",
"subtitle": "15-year income sheet & balance sheet data for global firms",
"url": "https://www.kaggle.com/datasets/rish59/financial-statements-of-major-companies2009-2023",
"keywords": [
"financials", "income", "balance sheet", "cashflow",
"μž¬λ¬΄μ œν‘œ", "맀좜", "μˆ˜μ΅μ„±", "κΈ°μ—…μž¬λ¬΄", "포트폴리였"
]
},
"startup_investments": {
"ref": "justinas/startup-investments",
"title": "Crunchbase Startup Investments",
"subtitle": "Funding rounds & investor info for global startups",
"url": "https://www.kaggle.com/datasets/justinas/startup-investments",
"keywords": [
"startup", "venture", "funding", "crunchbase",
"투자", "VC", "μŠ€νƒ€νŠΈμ—…", "λΌμš΄λ“œ", "μ‹ κ·œμ§„μž…"
]
},
"global_energy": {
"ref": "atharvasoundankar/global-energy-consumption-2000-2024",
"title": "Global Energy Consumption (2000-2024)",
"subtitle": "Country-level energy usage by source & sector",
"url": "https://www.kaggle.com/datasets/atharvasoundankar/global-energy-consumption-2000-2024",
"keywords": [
"energy", "consumption", "renewable", "oil", "utility",
"μ—λ„ˆμ§€", "μ†ŒλΉ„", "μž¬μƒμ—λ„ˆμ§€", "μ „λ ₯μˆ˜μš”", "ν™”μ„μ—°λ£Œ"
]
},
"co2_emissions": {
"ref": "ulrikthygepedersen/co2-emissions-by-country",
"title": "COβ‚‚ Emissions by Country",
"subtitle": "Annual COβ‚‚ emissions & per-capita data since 1960s",
"url": "https://www.kaggle.com/datasets/ulrikthygepedersen/co2-emissions-by-country",
"keywords": [
"co2", "emission", "climate", "environment", "carbon",
"νƒ„μ†Œλ°°μΆœ", "κΈ°ν›„λ³€ν™”", "ν™˜κ²½", "μ˜¨μ‹€κ°€μŠ€", "지속가λŠ₯"
]
},
"crop_climate": {
"ref": "thedevastator/the-relationship-between-crop-production-and-cli",
"title": "Crop Production & Climate Change",
"subtitle": "Yield & area stats for wheat, corn, rice, soybean vs climate",
"url": "https://www.kaggle.com/datasets/thedevastator/the-relationship-between-crop-production-and-cli",
"keywords": [
"agriculture", "crop", "climate", "yield", "food",
"농업", "μž‘λ¬Ό", "κΈ°ν›„", "μˆ˜ν™•λŸ‰", "μ‹ν’ˆ"
]
},
"esg_ratings": {
"ref": "alistairking/public-company-esg-ratings-dataset",
"title": "Public Company ESG Ratings",
"subtitle": "Environment, Social & Governance scores for listed firms",
"url": "https://www.kaggle.com/datasets/alistairking/public-company-esg-ratings-dataset",
"keywords": [
"esg", "sustainability", "governance", "csr",
"ν™˜κ²½", "μ‚¬νšŒ", "지배ꡬ쑰", "지속가λŠ₯", "평가"
]
},
"global_health": {
"ref": "malaiarasugraj/global-health-statistics",
"title": "Global Health Statistics",
"subtitle": "Comprehensive health indicators & disease prevalence by country",
"url": "https://www.kaggle.com/datasets/malaiarasugraj/global-health-statistics",
"keywords": [
"health", "disease", "life expectancy", "WHO",
"보건", "μ§ˆλ³‘", "κΈ°λŒ€μˆ˜λͺ…", "의료", "곡쀑보건"
]
},
"housing_market": {
"ref": "atharvasoundankar/global-housing-market-analysis-2015-2024",
"title": "Global Housing Market Analysis (2015-2024)",
"subtitle": "House price index, mortgage rates, rent data by country",
"url": "https://www.kaggle.com/datasets/atharvasoundankar/global-housing-market-analysis-2015-2024",
"keywords": [
"housing", "real estate", "price index", "mortgage",
"뢀동산", "주택가격", "μž„λŒ€λ£Œ", "μ‹œμž₯", "금리"
]
},
"pharma_sales": {
"ref": "milanzdravkovic/pharma-sales-data",
"title": "Pharma Sales Data (2014-2019)",
"subtitle": "600k sales records across 8 ATC drug categories",
"url": "https://www.kaggle.com/datasets/milanzdravkovic/pharma-sales-data",
"keywords": [
"pharma", "sales", "drug", "healthcare", "medicine",
"μ œμ•½", "μ˜μ•½ν’ˆ", "맀좜", "ν—¬μŠ€μΌ€μ–΄", "μ‹œμž₯"
]
},
"ev_sales": {
"ref": "muhammadehsan000/global-electric-vehicle-sales-data-2010-2024",
"title": "Global EV Sales Data (2010-2024)",
"subtitle": "Electric vehicle unit sales by region & model year",
"url": "https://www.kaggle.com/datasets/muhammadehsan000/global-electric-vehicle-sales-data-2010-2024",
"keywords": [
"ev", "electric vehicle", "automotive", "mobility",
"μ „κΈ°μ°¨", "νŒλ§€λŸ‰", "μžλ™μ°¨μ‚°μ—…", "μΉœν™˜κ²½λͺ¨λΉŒλ¦¬ν‹°", "μ‹œμž₯μ„±μž₯"
]
},
"hr_attrition": {
"ref": "pavansubhasht/ibm-hr-analytics-attrition-dataset",
"title": "IBM HR Analytics: Attrition & Performance",
"subtitle": "Employee demographics, satisfaction & attrition flags",
"url": "https://www.kaggle.com/datasets/pavansubhasht/ibm-hr-analytics-attrition-dataset",
"keywords": [
"hr", "attrition", "employee", "people analytics",
"인사", "이직λ₯ ", "직원", "HR뢄석", "쑰직관리"
]
},
"employee_satisfaction": {
"ref": "redpen12/employees-satisfaction-analysis",
"title": "Employee Satisfaction Survey Data",
"subtitle": "Department-level survey scores on satisfaction & engagement",
"url": "https://www.kaggle.com/datasets/redpen12/employees-satisfaction-analysis",
"keywords": [
"satisfaction", "engagement", "survey", "workplace",
"μ§μ›λ§Œμ‘±λ„", "쑰직문화", "μ„€λ¬Έ", "κ·Όλ¬΄ν™˜κ²½", "HR"
]
},
"world_bank_indicators": {
"ref": "georgejdinicola/world-bank-indicators",
"title": "World Bank Indicators by Topic (1960-Present)",
"subtitle": "Macro-economic, μ‚¬νšŒΒ·μΈκ΅¬ 톡계 λ“± 200+개ꡭ μž₯κΈ° μ‹œκ³„μ—΄ μ§€ν‘œ",
"url": "https://www.kaggle.com/datasets/georgejdinicola/world-bank-indicators",
"keywords": ["world bank", "development", "economy", "global", "indicator", "세계은행", "경제", "μ§€ν‘œ", "개발", "κ±°μ‹œ"]
},
"physical_chem_properties": {
"ref": "ivanyakovlevg/physical-and-chemical-properties-of-substances",
"title": "Physical & Chemical Properties of Substances",
"subtitle": "8λ§Œμ—¬ ν™”ν•©λ¬Όμ˜ 물리·화학 νŠΉμ„± 및 λΆ„λ₯˜ 정보",
"url": "https://www.kaggle.com/datasets/ivanyakovlevg/physical-and-chemical-properties-of-substances",
"keywords": ["chemistry", "materials", "property", "substance", "ν™”ν•™", "λ¬Όμ„±", "μ†Œμž¬", "데이터", "R&D"]
},
"global_weather_repository": {
"ref": "nelgiriyewithana/global-weather-repository",
"title": "Global Weather Repository",
"subtitle": "μ „ 세계 기상 κ΄€μΈ‘μΉ˜(κΈ°μ˜¨Β·κ°•μˆ˜Β·ν’μ† λ“±) 일별 μ—…λ°μ΄νŠΈ",
"url": "https://www.kaggle.com/datasets/nelgiriyewithana/global-weather-repository",
"keywords": ["weather", "climate", "meteorology", "global", "forecast", "기상", "날씨", "κΈ°ν›„", "κ΄€μΈ‘", "ν™˜κ²½"]
},
"amazon_best_seller_softwares": {
"ref": "kaverappa/amazon-best-seller-softwares",
"title": "Amazon Best Seller – Software Category",
"subtitle": "μ•„λ§ˆμ‘΄ μ†Œν”„νŠΈμ›¨μ–΄ λ² μŠ€νŠΈμ…€λŸ¬ μˆœμœ„ 및 리뷰 데이터",
"url": "https://www.kaggle.com/datasets/kaverappa/amazon-best-seller-softwares",
"keywords": ["amazon", "e-commerce", "software", "review", "ranking", "μ•„λ§ˆμ‘΄", "이컀머슀", "μ†Œν”„νŠΈμ›¨μ–΄", "λ² μŠ€νŠΈμ…€λŸ¬", "리뷰"]
},
"world_stock_prices": {
"ref": "nelgiriyewithana/world-stock-prices-daily-updating",
"title": "World Stock Prices (Daily Updating)",
"subtitle": "30,000μ—¬ κΈ€λ‘œλ²Œ 상μž₯μ‚¬μ˜ 일간 μ£Όκ°€Β·μ‹œμ΄Β·μ„Ήν„° 정보 μ‹€μ‹œκ°„ κ°±μ‹ ",
"url": "https://www.kaggle.com/datasets/nelgiriyewithana/world-stock-prices-daily-updating",
"keywords": ["stock", "finance", "market", "equity", "price", "κΈ€λ‘œλ²Œ", "μ£Όκ°€", "금육", "μ‹œμž₯", "투자"]
}
}
SUN_TZU_STRATEGIES = [
{"계": "λ§Œμ²œκ³Όν•΄", "μš”μ•½": "ν‰λ²”ν•œ μ²™, λͺ°λž˜ μ§„ν–‰", "쑰건": "μƒλŒ€κ°€ μ§€μΌœλ³΄κ³  μžˆμ„ λ•Œ", "행동": "λ£¨ν‹΄Β·ν‰μ˜¨ν•¨ κ³Όμ‹œ", "λͺ©μ ": "경계 무λ ₯ν™”", "μ˜ˆμ‹œ": "κ·œμ œκΈ°κ΄€ 눈치 λ³΄λŠ” 신사업 파일럿"},
{"계": "μœ„μœ„κ΅¬μ‘°", "μš”μ•½": "λ’€ν†΅μˆ˜ 치면 ν¬μœ„ ν’€λ¦°λ‹€", "쑰건": "우리 츑이 압박받을 λ•Œ", "행동": "적 λ³Έμ§„ κΈ‰μŠ΅", "λͺ©μ ": "μ••λ°• ν•΄μ†Œ", "μ˜ˆμ‹œ": "κ²½μŸμ‚¬ 핡심 고객 뺏기"},
{"계": "차도살인", "μš”μ•½": "λ‚΄ 손 λ”λŸ½νžˆμ§€ 마", "쑰건": "직접 곡격 λΆ€λ‹΄", "행동": "제3자 ν™œμš©", "λͺ©μ ": "μ±…μž„ μ „κ°€", "μ˜ˆμ‹œ": "언둠을 ν†΅ν•œ κ²½μŸμ‚¬ λΉ„νŒ"},
{"계": "μ΄μΌλŒ€μš°", "μš”μ•½": "μš°λ¦¬κ°€ 쉬면 적이 μ§€μΉœλ‹€", "쑰건": "μƒλŒ€κ°€ 과둜 쀑", "행동": "버티며 체λ ₯ 보쑴", "λͺ©μ ": "μ—­μ „ 타이밍 확보", "μ˜ˆμ‹œ": "ν˜‘μƒ μ§€μ—° ν›„ 헐값 인수"},
{"계": "진화타겁", "μš”μ•½": "λΆˆλ‚  λ•Œ μ£Όμ›Œ λ‹΄κΈ°", "쑰건": "μ‹œμž₯ ν˜Όλž€Β·μœ„κΈ°", "행동": "μ €κ°€ 맀수", "λͺ©μ ": "μ €λΉ„μš© 고이읡", "μ˜ˆμ‹œ": "κΈˆμœ΅μœ„κΈ° λ•Œ μš°λŸ‰μžμ‚° λ§€μž…"},
{"계": "μ„±λ™κ²©μ„œ", "μš”μ•½": "μ†ŒμŒμ€ μ™Όμͺ½, 곡격은 였λ₯Έμͺ½", "쑰건": "μ •λ©΄ λ°©μ–΄ 견고", "행동": "κ°€μ§œ μ‹ ν˜Έ β†’ 우회", "λͺ©μ ": "λ°©μ–΄ λΆ„μ‚°", "μ˜ˆμ‹œ": "μ‹ μ œν’ˆ A 홍보, μ‹€μ œλŠ” B ν™•μž₯"},
{"계": "λ¬΄μ€‘μƒμœ ", "μš”μ•½": "μ—†λŠ” 것도 μžˆλŠ” μ²™", "쑰건": "μžμ› λΆ€μ‘±", "행동": "ν—ˆμ„ΈΒ·μ—°λ§‰", "λͺ©μ ": "μƒλŒ€ ν˜Όλž€", "μ˜ˆμ‹œ": "μŠ€νƒ€νŠΈμ—… κ³Όμž₯ λ‘œλ“œλ§΅"},
{"계": "암도진창", "μš”μ•½": "λ’·λ¬ΈμœΌλ‘œ λŒμ•„κ°€λΌ", "쑰건": "우회둜 쑴재", "행동": "λΉ„λ°€ 루트 침투", "λͺ©μ ": "ν—ˆλ₯Ό μ°Œλ¦„", "μ˜ˆμ‹œ": "κ΄€μ„Έ ν”Όν•΄ 제3κ΅­ 생산"},
{"계": "κ²©μ•ˆκ΄€ν™”", "μš”μ•½": "남 싸움 ꡬ경", "쑰건": "두 경쟁자 좩돌", "행동": "관망", "λͺ©μ ": "λ‘˜ λ‹€ μ†Œλͺ¨", "μ˜ˆμ‹œ": "ν”Œλž«νΌ μ „μŸ 쀑 쀑립 μœ μ§€"},
{"계": "μ†Œλ¦¬μž₯도", "μš”μ•½": "μ›ƒμœΌλ©° μΉΌ 숨기기", "쑰건": "μΉœλ°€ λΆ„μœ„κΈ°", "행동": "우호 제슀처 ν›„ 기슡", "λͺ©μ ": "경계 λΆ•κ΄΄", "μ˜ˆμ‹œ": "ν•©μž‘ ν›„ 핡심 기술 νƒˆμ·¨"},
{"계": "μ΄λŒ€λ„κ°•", "μš”μ•½": "덜 μ€‘μš”ν•œ κ±Έ λ‚΄μ€˜λΌ", "쑰건": "λ­”κ°€ μžƒμ—ˆμ„ λ•Œ", "행동": "뢀속 희생", "λͺ©μ ": "핡심 보호", "μ˜ˆμ‹œ": "μ œν’ˆ 라인 ν•˜λ‚˜ 단쒅"},
{"계": "μˆœμˆ˜κ²¬μ–‘", "μš”μ•½": "방치된 것 μ±™κΈ°κΈ°", "쑰건": "경계 ν—ˆμˆ ", "행동": "μžμ—°μŠ€λŸ½κ²Œ μˆ˜μ§‘", "λͺ©μ ": "무혈 이득", "μ˜ˆμ‹œ": "곡곡 API 데이터 긁기"},
{"계": "νƒ€μ΄ˆκ²½μ‚¬", "μš”μ•½": "ν’€ μ³μ„œ λ±€ λ‚˜μ˜¨λ‹€", "쑰건": "적이 μˆ¨μ„ λ•Œ", "행동": "μΌλΆ€λŸ¬ μ†Œλž€", "λͺ©μ ": "μœ„μΉ˜ λ…ΈμΆœ", "μ˜ˆμ‹œ": "μ΄μ‚¬νšŒ λ°˜λŒ€νŒŒ μ˜μ€‘ νŒŒμ•…"},
{"계": "μ°¨μ‹œν™˜ν˜Ό", "μš”μ•½": "죽은 μΉ΄λ“œ μž¬ν™œμš©", "쑰건": "폐기 μžμ›", "행동": "λ¦¬λΈŒλžœλ”©", "λͺ©μ ": "μƒˆ μ „λ ₯ 확보", "μ˜ˆμ‹œ": "μ‹€νŒ¨ μ•± μž¬μΆœμ‹œ"},
{"계": "μ‘°ν˜Έμ΄μ‚°", "μš”μ•½": "ν˜Έλž‘μ΄ μ‚° λ°–μœΌλ‘œ", "쑰건": "강적 거점", "행동": "유인 이동", "λͺ©μ ": "λΉˆμ§‘ 곡랡", "μ˜ˆμ‹œ": "경쟁 VC 행사 μœ λ„ ν›„ λ”œ 선점"},
{"계": "μš•κΈˆκ³ μ’…", "μš”μ•½": "작으렀면 λ†“μ•„μ€˜λΌ", "쑰건": "인재·적 포획", "행동": "μΌλΆ€λŸ¬ ν’€μ–΄μ€Œ", "λͺ©μ ": "μ €ν•­ μ•½ν™”", "μ˜ˆμ‹œ": "핡심 인재 μž¬κ³„μ•½ μœ λ„"},
{"계": "포전인μ˜₯", "μš”μ•½": "벽돌 던져 μ˜₯ μ–»κΈ°", "쑰건": "큰 보상 ν•„μš”", "행동": "μž‘μ€ 미끼", "λͺ©μ ": "μ°Έμ—¬ μœ λ„", "μ˜ˆμ‹œ": "무료 β†’ 유료 μ „ν™˜"},
{"계": "κΈˆμ κΈˆμ™•", "μš”μ•½": "도둑 작으렀면 두λͺ©λΆ€ν„°", "쑰건": "쑰직 볡작", "행동": "μˆ˜λ‡Œ 곡격", "λͺ©μ ": "쑰직 λΆ•κ΄΄", "μ˜ˆμ‹œ": "μ΅œλŒ€ μ£Όμ£Ό μ§€λΆ„ λ§€μž…"},
{"계": "뢀저이지", "μš”μ•½": "κ°€λ§ˆ λ°‘ 뢈 끄기", "쑰건": "적 μ˜μ‘΄μ„± 쑴재", "행동": "보급 차단", "λͺ©μ ": "μ „λ ₯ 급감", "μ˜ˆμ‹œ": "핡심 곡급업체 선점"},
{"계": "혼수λͺ¨μ–΄", "μš”μ•½": "λ¬Ό 흐렀 놓고 λ‚šμ‹œ", "쑰건": "νŒμ„Έ 뢈투λͺ…", "행동": "ν˜Όνƒ μœ μ§€", "λͺ©μ ": "어뢀지리", "μ˜ˆμ‹œ": "μž…λ²• μ§€μ—° λ‘œλΉ„"},
{"계": "κΈˆμ„ νƒˆκ°", "μš”μ•½": "ν—ˆλ¬Ό λ²—κ³  도망", "쑰건": "좔적 심함", "행동": "μ™Έν”Όλ§Œ 남김", "λͺ©μ ": "좔적 무효", "μ˜ˆμ‹œ": "λΆ€μ‹€ μžνšŒμ‚¬ λ–Όμ–΄λ‚΄κΈ°"},
{"계": "κ΄€λ¬Έμž‘μ ", "μš”μ•½": "λ¬Έ λ‹«κ³  μž‘μ•„λΌ", "쑰건": "ν‡΄λ‘œ 예츑", "행동": "좜ꡬ 봉쇄", "λͺ©μ ": "μ™„μ „ 포획", "μ˜ˆμ‹œ": "락업 μ‘°ν•­μœΌλ‘œ μ§€λΆ„ λ§€μ§‘"},
{"계": "원ꡐ근곡", "μš”μ•½": "λ¨Ό 데와 μΉœν•΄μ§€κ³  κ°€κΉŒμš΄ 데 μΉœλ‹€", "쑰건": "λ‹€κ΅­ κ°„ 경쟁", "행동": "원거리 동맹", "λͺ©μ ": "단계적 ν™•μž₯", "μ˜ˆμ‹œ": "원거리 FTA 체결 ν›„ 인근 M&A"},
{"계": "κ°€λ„λ²Œκ΄΅", "μš”μ•½": "κΈΈ 빌렀 곡격", "쑰건": "쀑간 μ„Έλ ₯ μž₯λ²½", "행동": "ν†΅λ‘œ λͺ…λΆ„ β†’ μ œμ••", "λͺ©μ ": "μž₯μ•  제거", "μ˜ˆμ‹œ": "총판 빌미 μ‹œμž₯ μ§„μž…"},
{"계": "νˆ¬λŸ‰ν™˜μ£Ό", "μš”μ•½": "듀보 λͺ°λž˜ λ°”κΏ”μΉ˜κΈ°", "쑰건": "κ°μ‹œ 쑴재", "행동": "λ‚΄λΆ€ ꡐ체", "λͺ©μ ": "인식 μ™œκ³‘", "μ˜ˆμ‹œ": "λ°±μ—”λ“œ κ°ˆμ•„λΌμš°κΈ°"},
{"계": "지상맀괴", "μš”μ•½": "λ½•λ‚˜λ¬΄ κ°€λ¦¬μΌœ 회초리 μš•", "쑰건": "직접 λΉ„νŒ κ³€λž€", "행동": "제3자 지적", "λͺ©μ ": "λ©”μ‹œμ§€ 전달", "μ˜ˆμ‹œ": "싱크탱크 λ³΄κ³ μ„œ μ••λ°•"},
{"계": "κ°€μΉ˜λΆˆμ „", "μš”μ•½": "바보 μ—°κΈ°", "쑰건": "μƒλŒ€ μ˜μ‹¬ 많음", "행동": "μΌλΆ€λŸ¬ ν—ˆμˆ ", "λͺ©μ ": "방심 μœ λ„", "μ˜ˆμ‹œ": "저평가 κ°€μ΄λ˜μŠ€"},
{"계": "상μ˜₯μΆ”μ œ", "μš”μ•½": "사닀리 κ±·μ–΄μ°¨κΈ°", "쑰건": "κΈΈ μ—΄μ–΄μ€€ λ’€", "행동": "ν‡΄λ‘œ 차단", "λͺ©μ ": "고립", "μ˜ˆμ‹œ": "투자자 초청 ν›„ 정보 차단"},
{"계": "μˆ˜μƒκ°œν™”", "μš”μ•½": "λ‚˜λ¬΄μ— 꽃 ν•€ μ²™", "쑰건": "μ‹€λ ₯ λΆ€μ‘±", "행동": "μ™Έν˜• λΆ€ν’€λ¦Ό", "λͺ©μ ": "영ν–₯λ ₯ ν™•λŒ€", "μ˜ˆμ‹œ": "MOU ·곡동 둜고 홍보"},
{"계": "λ°˜κ°μœ„μ£Ό", "μš”μ•½": "μ†λ‹˜μ—μ„œ 주인으둜", "쑰건": "뢀차적 μœ„μΉ˜", "행동": "μ£Όλ„κΆŒ μž₯μ•…", "λͺ©μ ": "μ—­μ „ μ§€νœ˜", "μ˜ˆμ‹œ": "ν”Œλž«νΌ μž…μ μ‚¬ 자체 λ§ˆμΌ“"},
{"계": "미인계", "μš”μ•½": "λ§€λ ₯으둜 νŒλ‹¨ 흐리기", "쑰건": "유혹 κ°€λŠ₯", "행동": "감정·맀λ ₯ ν™œμš©", "λͺ©μ ": "κ²°μ • μ™œκ³‘", "μ˜ˆμ‹œ": "μ§€μ—­ 투자둜 μ •μΉ˜μΈ 호감 μ–»κΈ°"},
{"계": "곡성계", "μš”μ•½": "ν…… 빈 μ„±λ¬Έ 열어놓기", "쑰건": "병λ ₯ λΆ€μ‘±", "행동": "과감히 곡개", "λͺ©μ ": "μƒλŒ€ μ˜μ‹¬", "μ˜ˆμ‹œ": "λ‚΄λΆ€μžλ£Œ μ „λ©΄ 곡개"},
{"계": "λ°˜κ°„κ³„", "μš”μ•½": "κ°€μ§œ 슀파이 μ—­μ΄μš©", "쑰건": "λ‚΄λΆ€ λΆˆμ‹  μš”μ†Œ", "행동": "κ΅λž€ 정보", "λͺ©μ ": "λΆ„μ—΄", "μ˜ˆμ‹œ": "κ²½μŸμ‚¬μ— κ°€μ§œ 루머"},
{"계": "κ³ μœ‘κ³„", "μš”μ•½": "μ‚΄ λ‚΄μ£Όκ³  뼈 μ·¨ν•˜κΈ°", "쑰건": "μ‹ λ’° 상싀", "행동": "슀슀둜 손싀", "λͺ©μ ": "μ§„μ •μ„± 증λͺ…", "μ˜ˆμ‹œ": "CEO λ³΄λ„ˆμŠ€ λ°˜λ‚©"},
{"계": "μ—°ν™˜κ³„", "μš”μ•½": "μ‚¬μŠ¬λ‘œ ν•œκΊΌλ²ˆμ—", "쑰건": "볡수 λŒ€μƒ λ‹€μˆ˜", "행동": "μ—°κ²° λ¬ΆκΈ°", "λͺ©μ ": "효율 타격", "μ˜ˆμ‹œ": "νŒ¨ν‚€μ§€ μ œμž¬μ•ˆ"},
{"계": "μ£Όμœ„μƒκ³„", "μš”μ•½": "도망이 상책", "쑰건": "μŠΉμ‚° μ—†μŒ", "행동": "μ¦‰μ‹œ 후퇴", "λͺ©μ ": "손싀 μ΅œμ†ŒΒ·μž¬κΈ°", "μ˜ˆμ‹œ": "적자 μ‹œμž₯ 철수"}
]
# (μƒλž΅ 없이 λͺ¨λ“  μΉ΄ν…Œκ³ λ¦¬ λ”•μ…”λ„ˆλ¦¬ μœ μ§€ β€” λ„ˆλ¬΄ 길어도 λ³€κ²½ κΈˆμ§€)
# ──────────────────────────────── ν”„λ ˆμž„μ›Œν¬ 뢄석 ν•¨μˆ˜λ“€ ─────────────────────────
@dataclass
class Category:
"""ν†΅μΌλœ μΉ΄ν…Œκ³ λ¦¬ 및 ν•­λͺ© ꡬ쑰"""
name_ko: str
name_en: str
tags: list[str]
items: list[str]
# (SWOT, PORTER, BCG λ“± κΈ°μ‘΄ λ”•μ…”λ„ˆλ¦¬ κ·ΈλŒ€λ‘œ μœ μ§€)
SWOT_FRAMEWORK = { ... } # μƒλž΅ 없이 원본 κ·ΈλŒ€λ‘œ
PORTER_FRAMEWORK = { ... }
BCG_FRAMEWORK = { ... }
BUSINESS_FRAMEWORKS = {
"sunzi": "μ†μžλ³‘λ²• 36계",
"swot": "SWOT 뢄석",
"porter": "Porter의 5 Forces",
"bcg": "BCG 맀트릭슀"
}
# ──────────────────────────────── (쀑간 λΆ€λΆ„ μƒλž΅ 없이) ──────────────────────────
def get_idea_system_prompt(selected_category: str | None = None,
selected_frameworks: list | None = None) -> str:
"""
λ””μžμΈ/발λͺ… λͺ©μ μ„ μœ„ν•΄ λ”μš± κ°•ν™”λœ μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ.
- μ‚¬μš©μž μš”μ²­: "κ°€μž₯ μš°μˆ˜ν•œ 10κ°€μ§€ 아이디어"λ₯Ό 상세 μ„€λͺ…
- κ²°κ³Ό 좜λ ₯ μ‹œ, 이미지 생성 μžλ™ν™”
- Kaggle + μ›Ή 검색 좜처 μ œμ‹œ
"""
cat_clause = (
f'\n**μΆ”κ°€ μ§€μΉ¨**: μ„ νƒλœ μΉ΄ν…Œκ³ λ¦¬ "{selected_category}"λ₯Ό νŠΉλ³„νžˆ μš°μ„ ν•˜μ—¬ κ³ λ €ν•˜μ„Έμš”.\n'
) if selected_category else ""
if not selected_frameworks:
selected_frameworks = []
framework_instruction = "\n\n### (μ„ νƒλœ 기타 뢄석 ν”„λ ˆμž„μ›Œν¬)\n"
for fw in selected_frameworks:
if fw == "sunzi":
framework_instruction += "- μ†μžλ³‘λ²• 36계\n"
elif fw == "swot":
framework_instruction += "- SWOT 뢄석\n"
elif fw == "porter":
framework_instruction += "- Porter의 5 Forces\n"
elif fw == "bcg":
framework_instruction += "- BCG 맀트릭슀\n"
# 핡심: "κ°€μž₯ μš°μˆ˜ν•œ 10κ°€μ§€ 아이디어λ₯Ό μ•„μ£Ό μƒμ„Έν•˜κ²Œ" + "각 아이디어별 이미지 ν”„λ‘¬ν”„νŠΈ" + "좜처 μ œμ‹œ"
base_prompt = f"""
당신은 창의적 λ””μžμΈ/발λͺ… μ „λ¬Έκ°€ AIμž…λ‹ˆλ‹€.
μ‚¬μš©μžκ°€ μž…λ ₯ν•œ 주제λ₯Ό λΆ„μ„ν•˜μ—¬,
**β€œκ°€μž₯ μš°μˆ˜ν•œ 10κ°€μ§€ λ””μžμΈ/발λͺ… 아이디어”**λ₯Ό λ„μΆœν•˜μ‹œμ˜€.
각 μ•„μ΄λ””μ–΄λŠ” λ‹€μŒ μš”κ΅¬λ₯Ό μΆ©μ‘±ν•΄μ•Ό ν•©λ‹ˆλ‹€:
1) **μ•„μ£Ό μƒμ„Έν•˜κ²Œ** μ„€λͺ…ν•˜μ—¬, λ…μžκ°€ 머릿속에 이미지λ₯Ό 그릴 수 μžˆμ„ μ •λ„λ‘œ ꡬ체적으둜 μ„œμˆ 
2) **이미지 ν”„λ‘¬ν”„νŠΈ**도 ν•¨κ»˜ μ œμ‹œν•˜μ—¬, μžλ™ 이미지 생성이 λ˜λ„λ‘ ν•˜λΌ
- 예: `### 이미지 ν”„λ‘¬ν”„νŠΈ\nν•œ 쀄 영문 문ꡬ`
3) **Kaggle 데이터셋**, **μ›Ή 검색**을 ν™œμš©ν•œ 톡찰(λ˜λŠ” μ°Έμ‘°)이 있으면 λ°˜λ“œμ‹œ 결과에 μ–ΈκΈ‰
4) μ΅œμ’… 좜λ ₯의 λ§ˆμ§€λ§‰μ— **β€œμΆœμ²˜β€** μ„Ήμ…˜μ„ λ§Œλ“€κ³ ,
- μ›Ή 검색(Brave)μ—μ„œ μ°Έμ‘°ν•œ URL 3~5개
- Kaggle 데이터셋 이름/URL(μžˆλ‹€λ©΄)
- κ·Έ λ°–μ˜ μ°Έκ³  자료
{framework_instruction}
좜λ ₯은 λ°˜λ“œμ‹œ **ν•œκ΅­μ–΄**둜 ν•˜λ©°, μ•„λž˜ ꡬ쑰λ₯Ό μ€€μˆ˜ν•˜μ‹­μ‹œμ˜€:
1. **주제 μš”μ•½** (μ‚¬μš©μž 질문 μš”μ•½)
2. **Top 10 아이디어**
- 아이디어 A (상세섀λͺ… + 적용 μ‹œλ‚˜λ¦¬μ˜€ + μž₯단점 + etc)
- (λ°˜λ³΅ν•΄μ„œ 총 10개)
- 각 μ•„μ΄λ””μ–΄λ§ˆλ‹€ `### 이미지 ν”„λ‘¬ν”„νŠΈ`λ₯Ό λͺ…μ‹œν•˜μ—¬ ν•œ 쀄 영문 문ꡬλ₯Ό μ œμ‹œ
3. **뢀가적 톡찰** (μ›ν•˜λ©΄, μ„ νƒλœ ν”„λ ˆμž„μ›Œν¬λ‚˜ μΆ”κ°€ 아이디어)
4. **좜처** (웹검색 링크, Kaggle 데이터셋 λ“±)
{cat_clause}
아무리 길어도 이 μš”κ΅¬μ‚¬ν•­μ„ μ€€μˆ˜ν•˜κ³ , **였직 μ΅œμ’… μ™„μ„±λœ λ‹΅λ³€**만 좜λ ₯ν•˜μ‹­μ‹œμ˜€.
(λ‚΄λΆ€ 사고 과정은 감μΆ₯λ‹ˆλ‹€.)
"""
return base_prompt.strip()
# ──────────────────────────────── λ‚˜λ¨Έμ§€ μ½”λ“œ (웹검색, kaggle, 이미지 생성 λ“±) ──────────────────────────
@st.cache_data(ttl=3600)
def brave_search(query: str, count: int = 20):
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
if not BRAVE_KEY:
raise RuntimeError("⚠️ SERPHOUSE_API_KEY (Brave API Key) ν™˜κ²½ λ³€μˆ˜κ°€ λΉ„μ–΄μžˆμŠ΅λ‹ˆλ‹€.")
...
def mock_results(query: str) -> str:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def do_web_search(query: str) -> str:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def generate_image(prompt: str):
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
@st.cache_resource
def check_kaggle_availability():
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def extract_kaggle_search_keywords(prompt, top=3):
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def search_kaggle_datasets(query: str, top: int = 5) -> list[dict]:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
@st.cache_data
def download_and_analyze_dataset(dataset_ref: str, max_rows: int = 1000):
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def format_kaggle_analysis_markdown_multi(analyses: list[dict]) -> str:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def analyze_with_swot(prompt: str) -> dict:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def analyze_with_porter(prompt: str) -> dict:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def analyze_with_bcg(prompt: str) -> dict:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def format_business_framework_analysis(framework_type: str, analysis_result: dict) -> str:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def md_to_html(md_text: str, title: str = "Output") -> str:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def process_text_file(uploaded_file):
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def process_csv_file(uploaded_file):
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def process_pdf_file(uploaded_file):
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def process_uploaded_files(uploaded_files):
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def identify_decision_purpose(prompt: str) -> dict:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ, μ΄λ¦„λ§Œ "λ””μžμΈ/발λͺ… λͺ©μ  식별"둜 μ“°μ§€λ§Œ λ‚΄λΆ€ 둜직 동일)
...
def keywords(text: str, top: int = 8) -> str:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def compute_relevance_scores(prompt: str, categories: list[Category]) -> dict:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def compute_score(weight: int, impact: int, confidence: float) -> float:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def generate_comparison_matrix(
categories: list[Category],
relevance_scores: dict = None,
max_depth: int = 3,
max_combinations: int = 100,
relevance_threshold: float = 0.2
) -> list[tuple]:
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def smart_weight(cat_name, item, relevance, global_cnt, T):
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
def generate_random_comparison_matrix(
categories: list[Category],
relevance_scores: dict | None = None,
k_cat=(8, 12),
n_item=(6, 10),
depth_range=(3, 6),
max_combos=1000,
seed: int | None = None,
T: float = 1.3,
):
# (원본 μ½”λ“œ κ·ΈλŒ€λ‘œ)
...
# PHYS_CATEGORIES = [...] (원본 μΉ΄ν…Œκ³ λ¦¬ 리슀트 κ·ΈλŒ€λ‘œ)
PHYS_CATEGORIES: list[Category] = [
# (원본: μ„Όμ„œ κΈ°λŠ₯, 크기/ν˜•νƒœ λ³€ν™”, ... + μƒˆ μΉ΄ν…Œκ³ λ¦¬λ“€ μ „λΆ€)
...
]
# ──────────────────────────────── 메인 Streamlit μ•± ──────────────────────
def idea_generator_app():
st.title("IlΓΊvatar(일루바타λ₯΄) : Creative Design & Invention AI")
st.caption("이 μ‹œμŠ€ν…œμ€ 빅데이터λ₯Ό 자율적으둜 μˆ˜μ§‘Β·λΆ„μ„ν•˜μ—¬, 볡합적인 λ””μžμΈ/발λͺ… 아이디어λ₯Ό μ œμ•ˆν•©λ‹ˆλ‹€.")
default_vals = {
"ai_model": "gpt-4.1-mini",
"messages": [],
"auto_save": True,
"generate_image": True,
"web_search_enabled": True,
"kaggle_enabled": True,
"selected_frameworks": [],
"GLOBAL_PICK_COUNT": {},
"_skip_dup_idx": None
}
for k, v in default_vals.items():
if k not in st.session_state:
st.session_state[k] = v
sb = st.sidebar
st.session_state.temp = sb.slider(
"Diversity temperature", 0.1, 3.0, 1.3, 0.1,
help="0.1 = 맀우 보수적, 3.0 = 맀우 창의/λ¬΄μž‘μœ„"
)
sb.title("Settings")
sb.toggle("Auto Save", key="auto_save")
sb.toggle("Auto Image Generation", key="generate_image")
st.session_state.web_search_enabled = sb.toggle(
"Use Web Search", value=st.session_state.web_search_enabled
)
st.session_state.kaggle_enabled = sb.toggle(
"Use Kaggle Datasets", value=st.session_state.kaggle_enabled
)
if st.session_state.web_search_enabled:
sb.info("βœ… Web search results enabled")
if st.session_state.kaggle_enabled:
if KAGGLE_KEY:
sb.info("βœ… Kaggle data integration enabled")
else:
sb.error("⚠️ KAGGLE_KEY not set.")
st.session_state.kaggle_enabled = False
# μ˜ˆμ‹œ 주제
example_topics = {
"example1": "μŠ€λ§ˆνŠΈν™ˆμ—μ„œ μ‚¬μš©ν•  μ°¨μ„ΈλŒ€ κ°€μ „μ œν’ˆ 발λͺ… 아이디어",
"example2": "지속가λŠ₯ν•œ μ†Œμž¬λ₯Ό ν™œμš©ν•œ νŒ¨μ…˜ λ””μžμΈ 컨셉",
"example3": "μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€(UI/UX) ν˜μ‹ μ„ μœ„ν•œ μ›¨μ–΄λŸ¬λΈ” κΈ°κΈ° 아이디어"
}
sb.subheader("Example Topics")
c1, c2, c3 = sb.columns(3)
if c1.button("κ°€μ „μ œν’ˆ 발λͺ…", key="ex1"):
process_example(example_topics["example1"])
if c2.button("μΉœν™˜κ²½ νŒ¨μ…˜ λ””μžμΈ", key="ex2"):
process_example(example_topics["example2"])
if c3.button("UI/UX ν˜μ‹ ", key="ex3"):
process_example(example_topics["example3"])
# λŒ€ν™” νžˆμŠ€ν† λ¦¬ λ‹€μš΄λ‘œλ“œ
latest_ideas = next(
(m["content"] for m in reversed(st.session_state.messages)
if m["role"] == "assistant" and m["content"].strip()),
None
)
if latest_ideas:
title_match = re.search(r"# (.*?)(\n|$)", latest_ideas)
title = (title_match.group(1) if title_match else "design_invention").strip()
sb.subheader("Download Latest Ideas")
d1, d2 = sb.columns(2)
d1.download_button("Download as Markdown", latest_ideas,
file_name=f"{title}.md", mime="text/markdown")
d2.download_button("Download as HTML", md_to_html(latest_ideas, title),
file_name=f"{title}.html", mime="text/html")
# λŒ€ν™” νžˆμŠ€ν† λ¦¬ λ‘œλ“œ/μ €μž₯
up = sb.file_uploader("Load Conversation (.json)", type=["json"], key="json_uploader")
if up:
try:
st.session_state.messages = json.load(up)
sb.success("Conversation history loaded successfully")
except Exception as e:
sb.error(f"Failed to load: {e}")
if sb.button("Download Conversation as JSON"):
sb.download_button(
"Save JSON",
data=json.dumps(st.session_state.messages, ensure_ascii=False, indent=2),
file_name="chat_history.json",
mime="application/json"
)
# 파일 μ—…λ‘œλ“œ
st.subheader("File Upload (Optional)")
uploaded_files = st.file_uploader(
"Upload reference files (txt, csv, pdf)",
type=["txt", "csv", "pdf"],
accept_multiple_files=True,
key="file_uploader"
)
if uploaded_files:
st.success(f"{len(uploaded_files)} files uploaded.")
with st.expander("Preview Uploaded Files", expanded=False):
for idx, file in enumerate(uploaded_files):
st.write(f"**File Name:** {file.name}")
ext = file.name.split('.')[-1].lower()
try:
if ext == 'txt':
preview = file.read(1000).decode('utf-8', errors='ignore')
file.seek(0)
st.text_area("Preview", preview + ("..." if len(preview) >= 1000 else ""), height=150)
elif ext == 'csv':
df = pd.read_csv(file)
file.seek(0)
st.dataframe(df.head(5))
elif ext == 'pdf':
reader = PyPDF2.PdfReader(io.BytesIO(file.read()), strict=False)
file.seek(0)
pg_txt = reader.pages[0].extract_text() if reader.pages else "(No text)"
st.text_area("Preview", (pg_txt[:500] + "...") if pg_txt else "(No text)", height=150)
except Exception as e:
st.error(f"Preview failed: {e}")
if idx < len(uploaded_files) - 1:
st.divider()
# 이미 λ Œλ”λœ λ©”μ‹œμ§€(쀑볡 λ°©μ§€)
skip_idx = st.session_state.get("_skip_dup_idx")
for i, m in enumerate(st.session_state.messages):
if skip_idx is not None and i == skip_idx:
continue
with st.chat_message(m["role"]):
st.markdown(m["content"])
if "image" in m:
st.image(m["image"], caption=m.get("image_caption", ""))
st.session_state["_skip_dup_idx"] = None
# 메인 μ±„νŒ… μž…λ ₯
prompt = st.chat_input("μƒˆλ‘œμš΄ λ””μžμΈ/발λͺ… 아이디어가 ν•„μš”ν•˜μ‹ κ°€μš”? 여기에 μƒν™©μ΄λ‚˜ λͺ©ν‘œλ₯Ό μž‘μ„±ν•˜μ„Έμš”!")
if prompt:
process_input(prompt, uploaded_files)
sb.markdown("---")
sb.markdown("Created by [VIDraft](https://discord.gg/openfreeai)")
def process_example(topic):
process_input(topic, [])
def process_input(prompt: str, uploaded_files):
"""
메인 μ±„νŒ… μž…λ ₯을 λ°›μ•„ λ””μžμΈ/발λͺ… 아이디어λ₯Ό μƒμ„±ν•œλ‹€.
"""
if not any(m["role"] == "user" and m["content"] == prompt for m in st.session_state.messages):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# 쀑볡 λ‹΅λ³€ λ°©μ§€
for i in range(len(st.session_state.messages) - 1):
if (st.session_state.messages[i]["role"] == "user"
and st.session_state.messages[i]["content"] == prompt
and st.session_state.messages[i + 1]["role"] == "assistant"):
return
with st.chat_message("assistant"):
status = st.status("Preparing to generate invention ideas…")
stream_placeholder = st.empty()
full_response = ""
try:
client = get_openai_client()
status.update(label="Initializing model…")
selected_cat = st.session_state.get("category_focus", None)
selected_frameworks = st.session_state.get("selected_frameworks", [])
# κ°•ν™”λœ μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈλ₯Ό μ‚¬μš©
sys_prompt = get_idea_system_prompt(
selected_category=selected_cat,
selected_frameworks=selected_frameworks
)
def category_context(sel):
if sel:
return json.dumps({sel: physical_transformation_categories[sel]}, ensure_ascii=False)
return "ALL_CATEGORIES: " + ", ".join(physical_transformation_categories.keys())
use_web_search = st.session_state.web_search_enabled
use_kaggle = st.session_state.kaggle_enabled
has_uploaded = bool(uploaded_files)
search_content = None
kaggle_content = None
file_content = None
# β‘  웹검색
if use_web_search:
status.update(label="Searching the web…")
with st.spinner("Searching…"):
search_content = do_web_search(keywords(prompt, top=5))
# β‘‘ Kaggle
if use_kaggle and check_kaggle_availability():
status.update(label="Kaggle 데이터셋 뢄석 쀑…")
with st.spinner("Searching Kaggle…"):
kaggle_kw = extract_kaggle_search_keywords(prompt)
try:
datasets = search_kaggle_datasets(kaggle_kw)
except Exception as e:
logging.warning(f"search_kaggle_datasets 였λ₯˜ λ¬΄μ‹œ: {e}")
datasets = []
analyses = []
if datasets:
status.update(label="Downloading & analysing datasets…")
for ds in datasets:
try:
ana = download_and_analyze_dataset(ds["ref"])
except Exception as e:
logging.error(f"Kaggle 뢄석 였λ₯˜({ds['ref']}) : {e}")
ana = f"데이터셋 뢄석 였λ₯˜: {e}"
analyses.append({"meta": ds, "analysis": ana})
if analyses:
kaggle_content = format_kaggle_analysis_markdown_multi(analyses)
# β‘’ 파일 μ—…λ‘œλ“œ
if has_uploaded:
status.update(label="Reading uploaded files…")
with st.spinner("Processing files…"):
file_content = process_uploaded_files(uploaded_files)
# β‘£ ꡰ사 μ „μˆ  데이터 (ν•„μš” μ‹œ)
mil_content = None
if is_military_query(prompt):
status.update(label="Searching military tactics dataset…")
with st.spinner("Loading military insights…"):
mil_rows = military_search(prompt)
if mil_rows:
mil_content = "# Military Tactics Dataset Reference\n\n"
for i, row in enumerate(mil_rows, 1):
mil_content += (
f"### Case {i}\n"
f"**Scenario:** {row['scenario_description']}\n\n"
f"**Attack Reasoning:** {row['attack_reasoning']}\n\n"
f"**Defense Reasoning:** {row['defense_reasoning']}\n\n---\n"
)
user_content = prompt
if search_content:
user_content += "\n\n" + search_content
if kaggle_content:
user_content += "\n\n" + kaggle_content
if file_content:
user_content += "\n\n" + file_content
if mil_content:
user_content += "\n\n" + mil_content
# λ‚΄λΆ€ 뢄석
status.update(label="뢄석 쀑…")
decision_purpose = identify_decision_purpose(prompt)
relevance_scores = compute_relevance_scores(prompt, PHYS_CATEGORIES)
status.update(label="μΉ΄ν…Œκ³ λ¦¬ μ‘°ν•© 아이디어 생성 쀑…")
T = st.session_state.temp
k_cat_range = (4, 8) if T < 1.0 else (6, 10) if T < 2.0 else (8, 12)
n_item_range = (2, 4) if T < 1.0 else (3, 6) if T < 2.0 else (4, 8)
depth_range = (2, 3) if T < 1.0 else (2, 5) if T < 2.0 else (2, 6)
combos = generate_random_comparison_matrix(
PHYS_CATEGORIES,
relevance_scores,
k_cat=k_cat_range,
n_item=n_item_range,
depth_range=depth_range,
seed=hash(prompt) & 0xFFFFFFFF,
T=T,
)
# μ˜ˆμ‹œ 맀트릭슀 (λ””λ²„κ·Έμš©, μ΅œμ’… 닡변에 λΆ™μž„)
combos_table = "| μ‘°ν•© | κ°€μ€‘μΉ˜ | 영ν–₯도 | 신뒰도 | 총점 |\n|------|--------|--------|--------|-----|\n"
for w, imp, conf, tot, cmb in combos:
combo_str = " + ".join(f"{c[0]}-{c[1]}" for c in cmb)
combos_table += f"| {combo_str} | {w} | {imp} | {conf:.1f} | {tot} |\n"
purpose_info = "\n\n## λ””μžμΈ/발λͺ… λͺ©ν‘œ 뢄석\n"
if decision_purpose['purposes']:
purpose_info += "### 핡심 λͺ©μ \n"
for p, s in decision_purpose['purposes']:
purpose_info += f"- **{p}** (κ΄€λ ¨μ„±: {s})\n"
if decision_purpose['constraints']:
purpose_info += "\n### μ œμ•½ 쑰건\n"
for c, s in decision_purpose['constraints']:
purpose_info += f"- **{c}** (κ΄€λ ¨μ„±: {s})\n"
# (ν”„λ ˆμž„μ›Œν¬ κ²°κ³Ό: ν•„μš” μ‹œ)
framework_contents = []
for fw in selected_frameworks:
if fw == "swot":
swot_res = analyze_with_swot(prompt)
framework_contents.append(format_business_framework_analysis("swot", swot_res))
elif fw == "porter":
porter_res = analyze_with_porter(prompt)
framework_contents.append(format_business_framework_analysis("porter", porter_res))
elif fw == "bcg":
bcg_res = analyze_with_bcg(prompt)
framework_contents.append(format_business_framework_analysis("bcg", bcg_res))
elif fw == "sunzi":
# μƒλž΅ (μ›ν•œλ‹€λ©΄ μ†μžλ³‘λ²• 뢄석도 κ°€λŠ₯)
pass
if framework_contents:
user_content += "\n\n## (Optional) 기타 ν”„λ ˆμž„μ›Œν¬ 뢄석\n\n" + "\n\n".join(framework_contents)
user_content += f"\n\n## μΉ΄ν…Œκ³ λ¦¬ 맀트릭슀 뢄석{purpose_info}\n{combos_table}"
status.update(label="Generating final design/invention ideas…")
api_messages = [
{"role": "system", "content": sys_prompt},
{"role": "system", "name": "category_db", "content": category_context(selected_cat)},
{"role": "user", "content": user_content},
]
stream = client.chat.completions.create(
model="gpt-4.1-mini",
messages=api_messages,
temperature=1,
max_tokens=MAX_TOKENS,
top_p=1,
stream=True
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
full_response += chunk.choices[0].delta.content
stream_placeholder.markdown(full_response + "β–Œ")
stream_placeholder.markdown(full_response)
status.update(label="Invention ideas created!", state="complete")
# 이미지 생성 (μžλ™)
img_data = img_caption = None
if st.session_state.generate_image and full_response:
# μ •κ·œμ‹μœΌλ‘œ "### 이미지 ν”„λ‘¬ν”„νŠΈ" ꡬ문을 μ°Ύμ•„ 이미지 생성
# μ—¬λŸ¬ κ°œκ°€ μžˆμ„ 수 μžˆμœΌλ―€λ‘œ, λŒ€ν‘œ 1개만 μƒμ„±ν•˜κ±°λ‚˜
# (μ—¬κΈ°μ„œλŠ” νŽΈμ˜μƒ 첫 번째만)
match = re.search(r"###\s*이미지\s*ν”„λ‘¬ν”„νŠΈ\s*\n+([^\n]+)", full_response, re.I)
if not match:
match = re.search(r"Image\s+Prompt\s*[:\-]\s*([^\n]+)", full_response, re.I)
if match:
raw_prompt = re.sub(r'[\r\n"\'\\]', " ", match.group(1)).strip()
with st.spinner("Generating illustrative image…"):
img_data, img_caption = generate_image(raw_prompt)
if img_data:
st.image(img_data, caption=f"Visualized Concept – {img_caption}")
answer_msg = {"role": "assistant", "content": full_response}
if img_data:
answer_msg["image"] = img_data
answer_msg["image_caption"] = img_caption
st.session_state["_skip_dup_idx"] = len(st.session_state.messages)
st.session_state.messages.append(answer_msg)
# λ‹€μš΄λ‘œλ“œ λ²„νŠΌ
st.subheader("Download This Output")
col_md, col_html = st.columns(2)
col_md.download_button(
"Markdown",
data=full_response,
file_name=f"{prompt[:30]}.md",
mime="text/markdown"
)
col_html.download_button(
"HTML",
data=md_to_html(full_response, prompt[:30]),
file_name=f"{prompt[:30]}.html",
mime="text/html"
)
if st.session_state.auto_save:
fn = f"chat_history_auto_{datetime.now():%Y%m%d_%H%M%S}.json"
with open(fn, "w", encoding="utf-8") as fp:
json.dump(st.session_state.messages, fp, ensure_ascii=False, indent=2)
except Exception as e:
logging.error("process_input error", exc_info=True)
st.error(f"⚠️ μž‘μ—… 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {e}")
st.session_state.messages.append(
{"role": "assistant", "content": f"⚠️ 였λ₯˜: {e}"}
)
def main():
idea_generator_app()
if __name__ == "__main__":
main()