ginipick commited on
Commit
95c306a
Β·
verified Β·
1 Parent(s): aade38c

Update app-BACKUP-LAST.py

Browse files
Files changed (1) hide show
  1. app-BACKUP-LAST.py +205 -105
app-BACKUP-LAST.py CHANGED
@@ -15,10 +15,10 @@ from typing import Iterator
15
 
16
  import streamlit as st
17
  import pandas as pd
18
- import PyPDF2 # For handling PDF files
19
  from collections import Counter
20
 
21
- from openai import OpenAI # OpenAI 라이브러리
22
  from gradio_client import Client
23
  from kaggle.api.kaggle_api_extended import KaggleApi
24
  import tempfile
@@ -30,6 +30,52 @@ import pyarrow.parquet as pq
30
  from sklearn.feature_extraction.text import TfidfVectorizer
31
  from sklearn.metrics.pairwise import cosine_similarity
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  # ─────────────────────────────── Environment Variables / Constants ─────────────────────────
34
 
35
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
@@ -46,7 +92,7 @@ os.environ["KAGGLE_KEY"] = KAGGLE_KEY
46
 
47
  BRAVE_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"
48
  IMAGE_API_URL = "http://211.233.58.201:7896" # μ˜ˆμ‹œ 이미지 μƒμ„±μš© API
49
- MAX_TOKENS = 7999
50
 
51
  # ─────────────────────────────── Logging ───────────────────────────────
52
  logging.basicConfig(
@@ -54,6 +100,7 @@ logging.basicConfig(
54
  format="%(asctime)s - %(levelname)s - %(message)s"
55
  )
56
 
 
57
  # ─────────────────────────────── ꡰ사(밀리터리) μ „μˆ  데이터셋 λ‘œλ“œ ─────────────────
58
  @st.cache_resource
59
  def load_military_dataset():
@@ -750,8 +797,7 @@ physical_transformation_categories = {
750
  ]
751
  }
752
 
