Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +148 -38
src/streamlit_app.py
CHANGED
|
@@ -1,40 +1,150 @@
|
|
| 1 |
-
|
| 2 |
-
import
|
| 3 |
-
import
|
|
|
|
| 4 |
import streamlit as st
|
|
|
|
| 5 |
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
import os, io, time, json, re, requests
|
| 3 |
+
from io import BytesIO # ํ์ผ์ด ์์ด๋, ๋ฉ๋ชจ๋ฆฌ ์ ๋ฐ์ดํฐ(๋ฐ์ดํธ์ด) ๋ฅผ ํ์ผ์ฒ๋ผ ๋ค๋ฃฐ ์ ์๊ฒ ํด์ค. ์น์์ ๋ฐ์ ์ด๋ฏธ์ง/ํ์ผ์ ์ ์ฅํ์ง ์๊ณ ๋ฐ๋ก ์ฌ์ฉ
|
| 4 |
+
from PIL import Image
|
| 5 |
import streamlit as st
|
| 6 |
+
from google import genai
|
| 7 |
|
| 8 |
+
st.set_page_config(page_title="๋ฉํฐ๋ชจ๋ฌ LLM ๋ฐ๋ชจ (Gemma 3 27B)", page_icon="๐ค", layout="wide")
|
| 9 |
+
|
| 10 |
+
# ===== Sidebar: ์ค์ =====
|
| 11 |
+
st.sidebar.title("๐ง ์ค์ ")
|
| 12 |
+
api_key = st.sidebar.text_input("GOOGLE_API_KEY", value=os.getenv("GOOGLE_API_KEY", ""), type="password")
|
| 13 |
+
model_name = st.sidebar.selectbox("๋ชจ๋ธ", ["gemma-3-27b-it"], index=0)
|
| 14 |
+
rate_delay = st.sidebar.number_input("ํธ์ถ๊ฐ ๋๊ธฐ(์ด)", value=1.0, step=0.5, min_value=0.0)
|
| 15 |
+
|
| 16 |
+
if not api_key:
|
| 17 |
+
st.warning("์ฌ์ด๋๋ฐ์ GOOGLE_API_KEY๋ฅผ ์
๋ ฅํ๊ฑฐ๋ ํ๊ฒฝ๋ณ์๋ก ์ค์ ํด ์ฃผ์ธ์.")
|
| 18 |
+
st.stop()
|
| 19 |
+
|
| 20 |
+
client = genai.Client(api_key=api_key)
|
| 21 |
+
|
| 22 |
+
# ===== ์ ํธ =====
|
| 23 |
+
RETRY_LIMIT = 3
|
| 24 |
+
|
| 25 |
+
def load_image(source: str) -> Image.Image:
|
| 26 |
+
"""URL/๋ก์ปฌ ๋ชจ๋ ์ง์ + ํฌ๋ช
๋ฐฐ๊ฒฝ ๋ณด์ ."""
|
| 27 |
+
session = requests.Session()
|
| 28 |
+
session.headers.update({"User-Agent": "Mozilla/5.0"})
|
| 29 |
+
last_err = None
|
| 30 |
+
for _ in range(RETRY_LIMIT):
|
| 31 |
+
try:
|
| 32 |
+
if source.startswith(("http://", "https://")):
|
| 33 |
+
resp = session.get(source, timeout=10)
|
| 34 |
+
resp.raise_for_status()
|
| 35 |
+
img = Image.open(BytesIO(resp.content))
|
| 36 |
+
else:
|
| 37 |
+
img = Image.open(source)
|
| 38 |
+
|
| 39 |
+
if img.mode in ("RGBA", "LA", "P"):
|
| 40 |
+
bg = Image.new("RGB", img.size, (255, 255, 255))
|
| 41 |
+
img = img.convert("RGBA")
|
| 42 |
+
bg.paste(img, mask=img.split()[-1] if len(img.split()) > 3 else None)
|
| 43 |
+
img = bg
|
| 44 |
+
else:
|
| 45 |
+
img = img.convert("RGB")
|
| 46 |
+
return img
|
| 47 |
+
except Exception as e:
|
| 48 |
+
last_err = e
|
| 49 |
+
time.sleep(0.5)
|
| 50 |
+
raise RuntimeError(f"์ด๋ฏธ์ง ๋ก๋ ์คํจ: {last_err}")
|
| 51 |
+
|
| 52 |
+
RESET_PROMPT = "์ด์ ๋ํ๋ ๋ฌด์ํ๊ณ , ์๋ ์ง์์๋ง ์๋ตํ์ธ์.\n\n"
|
| 53 |
+
|
| 54 |
+
def try_parse_json(raw_text: str):
|
| 55 |
+
"""์๋ต์์ JSON ์ถ์ถ ์๋."""
|
| 56 |
+
pattern = r'```(?:json)?\s*(\{[\s\S]*?\})\s*```|(\{[\s\S]*?\})'
|
| 57 |
+
for g1, g2 in re.findall(pattern, raw_text):
|
| 58 |
+
cand = (g1 or g2).strip()
|
| 59 |
+
try:
|
| 60 |
+
return json.loads(cand)
|
| 61 |
+
except json.JSONDecodeError:
|
| 62 |
+
pass
|
| 63 |
+
cleaned = re.sub(r'```json|```', '', raw_text).strip()
|
| 64 |
+
try:
|
| 65 |
+
return json.loads(cleaned)
|
| 66 |
+
except json.JSONDecodeError:
|
| 67 |
+
return None
|
| 68 |
+
|
| 69 |
+
def infer_text(article: str, prompt: str):
|
| 70 |
+
contents = [RESET_PROMPT + prompt.strip() + "\n\n" + article.strip()]
|
| 71 |
+
resp = client.models.generate_content(model=model_name, contents=contents)
|
| 72 |
+
time.sleep(rate_delay)
|
| 73 |
+
text = (resp.text or "").strip()
|
| 74 |
+
parsed = try_parse_json(text)
|
| 75 |
+
return parsed if parsed is not None else {"text": text}
|
| 76 |
+
|
| 77 |
+
def infer_image(image: Image.Image, prompt: str):
|
| 78 |
+
contents = [RESET_PROMPT + prompt.strip(), image]
|
| 79 |
+
resp = client.models.generate_content(model=model_name, contents=contents)
|
| 80 |
+
time.sleep(rate_delay)
|
| 81 |
+
text = (resp.text or "").strip()
|
| 82 |
+
parsed = try_parse_json(text)
|
| 83 |
+
return parsed if parsed is not None else {"text": text}
|
| 84 |
+
|
| 85 |
+
# ===== ๊ธฐ๋ณธ ํ๋กฌํํธ(๊ฐ๊ฒฐ ๋ฒ์ ) =====
|
| 86 |
+
TEXT_PROMPT = (
|
| 87 |
+
"๋น์ ์ ๊ธฐ์ฌ ์ ๋ณด์ ๋ถ์ ์ ๋ฌธ๊ฐ์
๋๋ค. ๋ค์ ๊ธฐ์ฌ์์ ์ ๋ณด์์ ์ถ์ถํ๊ณ , "
|
| 88 |
+
"์ ๋ณด์๋ณ ๋ฌ์ฌ ํ๋ ์์ ๊ธ์ /์ค๋ฆฝ/๋ถ์ ์ผ๋ก ํ์ ํด JSON์ผ๋ก๋ง ์ถ๋ ฅํ์ธ์.\n"
|
| 89 |
+
'์์: {"sources": ["์ ๋ณด์A","์ ๋ณด์B"], "frames": ["์ค๋ฆฝ","๋ถ์ "]}'
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
IMAGE_PROMPT = (
|
| 93 |
+
"๋น์ ์ ๋ณด๋์ฌ์ง ๋ถ์ ์ ๋ฌธ๊ฐ์
๋๋ค. ์ ๊ณต๋ ์ฌ์ง์์ Donald Trump ์กด์ฌ ์ฌ๋ถ๋ฅผ ํ๋จํ๊ณ "
|
| 94 |
+
"๊ฐ์ (emotion: positive/negative/neutral)๊ณผ ์ญ๋์ฑ(dynamism: high/medium/low)์ ํ๊ฐํด "
|
| 95 |
+
'JSON์ผ๋ก๋ง ์ถ๋ ฅํ์ธ์. Trump๊ฐ ์์ผ๋ฉด {"trump_present": false}๋ง ๋ฐํํ์ธ์.'
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
# ===== UI =====
|
| 99 |
+
st.title("๐ค ๋ฉํฐ๋ชจ๋ฌ LLM ๋ฐ๋ชจ (Gemma 3 27B)")
|
| 100 |
+
tab_text, tab_img = st.tabs(["๐ ํ
์คํธ ๋ถ์", "๐ผ๏ธ ์ด๋ฏธ์ง ๋ถ์"])
|
| 101 |
+
|
| 102 |
+
with tab_text:
|
| 103 |
+
st.subheader("๊ธฐ์ฌ ํ
์คํธ โ ์ ๋ณด์ & ํ๋ ์ ํ์ ")
|
| 104 |
+
article = st.text_area(
|
| 105 |
+
"๊ธฐ์ฌ ๋ณธ๋ฌธ", height=180,
|
| 106 |
+
value="Donald Trump์ Nancy Pelosi๊ฐ ํ์์ฅ์์ ๊ฒฉ๋ ฌํ ๋
ผ์์ ๋ฒ์๋ค..."
|
| 107 |
+
)
|
| 108 |
+
user_prompt = st.text_area("ํ๋กฌํํธ(์ต์
)", value=TEXT_PROMPT, height=120)
|
| 109 |
+
if st.button("ํ
์คํธ ๋ถ์ ์คํ", type="primary", use_container_width=True):
|
| 110 |
+
if not article.strip():
|
| 111 |
+
st.error("๊ธฐ์ฌ ๋ณธ๋ฌธ์ ์
๋ ฅํด ์ฃผ์ธ์.")
|
| 112 |
+
else:
|
| 113 |
+
with st.spinner("๋ถ์ ์ค..."):
|
| 114 |
+
try:
|
| 115 |
+
result = infer_text(article, user_prompt or TEXT_PROMPT)
|
| 116 |
+
st.success("์๋ฃ")
|
| 117 |
+
st.json(result)
|
| 118 |
+
st.download_button("๊ฒฐ๊ณผ JSON ๋ค์ด๋ก๋", data=json.dumps(result, ensure_ascii=False, indent=2),
|
| 119 |
+
file_name="text_result.json", mime="application/json")
|
| 120 |
+
except Exception as e:
|
| 121 |
+
st.error(f"์ค๋ฅ: {e}")
|
| 122 |
+
|
| 123 |
+
with tab_img:
|
| 124 |
+
st.subheader("๋ณด๋์ฌ์ง โ ์ธ๋ฌผ ์กด์ฌยท๊ฐ์ ยท์ญ๋์ฑ ๋ถ์")
|
| 125 |
+
col1, col2 = st.columns(2)
|
| 126 |
+
with col1:
|
| 127 |
+
img_url = st.text_input("์ด๋ฏธ์ง URL")
|
| 128 |
+
with col2:
|
| 129 |
+
file = st.file_uploader("์ด๋ฏธ์ง ์
๋ก๋", type=["jpg","jpeg","png","webp","gif","bmp"])
|
| 130 |
+
|
| 131 |
+
img_prompt = st.text_area("ํ๋กฌํํธ(์ต์
)", value=IMAGE_PROMPT, height=120)
|
| 132 |
+
if st.button("์ด๋ฏธ์ง ๋ถ์ ์คํ", type="primary", use_container_width=True):
|
| 133 |
+
try:
|
| 134 |
+
if file is not None:
|
| 135 |
+
image = Image.open(io.BytesIO(file.read())).convert("RGB")
|
| 136 |
+
elif img_url.strip():
|
| 137 |
+
image = load_image(img_url.strip())
|
| 138 |
+
else:
|
| 139 |
+
st.error("์ด๋ฏธ์ง URL์ ์
๋ ฅํ๊ฑฐ๋ ํ์ผ์ ์
๋ก๋ํด ์ฃผ์ธ์.")
|
| 140 |
+
st.stop()
|
| 141 |
+
|
| 142 |
+
st.image(image, caption="์
๋ ฅ ์ด๋ฏธ์ง", use_container_width=True)
|
| 143 |
+
with st.spinner("๋ถ์ ์ค..."):
|
| 144 |
+
result = infer_image(image, img_prompt or IMAGE_PROMPT)
|
| 145 |
+
st.success("์๋ฃ")
|
| 146 |
+
st.json(result)
|
| 147 |
+
st.download_button("๊ฒฐ๊ณผ JSON ๋ค์ด๋ก๋", data=json.dumps(result, ensure_ascii=False, indent=2),
|
| 148 |
+
file_name="image_result.json", mime="application/json")
|
| 149 |
+
except Exception as e:
|
| 150 |
+
st.error(f"์ค๋ฅ: {e}")
|