jonghhhh commited on
Commit
0cd8d7e
ยท
verified ยท
1 Parent(s): 0ce9638

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +148 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,150 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
 
4
  import streamlit as st
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
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}")