753
- physical_transformation_categories_en = {} # (영문 μΉ΄ν…Œκ³ λ¦¬λŠ” μ‚¬μš©ν•˜μ§€ μ•Šμ•„λ„ λ˜λ―€λ‘œ λΉ„μ›Œλ‘ )
754
-
755
  SWOT_FRAMEWORK = {
756
  "strengths": {
757
  "title": "강점 (Strengths)",
@@ -841,7 +887,6 @@ class Category:
841
  tags: list[str]
842
  items: list[str]
843
 
844
- # ──────────────────────────────── ν”„λ ˆμž„μ›Œν¬ 뢄석 ν•¨μˆ˜λ“€ ─────────────────────────
845
  def analyze_with_swot(prompt: str) -> dict:
846
  prompt_lower = prompt.lower()
847
  results = {}
@@ -1662,78 +1707,116 @@ PHYS_CATEGORIES: list[Category] = [
1662
  items=physical_transformation_categories["λ―Έν•™ 및 감성 κ²½ν—˜"]
1663
  )
1664
  ]
 
1665
 
1666
- # ──────────────────────────────── μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ 생성 ─────────────────────
1667
  def get_idea_system_prompt(selected_category: str | None = None,
1668
  selected_frameworks: list | None = None) -> str:
1669
  """
1670
- [λ””μžμΈ/발λͺ… μ „μš©] μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ.
 
 
 
 
1671
  """
1672
  cat_clause = (
1673
- f'\n**μΆ”κ°€ μ§€μΉ¨**: μ„ νƒλœ μΉ΄ν…Œκ³ λ¦¬ "{selected_category}"에 νŠΉλ³„ν•œ 주의λ₯Ό κΈ°μšΈμ΄μ‹­μ‹œμ˜€. '
1674
- f'이 μΉ΄ν…Œκ³ λ¦¬μ˜ ν•­λͺ©λ“€μ„ 아이디어 λ°œμƒ μ „ 과정에 μš°μ„  κ³ λ €ν•˜μ„Έμš”.\n'
1675
  ) if selected_category else ""
1676
-
1677
- # (기쑴에 'sunzi','swot','porter','bcg' λ“± μ‚¬μš©ν–ˆμœΌλ‚˜, 이제 λ””μžμΈ/발λͺ… μœ„μ£Όμ΄λ―€λ‘œ μ΅œμ†Œν™”)
1678
  if not selected_frameworks:
1679
  selected_frameworks = []
1680
-
1681
- # μƒˆ λͺ©μ : λ””μžμΈ/발λͺ… 아이디어 μ°©μ•ˆ
1682
- framework_instruction = "\n\n### (λ””μžμΈ/발λͺ…) ν™œμš© ν”„λ ˆμž„μ›Œν¬\n"
1683
- framework_output_format = ""
1684
-
1685
- # ν”„λ ˆμž„μ›Œν¬ μ‚¬μš© μ˜ˆμ‹œ(μ›ν•œλ‹€λ©΄ "sunzi" λ“± μ‚¬μš© κ°€λŠ₯)
1686
  for fw in selected_frameworks:
1687
  if fw == "sunzi":
1688
- framework_instruction += "- **μ†μžλ³‘λ²• 36계**: 창의적 μ „λž΅μœΌλ‘œ 발λͺ… 아이디어에 적용\n"
1689
- framework_output_format += """
1690
- ## μ†μžλ³‘λ²• κ΄€μ μ—μ„œμ˜ 창의 아이디어
1691
- (μ›ν•˜λŠ” κ²½μš°μ— μ‚¬μš©)
1692
- """
1693
  elif fw == "swot":
1694
- framework_instruction += "- **SWOT 뢄석**: λ‚΄λΆ€/μ™ΈλΆ€ μš”μ†Œ κ³ λ €ν•˜μ—¬ 발λͺ… λ°©ν–₯μ„± 탐색\n"
1695
  elif fw == "porter":
1696
- framework_instruction += "- **Porter의 5 Forces**: μ‹œμž₯/ν™˜κ²½ κ΄€μ μ—μ„œ λ””μžμΈ μš”κ΅¬ νŒŒμ•…\n"
1697
  elif fw == "bcg":
1698
- framework_instruction += "- **BCG 맀트릭슀**: 발λͺ… μ•„μ΄ν…œ 포트폴리였적 κ΄€μ μœΌλ‘œ μ ‘κ·Ό\n"
1699
-
1700
  base_prompt = f"""
1701
- 당신은 창의적 λ””μžμΈ/발λͺ… μ „λ¬Έκ°€ AIμž…λ‹ˆλ‹€.
1702
- μ‚¬μš©μžμ˜ ν…μŠ€νŠΈ(μ§ˆλ¬Έμ΄λ‚˜ 아이디어 λ°©ν–₯)λ₯Ό λΆ„μ„ν•˜μ—¬, **μƒˆλ‘œμš΄ 발λͺ…/λ””μžμΈ 아이디어**λ₯Ό ꡬ체적으둜 μ œμ‹œν•˜κ³ ,
1703
- ν•„μš”ν•˜λ‹€λ©΄ μ—¬λŸ¬ κ΄€λ ¨ **데이터/자료**λ₯Ό ν™œμš©ν•˜μ—¬ 톡찰λ ₯을 λ³΄κ°•ν•˜μ‹­μ‹œμ˜€.
1704
-
1705
- 1) 문제/λͺ©ν‘œ 뢄석:
1706
- - μ‚¬μš©μžκ°€ λ§ν•œ λ””μžμΈ/발λͺ… λͺ©μ Β·ν‚€μ›Œλ“œλ₯Ό νŒŒμ•…,
1707
- - μ—°κ΄€λ˜λŠ” μΉ΄ν…Œκ³ λ¦¬ 및 ν•­λͺ© 식별
1708
-
1709
- 2) μΉ΄ν…Œκ³ λ¦¬ λ§€ν•‘ 및 아이디어 μŠ€μΌ€μΉ˜:
1710
- - λ‹€μ–‘ν•œ 물리적/화학적/ꡬ쑰적/ν™˜κ²½μ  λ²”μ£Όμ˜ μš”μ†Œκ°€ μ–΄λ–»κ²Œ 아이디어에 적용 κ°€λŠ₯ν•œμ§€ μ„œμˆ 
1711
- - κ΄€λ ¨ 데이터(웹검색, Kaggle λ“±)도 μ°Έμ‘° κ°€λŠ₯
1712
-
1713
- 3) μ’…ν•© 아이디어 μ œμ•ˆ:
1714
- - μƒμœ„ 2~3κ°€μ§€ μ£Όμš” λ””μžμΈ/발λͺ… λ°©ν–₯ μ œμ‹œ
1715
- - ꡬ체적으둜 κ΅¬ν˜„ κ°€λŠ₯μ„±, μ˜ˆμƒ κΈ°λŠ₯, μž₯단점, ν™•μž₯μ„±, μœ„ν—˜ μš”μ†Œ λ“± 뢄석
1716
-
1717
  {framework_instruction}
1718
 
1719
- ### μ΅œμ’… 좜λ ₯ ν˜•μ‹ (λ§ˆν¬λ‹€μš΄)
1720
- - **핡심 λͺ©ν‘œ/ν‚€μ›Œλ“œ**: (μ‚¬μš©μž 질문 μš”μ•½)
1721
- - **μ—°κ΄€ μΉ΄ν…Œκ³ λ¦¬ μš”μ†Œ**: ν‘œ ν˜•μ‹ λ˜λŠ” λ‚˜μ—΄
1722
- - **아이디어 μŠ€μΌ€μΉ˜**: (각 아이디어별 κ°œλ…, νŠΉμ§•, μ‹œλ‚˜λ¦¬μ˜€)
1723
- - **μ˜ˆμƒ 문제/리슀크**: (κΈ°μˆ Β·λΉ„μš©Β·μœ€λ¦¬ λ“±)
1724
- - **μ‹€ν–‰ 단계**: (ν”„λ‘œν† νƒ€μž…, ν…ŒμŠ€νŠΈ, 검증, 런칭)
1725
- - **μΆ”κ°€ 아이디어/μ°Έκ³ **: (μ˜΅μ…˜)
1726
-
1727
- {framework_output_format}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1728
  {cat_clause}
1729
-
1730
- **λͺ¨λ“  닡변은 ν•œκ΅­μ–΄**둜 μž‘μ„±.
1731
- 쀑간 사고 과정을 λ‚΄λΆ€μ μœΌλ‘œ μˆ˜ν–‰ν•˜λ˜, μ΅œμ’… λ‹΅λ³€λ§Œ 좜λ ₯.
1732
- μ›Ή 검색 및 Kaggle 데이터셋 뢄석도 ν†΅ν•©ν•˜μ—¬ 아이디어 ν’ˆμ§ˆμ„ λ†’μ΄μ‹­μ‹œμ˜€.
1733
  """
1734
  return base_prompt.strip()
1735
 
1736
- # ──────────────────────────────── Brave Search API ───────────────────────
 
1737
  @st.cache_data(ttl=3600)
1738
  def brave_search(query: str, count: int = 20):
1739
  if not BRAVE_KEY:
@@ -1889,15 +1972,15 @@ def idea_generator_app():
1889
 
1890
  # μ˜ˆμ‹œ 주제
1891
  example_topics = {
1892
- "example1": "μŠ€λ§ˆνŠΈν™ˆμ—μ„œ μ‚¬μš©ν•  μ°¨μ„ΈλŒ€ κ°€μ „μ œν’ˆ 발λͺ… 아이디어",
1893
- "example2": "지속가λŠ₯ν•œ μ†Œμž¬λ₯Ό ν™œμš©ν•œ νŒ¨μ…˜ λ””μžμΈ 컨셉",
1894
  "example3": "μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€(UI/UX) ν˜μ‹ μ„ μœ„ν•œ μ›¨μ–΄λŸ¬λΈ” κΈ°κΈ° 아이디어"
1895
  }
1896
  sb.subheader("Example Topics")
1897
  c1, c2, c3 = sb.columns(3)
1898
- if c1.button("κ°€μ „μ œν’ˆ 발λͺ…", key="ex1"):
1899
  process_example(example_topics["example1"])
1900
- if c2.button("μΉœν™˜κ²½ νŒ¨μ…˜ λ””μžμΈ", key="ex2"):
1901
  process_example(example_topics["example2"])
1902
  if c3.button("UI/UX ν˜μ‹ ", key="ex3"):
1903
  process_example(example_topics["example3"])
@@ -1993,7 +2076,10 @@ def process_example(topic):
1993
  def process_input(prompt: str, uploaded_files):
1994
  """
1995
  메인 μ±„νŒ… μž…λ ₯을 λ°›μ•„ λ””μžμΈ/발λͺ… 아이디어λ₯Ό μƒμ„±ν•œλ‹€.
 
 
1996
  """
 
1997
  if not any(m["role"] == "user" and m["content"] == prompt for m in st.session_state.messages):
1998
  st.session_state.messages.append({"role": "user", "content": prompt})
1999
  with st.chat_message("user"):
@@ -2005,6 +2091,7 @@ def process_input(prompt: str, uploaded_files):
2005
  and st.session_state.messages[i + 1]["role"] == "assistant"):
