Spaces:
Sleeping
Sleeping
| # app.py | |
| import os, io, time, json, re, requests | |
| from io import BytesIO # ํ์ผ์ด ์์ด๋, ๋ฉ๋ชจ๋ฆฌ ์ ๋ฐ์ดํฐ(๋ฐ์ดํธ์ด) ๋ฅผ ํ์ผ์ฒ๋ผ ๋ค๋ฃฐ ์ ์๊ฒ ํด์ค. ์น์์ ๋ฐ์ ์ด๋ฏธ์ง/ํ์ผ์ ์ ์ฅํ์ง ์๊ณ ๋ฐ๋ก ์ฌ์ฉ | |
| from PIL import Image | |
| import streamlit as st | |
| from google import genai | |
| st.set_page_config(page_title="๋ฉํฐ๋ชจ๋ฌ LLM ๋ฐ๋ชจ (Gemma 3 27B)", page_icon="๐ค", layout="wide") | |
| # ===== Sidebar: ์ค์ ===== | |
| st.sidebar.title("๐ง ์ค์ ") | |
| api_key = st.sidebar.text_input("GOOGLE_API_KEY", value=os.getenv("GOOGLE_API_KEY", ""), type="password") | |
| model_name = st.sidebar.selectbox("๋ชจ๋ธ", ["gemma-3-27b-it"], index=0) | |
| rate_delay = st.sidebar.number_input("ํธ์ถ๊ฐ ๋๊ธฐ(์ด)", value=1.0, step=0.5, min_value=0.0) | |
| if not api_key: | |
| st.warning("์ฌ์ด๋๋ฐ์ GOOGLE_API_KEY๋ฅผ ์ ๋ ฅํ๊ฑฐ๋ ํ๊ฒฝ๋ณ์๋ก ์ค์ ํด ์ฃผ์ธ์.") | |
| st.stop() | |
| client = genai.Client(api_key=api_key) | |
| # ===== ์ ํธ ===== | |
| RETRY_LIMIT = 3 | |
| def load_image(source: str) -> Image.Image: | |
| """URL/๋ก์ปฌ ๋ชจ๋ ์ง์ + ํฌ๋ช ๋ฐฐ๊ฒฝ ๋ณด์ .""" | |
| session = requests.Session() | |
| session.headers.update({"User-Agent": "Mozilla/5.0"}) | |
| last_err = None | |
| for _ in range(RETRY_LIMIT): | |
| try: | |
| if source.startswith(("http://", "https://")): | |
| resp = session.get(source, timeout=10) | |
| resp.raise_for_status() | |
| img = Image.open(BytesIO(resp.content)) | |
| else: | |
| img = Image.open(source) | |
| if img.mode in ("RGBA", "LA", "P"): | |
| bg = Image.new("RGB", img.size, (255, 255, 255)) | |
| img = img.convert("RGBA") | |
| bg.paste(img, mask=img.split()[-1] if len(img.split()) > 3 else None) | |
| img = bg | |
| else: | |
| img = img.convert("RGB") | |
| return img | |
| except Exception as e: | |
| last_err = e | |
| time.sleep(0.5) | |
| raise RuntimeError(f"์ด๋ฏธ์ง ๋ก๋ ์คํจ: {last_err}") | |
| RESET_PROMPT = "์ด์ ๋ํ๋ ๋ฌด์ํ๊ณ , ์๋ ์ง์์๋ง ์๋ตํ์ธ์.\n\n" | |
| def try_parse_json(raw_text: str): | |
| """์๋ต์์ JSON ์ถ์ถ ์๋.""" | |
| pattern = r'```(?:json)?\s*(\{[\s\S]*?\})\s*```|(\{[\s\S]*?\})' | |
| for g1, g2 in re.findall(pattern, raw_text): | |
| cand = (g1 or g2).strip() | |
| try: | |
| return json.loads(cand) | |
| except json.JSONDecodeError: | |
| pass | |
| cleaned = re.sub(r'```json|```', '', raw_text).strip() | |
| try: | |
| return json.loads(cleaned) | |
| except json.JSONDecodeError: | |
| return None | |
| def infer_text(article: str, prompt: str): | |
| contents = [RESET_PROMPT + prompt.strip() + "\n\n" + article.strip()] | |
| resp = client.models.generate_content(model=model_name, contents=contents) | |
| time.sleep(rate_delay) | |
| text = (resp.text or "").strip() | |
| parsed = try_parse_json(text) | |
| return parsed if parsed is not None else {"text": text} | |
| def infer_image(image: Image.Image, prompt: str): | |
| contents = [RESET_PROMPT + prompt.strip(), image] | |
| resp = client.models.generate_content(model=model_name, contents=contents) | |
| time.sleep(rate_delay) | |
| text = (resp.text or "").strip() | |
| parsed = try_parse_json(text) | |
| return parsed if parsed is not None else {"text": text} | |
| # ===== ๊ธฐ๋ณธ ํ๋กฌํํธ(๊ฐ๊ฒฐ ๋ฒ์ ) ===== | |
| TEXT_PROMPT = ( | |
| "๋น์ ์ ๊ธฐ์ฌ ์ ๋ณด์ ๋ถ์ ์ ๋ฌธ๊ฐ์ ๋๋ค. ๋ค์ ๊ธฐ์ฌ์์ ์ ๋ณด์์ ์ถ์ถํ๊ณ , " | |
| "์ ๋ณด์๋ณ ๋ฌ์ฌ ํ๋ ์์ ๊ธ์ /์ค๋ฆฝ/๋ถ์ ์ผ๋ก ํ์ ํด JSON์ผ๋ก๋ง ์ถ๋ ฅํ์ธ์.\n" | |
| '์์: {"sources": ["์ ๋ณด์A","์ ๋ณด์B"], "frames": ["์ค๋ฆฝ","๋ถ์ "]}' | |
| ) | |
| IMAGE_PROMPT = ( | |
| "๋น์ ์ ๋ณด๋์ฌ์ง ๋ถ์ ์ ๋ฌธ๊ฐ์ ๋๋ค. ์ ๊ณต๋ ์ฌ์ง์์ Donald Trump ์กด์ฌ ์ฌ๋ถ๋ฅผ ํ๋จํ๊ณ " | |
| "๊ฐ์ (emotion: positive/negative/neutral)๊ณผ ์ญ๋์ฑ(dynamism: high/medium/low)์ ํ๊ฐํด " | |
| 'JSON์ผ๋ก๋ง ์ถ๋ ฅํ์ธ์. Trump๊ฐ ์์ผ๋ฉด {"trump_present": false}๋ง ๋ฐํํ์ธ์.' | |
| ) | |
| # ===== UI ===== | |
| st.title("๐ค ๋ฉํฐ๋ชจ๋ฌ LLM ๋ฐ๋ชจ (Gemma 3 27B)") | |
| tab_text, tab_img = st.tabs(["๐ ํ ์คํธ ๋ถ์", "๐ผ๏ธ ์ด๋ฏธ์ง ๋ถ์"]) | |
| with tab_text: | |
| st.subheader("๊ธฐ์ฌ ํ ์คํธ โ ์ ๋ณด์ & ํ๋ ์ ํ์ ") | |
| article = st.text_area( | |
| "๊ธฐ์ฌ ๋ณธ๋ฌธ", height=180, | |
| value="Donald Trump์ Nancy Pelosi๊ฐ ํ์์ฅ์์ ๊ฒฉ๋ ฌํ ๋ ผ์์ ๋ฒ์๋ค..." | |
| ) | |
| user_prompt = st.text_area("ํ๋กฌํํธ(์ต์ )", value=TEXT_PROMPT, height=120) | |
| if st.button("ํ ์คํธ ๋ถ์ ์คํ", type="primary", use_container_width=True): | |
| if not article.strip(): | |
| st.error("๊ธฐ์ฌ ๋ณธ๋ฌธ์ ์ ๋ ฅํด ์ฃผ์ธ์.") | |
| else: | |
| with st.spinner("๋ถ์ ์ค..."): | |
| try: | |
| result = infer_text(article, user_prompt or TEXT_PROMPT) | |
| st.success("์๋ฃ") | |
| st.json(result) | |
| st.download_button("๊ฒฐ๊ณผ JSON ๋ค์ด๋ก๋", data=json.dumps(result, ensure_ascii=False, indent=2), | |
| file_name="text_result.json", mime="application/json") | |
| except Exception as e: | |
| st.error(f"์ค๋ฅ: {e}") | |
| with tab_img: | |
| st.subheader("๋ณด๋์ฌ์ง โ ์ธ๋ฌผ ์กด์ฌยท๊ฐ์ ยท์ญ๋์ฑ ๋ถ์") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| img_url = st.text_input("์ด๋ฏธ์ง URL") | |
| with col2: | |
| file = st.file_uploader("์ด๋ฏธ์ง ์ ๋ก๋", type=["jpg","jpeg","png","webp","gif","bmp"]) | |
| img_prompt = st.text_area("ํ๋กฌํํธ(์ต์ )", value=IMAGE_PROMPT, height=120) | |
| if st.button("์ด๋ฏธ์ง ๋ถ์ ์คํ", type="primary", use_container_width=True): | |
| try: | |
| if file is not None: | |
| image = Image.open(io.BytesIO(file.read())).convert("RGB") | |
| elif img_url.strip(): | |
| image = load_image(img_url.strip()) | |
| else: | |
| st.error("์ด๋ฏธ์ง URL์ ์ ๋ ฅํ๊ฑฐ๋ ํ์ผ์ ์ ๋ก๋ํด ์ฃผ์ธ์.") | |
| st.stop() | |
| st.image(image, caption="์ ๋ ฅ ์ด๋ฏธ์ง", use_container_width=True) | |
| with st.spinner("๋ถ์ ์ค..."): | |
| result = infer_image(image, img_prompt or IMAGE_PROMPT) | |
| st.success("์๋ฃ") | |
| st.json(result) | |
| st.download_button("๊ฒฐ๊ณผ JSON ๋ค์ด๋ก๋", data=json.dumps(result, ensure_ascii=False, indent=2), | |
| file_name="image_result.json", mime="application/json") | |
| except Exception as e: | |
| st.error(f"์ค๋ฅ: {e}") | |