2006
  return
2007
 
 
2008
  with st.chat_message("assistant"):
2009
  status = st.status("Preparing to generate invention ideas…")
2010
  stream_placeholder = st.empty()
@@ -2014,10 +2101,8 @@ def process_input(prompt: str, uploaded_files):
2014
  client = get_openai_client()
2015
  status.update(label="Initializing model…")
2016
 
2017
- selected_cat = st.session_state.get("category_focus", None)
2018
  selected_frameworks = st.session_state.get("selected_frameworks", [])
2019
-
2020
- # λͺ©μ μ΄ "λ””μžμΈ/발λͺ…"μ΄λ―€λ‘œ, system prompt λ³€κ²½
2021
  sys_prompt = get_idea_system_prompt(
2022
  selected_category=selected_cat,
2023
  selected_frameworks=selected_frameworks
@@ -2032,11 +2117,9 @@ def process_input(prompt: str, uploaded_files):
2032
  use_kaggle = st.session_state.kaggle_enabled
2033
  has_uploaded = bool(uploaded_files)
2034
 
2035
- search_content = None
2036
- kaggle_content = None
2037
- file_content = None
2038
 
2039
- # β‘  웹검색
2040
  if use_web_search:
2041
  status.update(label="Searching the web…")
2042
  with st.spinner("Searching…"):
@@ -2071,8 +2154,7 @@ def process_input(prompt: str, uploaded_files):
2071
  with st.spinner("Processing files…"):
2072
  file_content = process_uploaded_files(uploaded_files)
2073
 
2074
- # β‘£ ꡰ사 μ „μˆ  데이터 (ν•„μš” μ‹œ)
2075
- mil_content = None
2076
  if is_military_query(prompt):
2077
  status.update(label="Searching military tactics dataset…")
2078
  with st.spinner("Loading military insights…"):
@@ -2087,17 +2169,13 @@ def process_input(prompt: str, uploaded_files):
2087
  f"**Defense Reasoning:** {row['defense_reasoning']}\n\n---\n"
2088
  )
2089
 
 
2090
  user_content = prompt
2091
- if search_content:
2092
- user_content += "\n\n" + search_content
2093
- if kaggle_content:
2094
- user_content += "\n\n" + kaggle_content
2095
- if file_content:
2096
- user_content += "\n\n" + file_content
2097
- if mil_content:
2098
- user_content += "\n\n" + mil_content
2099
-
2100
- # λ‚΄λΆ€ 뢄석
2101
  status.update(label="뢄석 쀑…")
2102
  decision_purpose = identify_decision_purpose(prompt)
2103
  relevance_scores = compute_relevance_scores(prompt, PHYS_CATEGORIES)
@@ -2132,22 +2210,21 @@ def process_input(prompt: str, uploaded_files):
2132
  for c, s in decision_purpose['constraints']:
2133
  purpose_info += f"- **{c}** (κ΄€λ ¨μ„±: {s})\n"
2134
 
2135
- # ν”„λ ˆμž„μ›Œν¬ 적용 κ²°κ³Ό (ν˜„μž¬ λͺ©μ μ΄ λ””μžμΈ/발λͺ… -> μ„ νƒμ μœΌλ‘œ ν‘œμ‹œ)
2136
  framework_contents = []
2137
  for fw in selected_frameworks:
2138
  if fw == "swot":
2139
- swot_res = analyze_with_swot(prompt)
2140
- framework_contents.append(format_business_framework_analysis("swot", swot_res))
 
2141
  elif fw == "porter":
2142
- porter_res = analyze_with_porter(prompt)
2143
- framework_contents.append(format_business_framework_analysis("porter", porter_res))
 
2144
  elif fw == "bcg":
2145
- bcg_res = analyze_with_bcg(prompt)
2146
- framework_contents.append(format_business_framework_analysis("bcg", bcg_res))
2147
- elif fw == "sunzi":
2148
- # μ†μžλ³‘λ²• μ˜ˆμ‹œ
2149
- # (μ‹€μ œλ‘œλŠ” 별도 둜직이 ν•„μš”ν•˜λ‚˜ μ—¬κΈ°μ„  μƒλž΅)
2150
- pass
2151
 
2152
  if framework_contents:
2153
  user_content += "\n\n## (Optional) 기타 ν”„λ ˆμž„μ›Œν¬ 뢄석\n\n" + "\n\n".join(framework_contents)
@@ -2155,29 +2232,50 @@ def process_input(prompt: str, uploaded_files):
2155
  user_content += f"\n\n## μΉ΄ν…Œκ³ λ¦¬ 맀트릭슀 뢄석{purpose_info}\n{combos_table}"
2156
 
2157
  status.update(label="Generating final design/invention ideas…")
 
2158
  api_messages = [
2159
  {"role": "system", "content": sys_prompt},
2160
  {"role": "system", "name": "category_db", "content": category_context(selected_cat)},
2161
  {"role": "user", "content": user_content},
2162
  ]
2163
- stream = client.chat.completions.create(
2164
- model="gpt-4.1-mini",
2165
- messages=api_messages,
2166
- temperature=1,
2167
- max_tokens=MAX_TOKENS,
2168
- top_p=1,
2169
- stream=True
2170
  )
 
 
 
 
 
 
 
 
 
2171
 
2172
- for chunk in stream:
2173
- if chunk.choices and chunk.choices[0].delta.content:
2174
- full_response += chunk.choices[0].delta.content
2175
- stream_placeholder.markdown(full_response + "β–Œ")
2176
 
2177
- stream_placeholder.markdown(full_response)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2178
  status.update(label="Invention ideas created!", state="complete")
2179
 
2180
- # 이미지 생성
2181
  img_data = img_caption = None
2182
  if st.session_state.generate_image and full_response:
2183
  match = re.search(r"###\s*이미지\s*ν”„λ‘¬ν”„νŠΈ\s*\n+([^\n]+)", full_response, re.I)
@@ -2190,6 +2288,7 @@ def process_input(prompt: str, uploaded_files):
2190
  if img_data:
2191
  st.image(img_data, caption=f"Visualized Concept – {img_caption}")
2192
 
 
2193
  answer_msg = {"role": "assistant", "content": full_response}
2194
  if img_data:
2195
  answer_msg["image"] = img_data
@@ -2197,7 +2296,7 @@ def process_input(prompt: str, uploaded_files):
2197
  st.session_state["_skip_dup_idx"] = len(st.session_state.messages)
2198
  st.session_state.messages.append(answer_msg)
2199
 
2200
- # λ‹€μš΄λ‘œλ“œ λ²„νŠΌ
2201
  st.subheader("Download This Output")
2202
  col_md, col_html = st.columns(2)
2203
  col_md.download_button(
@@ -2225,6 +2324,7 @@ def process_input(prompt: str, uploaded_files):
2225
  {"role": "assistant", "content": f"⚠️ 였λ₯˜: {e}"}
2226
  )
2227
 
 
2228
  def main():
2229
  idea_generator_app()
2230
 
 
15
 
16
  import streamlit as st
17
  import pandas as pd
18
+ import PyPDF2 # For handling PDF files
19
  from collections import Counter
20
 
21
+ from openai import OpenAI, APIError, APITimeoutError
22
  from gradio_client import Client
23
  from kaggle.api.kaggle_api_extended import KaggleApi
24
  import tempfile
 
30
  from sklearn.feature_extraction.text import TfidfVectorizer
31
  from sklearn.metrics.pairwise import cosine_similarity
32
 
33
+ # ─── λ„€νŠΈμ›Œν¬ μ•ˆμ •ν™”μš© 라이브러리 ──────────────────────────────────────
34
+ import httpx
35
+ from httpx import RemoteProtocolError
36
+
37
+ # β–Έ backoff λͺ¨λ“ˆμ΄ μ—†μœΌλ©΄ μ¦‰μ„μ—μ„œ λŒ€μ²΄ κ΅¬ν˜„
38
+ try:
39
+ import backoff
40
+ except ImportError:
41
+ logging.warning("`backoff` λͺ¨λ“ˆμ΄ μ—†μ–΄ 간단 λŒ€μ²΄ λ°μ½”λ ˆμ΄ν„°λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.")
42
+
43
+ def _simple_backoff_on_exception(exceptions, *args, **kwargs):
44
+ """
45
+ κ°€λ²Όμš΄ μ§€μˆ˜(backoff=2^n) μž¬μ‹œλ„ λ°μ½”λ ˆμ΄ν„°.
46
+ backoff.on_exception API의 ν•„μˆ˜ 인자만 ν‰λ‚΄λƒ…λ‹ˆλ‹€.
47
+ - exceptions : μž¬μ‹œλ„ λŒ€μƒ μ˜ˆμ™Έ(tuple λ˜λŠ” 단일)
48
+ - max_tries : kwargs 둜 μ§€μ •(κΈ°λ³Έ 3)
49
+ - base : kwargs 둜 μ§€μ •(κΈ°λ³Έ 2, μ§€μˆ˜ 배수)
50
+ 기타 μΈμžλŠ” λ¬΄μ‹œν•©λ‹ˆλ‹€.
51
+ """
52
+ max_tries = kwargs.get("max_tries", 3)
53
+ base = kwargs.get("base", 2)
54
+
55
+ def decorator(fn):
56
+ def wrapper(*f_args, **f_kwargs):
57
+ attempt = 0
58
+ while True:
59
+ try:
60
+ return fn(*f_args, **f_kwargs)
61
+ except exceptions as e:
62
+ attempt += 1
63
+ if attempt >= max_tries:
64
+ raise
65
+ sleep = base ** attempt
66
+ logging.info(
67
+ f"[retry {attempt}/{max_tries}] {fn.__name__} -> {e} … {sleep}s λŒ€κΈ°"
68
+ )
69
+ time.sleep(sleep)
70
+ return wrapper
71
+ return decorator
72
+
73
+ class _DummyBackoff:
74
+ on_exception = _simple_backoff_on_exception
75
+
76
+ backoff = _DummyBackoff()
77
+
78
+
79
  # ─────────────────────────────── Environment Variables / Constants ─────────────────────────
80
 
81
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
 
92
 
93
  BRAVE_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"
94
  IMAGE_API_URL = "http://211.233.58.201:7896" # μ˜ˆμ‹œ 이미지 μƒμ„±μš© API
95
+ MAX_TOKENS = 7999 # μ•ˆμ „ν•œ 토큰 ν•œλ„
96
 
97
  # ─────────────────────────────── Logging ───────────────────────────────
98
  logging.basicConfig(
 
100
  format="%(asctime)s - %(levelname)s - %(message)s"
101
  )
102
 
103
+
104
  # ─────────────────────────────── ꡰ사(밀리터리) μ „μˆ  데이터셋 λ‘œλ“œ ─────────────────
105
  @st.cache_resource
106
  def load_military_dataset():
 
797
  ]
798
  }
799
 
800
+ # ──────────────────────────────── ν”„λ ˆμž„μ›Œν¬ 뢄석 ν•¨μˆ˜λ“€ ─────────────────────────
 
801
  SWOT_FRAMEWORK = {
802
  "strengths": {
803
  "title": "강점 (Strengths)",
 
887
  tags: list[str]
888
  items: list[str]
889
 
 
890
  def analyze_with_swot(prompt: str) -> dict:
891
  prompt_lower = prompt.lower()
892
  results = {}
 
1707
  items=physical_transformation_categories["λ―Έν•™ 및 감성 κ²½ν—˜"]
1708
  )
1709
  ]
1710
+ # ──────────────────────────────── (쀑간 λΆ€λΆ„ μƒλž΅ 없이) ──────────────────────────
1711
 
 
1712
  def get_idea_system_prompt(selected_category: str | None = None,
1713
  selected_frameworks: list | None = None) -> str:
1714
  """
1715
+ λ””μžμΈ/발λͺ… λͺ©μ μ„ μœ„ν•΄ λ”μš± κ°•ν™”λœ μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ.
1716
+ - μ‚¬μš©μž μš”μ²­: "κ°€μž₯ μš°μˆ˜ν•œ 10κ°€μ§€ 아이디어"λ₯Ό 상세 μ„€λͺ…
1717
+ - κ²°κ³Ό 좜λ ₯에 'κ°€μž₯ μš°μˆ˜ν•œ 10κ°€μ§€ 아이디어'에 ν¬ν•¨λ˜μ§€ μ•Šμ€ 'λΆ€κ°€ 아이디어' 30κ°€μ§€ 리슀트(ν•œμ€„μ”©)도 μ„€λͺ…
1718
+ - κ²°κ³Ό 좜λ ₯ μ‹œ, 이미지 생성 μžλ™ν™”
1719
+ - Kaggle + μ›Ή 검색 좜처 μ œμ‹œ
1720
  """
1721
  cat_clause = (
1722
+ f'\n**μΆ”κ°€ μ§€μΉ¨**: μ„ νƒλœ μΉ΄ν…Œκ³ λ¦¬ "{selected_category}"λ₯Ό νŠΉλ³„νžˆ μš°μ„ ν•˜μ—¬ κ³ λ €ν•˜μ„Έμš”.\n'
 
1723
  ) if selected_category else ""
 
 
1724
  if not selected_frameworks:
1725
  selected_frameworks = []
1726
+ framework_instruction = "\n\n### (μ„ νƒλœ 기타 뢄석 ν”„λ ˆμž„μ›Œν¬)\n"
 
 
 
 
 
1727
  for fw in selected_frameworks:
1728
  if fw == "sunzi":
1729
+ framework_instruction += "- μ†μžλ³‘λ²• 36계\n"
 
 
 
 
1730
  elif fw == "swot":
1731
+ framework_instruction += "- SWOT 뢄석\n"
1732
  elif fw == "porter":
1733
+ framework_instruction += "- Porter의 5 Forces\n"
1734
  elif fw == "bcg":
1735
+ framework_instruction += "- BCG 맀트릭슀\n"
1736
+ # 핡심: "κ°€μž₯ μš°μˆ˜ν•œ 10κ°€μ§€ 아이디어λ₯Ό μ•„μ£Ό μƒμ„Έν•˜κ²Œ" + "각 아이디어별 이미지 ν”„λ‘¬ν”„νŠΈ" + "좜처 μ œμ‹œ"
1737
  base_prompt = f"""
1738
+ 당신은 창의적 λ””μžμΈ/발λͺ… μ „λ¬Έκ°€ AIμž…λ‹ˆλ‹€.
1739
+ μ‚¬μš©μžκ°€ μž…λ ₯ν•œ 주제λ₯Ό λΆ„μ„ν•˜μ—¬,
1740
+ **"κ°€μž₯ μš°μˆ˜ν•œ 5κ°€μ§€ λ””μžμΈ/발λͺ… 아이디어"**λ₯Ό λ„μΆœν•˜μ‹œμ˜€.
1741
+ 각 μ•„μ΄λ””μ–΄λŠ” λ‹€μŒ μš”κ΅¬λ₯Ό μΆ©μ‘±ν•΄μ•Ό ν•©λ‹ˆλ‹€:
1742
+ 1) **μ•„μ£Ό μƒμ„Έν•˜κ²Œ** μ„€λͺ…ν•˜μ—¬, λ…μžκ°€ 머릿속에 이미지λ₯Ό 그릴 수 μžˆμ„ μ •λ„λ‘œ ꡬ체적으둜 μ„œμˆ 
1743
+ 2) **이미지 ν”„λ‘¬ν”„νŠΈ**도 ν•¨κ»˜ μ œμ‹œν•˜μ—¬, μžλ™ 이미지 생성이 λ˜λ„λ‘ ν•˜λΌ
1744
+ - 예: `### 이미지 ν”„λ‘¬ν”„νŠΈ\\nν•œ 쀄 영문 문ꡬ`
1745
+ 3) **Kaggle 데이터셋**, **μ›Ή 검색**을 ν™œμš©ν•œ 톡찰(λ˜λŠ” μ°Έμ‘°)이 있으면 λ°˜λ“œμ‹œ 결과에 μ–ΈκΈ‰
1746
+ 4) μ΅œμ’… 좜λ ₯의 λ§ˆμ§€λ§‰μ— **"좜처"** μ„Ήμ…˜μ„ λ§Œλ“€κ³ ,
1747
+ - μ›Ή 검색(Brave)μ—μ„œ μ°Έμ‘°ν•œ URL 3~5개
1748
+ - Kaggle 데이터셋 이름/URL(μžˆλ‹€λ©΄)
1749
+ - κ·Έ λ°–μ˜ μ°Έκ³  자료
1750
+ 5) **λΆ€κ°€ 아이디어** 5가지에 ν¬ν•¨λ˜μ§€ μ•Šμ€ λ‹€μŒ μˆœμ„œ 10개λ₯Ό μžμ„Έν•˜κ²Œ μž‘μ„±ν•˜μ—¬ κΈ΄ ν•œμ€„λ‘œ 각 λΌμΈλ³„λ‘œ μ„€λͺ…/좜λ ₯
1751
+ - 예: `#### λΆ€κ°€ 아이디어 X:\\nν•œ 쀄 ν•œκΈ€ 문ꡬ`
1752
+
 
1753
  {framework_instruction}
1754
 
1755
+ ## 아이디어 평가 κΈ°μ€€
1756
+ μ•„οΏ½οΏ½οΏ½λ””μ–΄ μ„ μ • μ‹œ λ‹€μŒ κΈ°μ€€μœΌλ‘œ ν‰κ°€ν•˜κ³  μ μˆ˜ν™”ν•˜μ—¬ μˆœμœ„λ₯Ό λ§€κΈ°μ‹­μ‹œμ˜€:
1757
+ 1. **ν˜μ‹ μ„±** (30%): κΈ°μ‘΄ μ†”λ£¨μ…˜κ³Όμ˜ 차별성, 독창성, 기술적 진보성
1758
+ 2. **μ‹€ν˜„ κ°€λŠ₯μ„±** (25%): 기술적, 경제적 μ‹€ν˜„ κ°€λŠ₯μ„±, κ΅¬ν˜„ λ‚œμ΄λ„
1759
+ 3. **μ‹œμž₯ 잠재λ ₯** (20%): νƒ€κ²Ÿ μ‹œμž₯ 규λͺ¨, μ„±μž₯ κ°€λŠ₯μ„±, μˆ˜μ΅μ„±, ROI
1760
+ 4. **μ‚¬νšŒμ  영ν–₯λ ₯** (15%): μ‚¬νšŒ, ν™˜κ²½μ  문제 ν•΄κ²° 기여도, μ‚Άμ˜ 질 ν–₯상 정도
1761
+ 5. **ν™•μž₯μ„±** (10%): λ‹€μ–‘ν•œ 상황/μ‹œμž₯으둜 ν™•μž₯ κ°€λŠ₯μ„±, μœ΅ν•© κ°€λŠ₯μ„±
1762
+
1763
+ 좜λ ₯은 λ°˜λ“œμ‹œ **ν•œκ΅­μ–΄**둜 ν•˜λ©°, μ•„λž˜ ꡬ쑰λ₯Ό μ€€μˆ˜ν•˜μ‹­μ‹œμ˜€:
1764
+ 1. **주제 μš”μ•½** (μ‚¬μš©μž 질문 μš”μ•½ 및 뢄석 μ ‘κ·Ό 방식 - 300자 이내)
1765
+ 2. **Top 5 아이디어 κ°œμš”** (5개 아이디어 μš”μ•½ 및 μ„ μ • 이유 κ°„λž΅νžˆ - 400단어 이내)
1766
+ 3. **Top 5 아이디어 상세**
1767
+ - 각 μ•„μ΄λ””μ–΄λŠ” λ‹€μŒ 체계적인 ꡬ쑰둜 μ „κ°œν•˜μ‹­μ‹œμ˜€:
1768
+ - ### 아이디어 X: [아이디어λͺ…] (μ’…ν•© 점수: x.x/10)
1769
+ - #### 핡심 κ°œλ…
1770
+ * μ•„μ΄λ””μ–΄μ˜ 핡심 원리와 μž‘λ™ λ©”μ»€λ‹ˆμ¦˜μ„ 400자 이상 μƒμ„Ένžˆ μ„€λͺ…
1771
+ * ν•΄κ²°ν•˜κ³ μž ν•˜λŠ” ꡬ체적인 λ¬Έμ œμ™€ κ·Έ μ‚¬νšŒμ /경제적 μ€‘μš”μ„±
1772
+ * κΈ°μ‘΄ μ†”λ£¨μ…˜ λŒ€λΉ„ ν˜μ‹ μ μΈ 차별점 3κ°€μ§€ 이상 λͺ…ν™•νžˆ μ œμ‹œ
1773
+ * 핡심 κ°€μΉ˜ μ œμ•ˆ(Value Proposition) λͺ…ν™•νžˆ μ •μ˜
1774
+ - #### 상세 섀계 및 기술적 κ΅¬ν˜„
1775
+ * ꡬ체적인 κ΅¬μ„±μš”μ†Œ, λ””μžμΈ νŠΉμ„±, μ œμž‘ 방법 λ“± 기술적 세뢀사항 μ„€λͺ…
1776
+ * 치수, 재료, μž‘λ™ 원리 λ“± μ‹€ν˜„ κ°€λŠ₯ν•œ 상세 정보 제곡
1777
+ * 핡심 기술적 λ„μ „κ³Όμ œ 3κ°€μ§€ 이상과 각각에 λŒ€ν•œ ν•΄κ²° λ°©μ•ˆ
1778
+ * νŠΉν—ˆ κ°€λŠ₯성이 μžˆλŠ” 고유 기술 μš”μ†Œ μ„€λͺ…
1779
+ * ν•„μš”ν•œ 핡심 기술 및 λ¦¬μ†ŒμŠ€ λͺ©λ‘
1780
+ - #### μ‚¬μš© μ‹œλ‚˜λ¦¬μ˜€ 및 μ‚¬μš©μž κ²½ν—˜
1781
+ * μ΅œμ†Œ 3κ°€μ§€ μ΄μƒμ˜ μ‹€μ œ μ‚¬μš© 상황 μ‹œλ‚˜λ¦¬μ˜€λ₯Ό μŠ€ν† λ¦¬ν…”λ§ λ°©μ‹μœΌλ‘œ μ„€λͺ…
1782
+ * μ£Όμš” μ‚¬μš©μž 페λ₯΄μ†Œλ‚˜ 2개 이상 ꡬ체적으둜 μ •μ˜
1783
+ * μ‚¬μš©μž μ—¬μ •(User Journey)을 λ‹¨κ³„λ³„λ‘œ μ‹œκ°μ μœΌλ‘œ λ¬˜μ‚¬
1784
+ * μ‚¬μš©μž κ²½ν—˜μ˜ 핡심 κ°€μΉ˜μ™€ 감성적 연결점 μ„€λͺ…
1785
+ * 잠재적 μ‚¬μš©μž ν”Όλ“œλ°± 예츑 및 λŒ€μ‘ λ°©μ•ˆ
1786
+ - #### μ‹œμž₯ 뢄석 및 λΉ„μ¦ˆλ‹ˆμŠ€ λͺ¨λΈ
1787
+ * νƒ€κ²Ÿ μ‹œμž₯ 규λͺ¨(TAM, SAM, SOM)와 μ„±μž₯λ₯  μΆ”μ •
1788
+ * μ£Όμš” 고객 μ„Έκ·Έλ¨ΌνŠΈ 뢄석 및 ꡬ체적인 λ‹ˆμ¦ˆ μ—°κ²°
1789
+ * 경쟁 μ œν’ˆ/μ„œλΉ„μŠ€ 5개 μ΄μƒκ³Όμ˜ 상세 λΉ„κ΅ν‘œ 및 경쟁 μš°μœ„μ 
1790
+ * 수읡 λͺ¨λΈ 및 수읡 흐름 상세 μ„€λͺ…
1791
+ * μ‹œμž₯ μ§„μž… μ „λž΅ 및 초기 λ§ˆμΌ€νŒ… 접근법
1792
+ * ν™•μž₯ κ°€λŠ₯ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ λͺ¨λΈ μΊ”λ²„μŠ€ μš”μ†Œ 뢄석
1793
+ - #### κ΅¬ν˜„ λ‘œλ“œλ§΅ 및 μžμ› κ³„νš
1794
+ * μ‹€ν˜„μ„ μœ„ν•œ 단계별 κ³„νš(κ°œλ…μ¦λͺ…, ν”„λ‘œν† νƒ€μž…, ν…ŒμŠ€νŠΈ, 생산 λ“±)
1795
+ * 6κ°œμ›”, 1λ…„, 3λ…„ λ‹¨μœ„μ˜ ꡬ체적인 개발 일정 및 μ£Όμš” λ§ˆμΌμŠ€ν†€
1796
+ * ν•„μš”ν•œ 핡심 인재/νŒ€ ꡬ성 및 μ—­ν• 
1797
+ * 초기 투자 μ˜ˆμƒμ•‘ 및 μžκΈˆμ‘°λ‹¬ μ „λž΅
1798
+ * μ£Όμš” νŒŒνŠΈλ„ˆμ‹­ 및 μ™ΈλΆ€ ν˜‘λ ₯ ν•„μš”μ‚¬ν•­
1799
+ * ν’ˆμ§ˆ 관리 및 μ„±κ³Ό μΈ‘μ • μ§€ν‘œ
1800
+ - #### SWOT 뢄석
1801
+ * 강점(Strengths): 이 μ•„μ΄λ””μ–΄λ§Œμ˜ λ…νŠΉν•œ 강점 5κ°€μ§€ 이상과 κ·Έ 이유
1802
+ * 약점(Weaknesses): 잠재적 약점 3κ°€μ§€ 이상 및 이λ₯Ό κ·Ήλ³΅ν•˜κΈ° μœ„ν•œ ꡬ체적인 λ°©μ•ˆ
1803
+ * 기회(Opportunities): μ™ΈλΆ€ ν™˜κ²½(기술, μ‹œμž₯, μ •μ±… λ“±)μ—μ„œ λ°œμƒν•˜λŠ” 기회 μš”μ†Œ 4κ°€μ§€ 이상
1804
+ * μœ„ν˜‘(Threats): 성곡을 λ°©ν•΄ν•  수 μžˆλŠ” μ™ΈλΆ€ μš”μΈ 3κ°€μ§€ 이상과 각각에 λŒ€ν•œ ꡬ체적 λŒ€μ‘μ±…
1805
+ 각 μƒμ„Ένžˆ μž‘μ„±
1806
+ - 각 μ•„μ΄λ””μ–΄λŠ” 이 ꡬ쑰둜 10개 아이디어 λͺ¨λ‘ λ™μΌν•˜κ²Œ μž‘μ„±ν•˜λΌ:
1807
+
1808
+ 4. **뢀가적 톡찰** (μ„ νƒλœ ν”„λ ˆμž„μ›Œν¬ 뢄석 κ²°κ³Ό)
1809
+ 5. **λΆ€κ°€ 아이디어** (TOP 5에 ν•΄λ‹Ήν•˜μ§€ μ•ŠλŠ” 10κ°€μ§€ 아이디어, 각각 ν•œ μ€„λ‘œ κ°„κ²°ν•˜κ²Œ μ„€λͺ…ν•˜λ˜ ν•΄λ‹Ή μ•„μ΄λ””μ–΄μ˜ 핡심 κ°€μΉ˜μ™€ ν˜μ‹ μ μ„ 포함)
1810
+ - 예: `#### λΆ€κ°€ 아이디어 X:\\n ν•œ μ€„λ‘œ μžμ„Έν•œ ν•œκΈ€ 문ꡬ`
1811
+ 6. **좜처** (웹검색 링크, Kaggle 데이터셋 λ“±)
1812
  {cat_clause}
1813
+ 아무리 길어도 이 μš”κ΅¬μ‚¬ν•­μ„ μ€€μˆ˜ν•˜κ³ , **였직 μ΅œμ’… μ™„μ„±λœ λ‹΅λ³€**만 좜λ ₯ν•˜μ‹­μ‹œμ˜€.
1814
+ (λ‚΄λΆ€ 사고 과정은 감μΆ₯λ‹ˆλ‹€.)
 
 
1815
  """
1816
  return base_prompt.strip()
1817
 
1818
+ # ──────────────────────────────── λ‚˜λ¨Έμ§€ μ½”λ“œ (웹검색, kaggle, 이미지 생성 λ“±) ──────────────────────────
1819
+
1820
  @st.cache_data(ttl=3600)
1821
  def brave_search(query: str, count: int = 20):
1822
  if not BRAVE_KEY:
 
1972
 
1973
  # μ˜ˆμ‹œ 주제
1974
  example_topics = {
1975
+ "example1": "'고양이 μž₯λ‚œκ°' λ””μžμΈ",
1976
+ "example2": "재밍 λŒ€μ‘ κ°€λŠ₯ν•œ λ“œλ‘  λ””μžμΈ",
1977
  "example3": "μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€(UI/UX) ν˜μ‹ μ„ μœ„ν•œ μ›¨μ–΄λŸ¬λΈ” κΈ°κΈ° 아이디어"
1978
  }
1979
  sb.subheader("Example Topics")
1980
  c1, c2, c3 = sb.columns(3)
1981
+ if c1.button("고양이 μž₯λ‚œκ°", key="ex1"):
1982
  process_example(example_topics["example1"])
1983
+ if c2.button("재밍 λŒ€μ‘ λ“œλ‘ ", key="ex2"):
1984
  process_example(example_topics["example2"])
1985
  if c3.button("UI/UX ν˜μ‹ ", key="ex3"):
1986
  process_example(example_topics["example3"])
 
2076
  def process_input(prompt: str, uploaded_files):
2077
  """
2078
  메인 μ±„νŒ… μž…λ ₯을 λ°›μ•„ λ””μžμΈ/발λͺ… 아이디어λ₯Ό μƒμ„±ν•œλ‹€.
2079
+ 슀트리밍 μ‹€νŒ¨(RemoteProtocolError λ“±) μ‹œ backoff μž¬μ‹œλ„ ν›„
2080
+ μ΅œμ’…μ μœΌλ‘œ non-stream 호좜둜 폴백.
2081
  """
2082
+ # ─── λŒ€ν™” 기둝 쀑볡 λ°©μ§€ ──────────────────────────────
2083
  if not any(m["role"] == "user" and m["content"] == prompt for m in st.session_state.messages):
2084
  st.session_state.messages.append({"role": "user", "content": prompt})
2085
  with st.chat_message("user"):
 
2091
  and st.session_state.messages[i + 1]["role"] == "assistant"):
2092
  return
2093
 
2094
+ # ─── κ²°κ³Ό 생성 ───────────────────────────────────────
2095
  with st.chat_message("assistant"):
2096
  status = st.status("Preparing to generate invention ideas…")
2097
  stream_placeholder = st.empty()
 
2101
  client = get_openai_client()
2102
  status.update(label="Initializing model…")
2103
 
2104
+ selected_cat = st.session_state.get("category_focus", None)
2105
  selected_frameworks = st.session_state.get("selected_frameworks", [])
 
 
2106
  sys_prompt = get_idea_system_prompt(
2107
  selected_category=selected_cat,
2108
  selected_frameworks=selected_frameworks
 
2117
  use_kaggle = st.session_state.kaggle_enabled
2118
  has_uploaded = bool(uploaded_files)
2119
 
2120
+ search_content = kaggle_content = file_content = mil_content = None
 
 
2121
 
2122
+ # β‘  μ›Ή 검색
2123
  if use_web_search:
2124
  status.update(label="Searching the web…")
2125
  with st.spinner("Searching…"):
 
2154
  with st.spinner("Processing files…"):
2155
  file_content = process_uploaded_files(uploaded_files)
2156
 
2157
+ # β‘£ ꡰ사 μ „μˆ  데이터
 
2158
  if is_military_query(prompt):
2159
  status.update(label="Searching military tactics dataset…")
2160
  with st.spinner("Loading military insights…"):
 
2169
  f"**Defense Reasoning:** {row['defense_reasoning']}\n\n---\n"
2170
  )
2171
 
2172
+ # ─── μœ μ € μ½˜ν…μΈ  ꡬ성 ──────────────────────────
2173
  user_content = prompt
2174
+ for extra in (search_content, kaggle_content, file_content, mil_content):
2175
+ if extra:
2176
+ user_content += "\n\n" + extra
2177
+
2178
+ # ─── λ‚΄λΆ€ 뢄석 ───────────────────────────────
 
 
 
 
 
2179
  status.update(label="뢄석 쀑…")
2180
  decision_purpose = identify_decision_purpose(prompt)
2181
  relevance_scores = compute_relevance_scores(prompt, PHYS_CATEGORIES)
 
2210
  for c, s in decision_purpose['constraints']:
2211
  purpose_info += f"- **{c}** (κ΄€λ ¨μ„±: {s})\n"
2212
 
2213
+ # ─── ν”„λ ˆμž„μ›Œν¬ 뢄석 (μ˜΅μ…˜) ────────────────────
2214
  framework_contents = []
2215
  for fw in selected_frameworks:
2216
  if fw == "swot":
2217
+ framework_contents.append(
2218
+ format_business_framework_analysis("swot", analyze_with_swot(prompt))
2219
+ )
2220
  elif fw == "porter":
2221
+ framework_contents.append(
2222
+ format_business_framework_analysis("porter", analyze_with_porter(prompt))
2223
+ )
2224
  elif fw == "bcg":
2225
+ framework_contents.append(
2226
+ format_business_framework_analysis("bcg", analyze_with_bcg(prompt))
2227
+ )
 
 
 
2228
 
2229
  if framework_contents:
2230
  user_content += "\n\n## (Optional) 기타 ν”„λ ˆμž„μ›Œν¬ 뢄석\n\n" + "\n\n".join(framework_contents)
 
2232
  user_content += f"\n\n## μΉ΄ν…Œκ³ λ¦¬ 맀트릭슀 뢄석{purpose_info}\n{combos_table}"
2233
 
2234
  status.update(label="Generating final design/invention ideas…")
2235
+
2236
  api_messages = [
2237
  {"role": "system", "content": sys_prompt},
2238
  {"role": "system", "name": "category_db", "content": category_context(selected_cat)},
2239
  {"role": "user", "content": user_content},
2240
  ]
2241
+
2242
+ # ─── OpenAI Chat 호좜 (backoff μž¬μ‹œλ„) ─────────────────
2243
+ @backoff.on_exception(
2244
+ (RemoteProtocolError, APITimeoutError, APIError), max_tries=3
 
 
 
2245
  )
2246
+ def safe_stream():
2247
+ return client.chat.completions.create(
2248
+ model="gpt-4.1-mini",
2249
+ messages=api_messages,
2250
+ temperature=1,
2251
+ max_tokens=MAX_TOKENS,
2252
+ top_p=1,
2253
+ stream=True
2254
+ )
2255
 
 
 
 
 
2256
 
2257
+ try:
2258
+ stream = safe_stream()
2259
+ for chunk in stream:
2260
+ if chunk.choices and chunk.choices[0].delta.content:
2261
+ full_response += chunk.choices[0].delta.content
2262
+ stream_placeholder.markdown(full_response + "β–Œ")
2263
+ except (RemoteProtocolError, APITimeoutError, APIError) as stream_err:
2264
+ logging.warning(f"슀트리밍 μ‹€νŒ¨, non-stream 폴백: {stream_err}")
2265
+ resp = client.chat.completions.create(
2266
+ model="gpt-4.1-mini",
2267
+ messages=api_messages,
2268
+ temperature=1,
2269
+ max_tokens=MAX_TOKENS,
2270
+ top_p=1,
2271
+ stream=False
2272
+ )
2273
+ full_response = resp.choices[0].message.content
2274
+ stream_placeholder.markdown(full_response)
2275
+
2276
  status.update(label="Invention ideas created!", state="complete")
2277
 
2278
+ # ─── 이미지 생성 ────────────────────────────────
2279
  img_data = img_caption = None
2280
  if st.session_state.generate_image and full_response:
2281
  match = re.search(r"###\s*이미지\s*ν”„λ‘¬ν”„νŠΈ\s*\n+([^\n]+)", full_response, re.I)
 
2288
  if img_data:
2289
  st.image(img_data, caption=f"Visualized Concept – {img_caption}")
2290
 
2291
+ # ─── μ„Έμ…˜ λ©”μ‹œμ§€ μ €μž₯ ─────────────────────────────
2292
  answer_msg = {"role": "assistant", "content": full_response}
2293
  if img_data:
2294
  answer_msg["image"] = img_data
 
2296
  st.session_state["_skip_dup_idx"] = len(st.session_state.messages)
2297
  st.session_state.messages.append(answer_msg)
2298
 
2299
+ # ─── λ‹€μš΄λ‘œλ“œ μ˜΅μ…˜ ──────────────────────────────
2300
  st.subheader("Download This Output")
2301
  col_md, col_html = st.columns(2)
2302
  col_md.download_button(
 
2324
  {"role": "assistant", "content": f"⚠️ 였λ₯˜: {e}"}
2325
  )
2326
 
2327
+
2328
  def main():
2329
  idea_generator_app()
2330