gooookim commited on
Commit
6e055ee
Β·
verified Β·
1 Parent(s): 1eb2185

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -69
app.py CHANGED
@@ -4,6 +4,7 @@
4
  # HF Spaces Secrets μ„€μ •:
5
  # NAVER_CLIENT_ID = λ°œκΈ‰λ°›μ€ Client ID
6
  # NAVER_CLIENT_SECRET = λ°œκΈ‰λ°›μ€ Client Secret
 
7
 
8
  import os
9
  import html
@@ -23,9 +24,41 @@ def _get_env(name: str) -> str:
23
  return v
24
 
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  def _strip_tags(text: str) -> str:
27
- # 넀이버 λ‰΄μŠ€ API 결과의 title/description은 <b>...</b>κ°€ 포함될 수 μžˆμ–΄ 제거
28
- # (HTML νƒœκ·Έ 제거 + μ—”ν‹°ν‹° 처리)
29
  if not text:
30
  return ""
31
  text = re.sub(r"<[^>]+>", "", text)
@@ -33,13 +66,10 @@ def _strip_tags(text: str) -> str:
33
 
34
 
35
  def _format_pubdate(pub_date: str) -> str:
36
- # 예: "Mon, 19 Jan 2026 09:30:00 +0900"
37
- # νŒŒμ‹± μ‹€νŒ¨ μ‹œ 원문 λ°˜ν™˜
38
  if not pub_date:
39
  return ""
40
  try:
41
  dt = datetime.strptime(pub_date, "%a, %d %b %Y %H:%M:%S %z")
42
- # ν•œκ΅­ ν™˜κ²½μ—μ„œ 보기 쒋은 포맷
43
  return dt.strftime("%Y-%m-%d %H:%M:%S %z")
44
  except Exception:
45
  return pub_date
@@ -80,7 +110,6 @@ def naver_news_search(
80
  timeout=timeout,
81
  )
82
 
83
- # 넀이버 APIκ°€ μ—λŸ¬λ₯Ό λ°˜ν™˜ν•˜λŠ” 경우, body에 상세가 μžˆμ„ 수 μžˆμ–΄ κ·ΈλŒ€λ‘œ 보여주기 μœ„ν•΄ 처리
84
  if r.status_code != 200:
85
  try:
86
  detail = r.json()
@@ -110,7 +139,6 @@ def render_results(data: Dict[str, Any], max_items: int = 10) -> str:
110
 
111
  lines.append(f"{i}. **{title}**")
112
 
113
- # ν•˜μœ„ ν•­λͺ© 4μΉΈ λ“€μ—¬μ“°κΈ°
114
  if pub:
115
  lines.append(f" - λ°œν–‰: {pub}")
116
  if origin:
@@ -127,10 +155,6 @@ def render_results(data: Dict[str, Any], max_items: int = 10) -> str:
127
 
128
 
129
  def dedup_items(all_items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
130
- """
131
- κ²°κ³Ό 쀑볡 제거:
132
- - originallink μš°μ„ , μ—†μœΌλ©΄ link, μ—†μœΌλ©΄ title+pubDate ν•΄μ‹œ
133
- """
134
  seen = set()
135
  out = []
136
  for it in all_items:
@@ -154,10 +178,7 @@ def aggregate_search(
154
  display: int,
155
  sort: str,
156
  ) -> Tuple[List[str], List[Dict[str, Any]], int]:
157
- """
158
- μ‚¬μš©μž μž…λ ₯ λ¬Έμž₯을 κ·ΈλŒ€λ‘œ query둜 μ‚¬μš©ν•˜μ—¬ API 호좜
159
- λ°˜ν™˜: (μ‚¬μš©λœ 쿼리 λͺ©λ‘, μ΅œμ’… μ•„μ΄ν…œ λͺ©λ‘, total)
160
- """
161
  queries = [sentence]
162
 
163
  all_items: List[Dict[str, Any]] = []
@@ -173,12 +194,8 @@ def aggregate_search(
173
 
174
 
175
  def render_results_from_items(items: List[Dict[str, Any]]) -> str:
176
- """
177
- items 리슀트λ₯Ό 동일 μŠ€νƒ€μΌλ‘œ 좜λ ₯
178
- """
179
  lines: List[str] = []
180
- # lines.append(f"- μ΅œμ’… λ°˜ν™˜ 개수: {len(items)}건")
181
- lines.append("") # (μš”μ²­: μˆ˜ν‰μ„ μ€ 상단 문ꡬ λ‹€μŒ μ€„μ—λ§Œ 좜λ ₯)
182
 
183
  for i, it in enumerate(items, start=1):
184
  title = _strip_tags(it.get("title", ""))
@@ -218,17 +235,9 @@ def handle_search(
218
  try:
219
  _, items, total = aggregate_search(sentence=q, display=int(display), sort=sort)
220
 
221
- total_to_show = total if total > 0 else len(items)
222
-
223
  lines: List[str] = []
224
- # lines.append(
225
- # f"\"{q}\"에 λŒ€ν•œ 검색 κ²°κ³ΌλŠ” **{total_to_show:,}건** 이며, μ΅œμ’… 응닡 κ°œμˆ˜λŠ” **{len(items)}건** μž…λ‹ˆλ‹€."
226
- # )
227
- # lines.append("---")
228
- # lines.append("")
229
- # lines.append("---") # βœ… μˆ˜ν‰μ„ μ€ β€œ... μž…λ‹ˆλ‹€β€ λ‹€μŒ μ€„μ—λ§Œ
230
  lines.append("")
231
- lines.append(render_results_from_items(items)) # βœ… κ²°κ³ΌλŠ” 1번만 좜λ ₯
232
  lines.append("")
233
 
234
  assistant_text = "\n".join(lines).strip()
@@ -240,52 +249,64 @@ def handle_search(
240
  return chat_history, ""
241
 
242
 
243
- with gr.Blocks(title="Naver News Search (Chat UI)") as demo:
244
- with gr.Accordion("검색 옡μ…₯ - λ‰΄μŠ€ κ°œμˆ˜μ™€ μ •λ ¬ 방식을 μ„ νƒν•˜μ„Έμš”.", open=False):
245
- with gr.Row():
246
- display = gr.Slider(
247
- minimum=1, maximum=100, value=30, step=1, label="λ‰΄μŠ€ 개수"
248
- )
249
- sort = gr.Dropdown(
250
- choices=[("μ΅œμ‹ μˆœ", "date"), ("μ •ν™•λ„μˆœ(μ—°κ΄€λ„μˆœ)", "sim")],
251
- value="date",
252
- label="μ •λ ¬ 방식",
253
- )
254
 
255
- chatbot = gr.Chatbot(
256
- value=[],
257
- label="NewsChat_v0.1",
258
- type="messages",
259
- height=600,
260
  )
261
 
262
- with gr.Row():
263
- query_in = gr.Textbox(
264
- label="검색어λ₯Ό μž…λ ₯ν•˜μ„Έμš”.",
265
- placeholder="(예: ν–‰μ•ˆλΆ€μ˜ 인곡지λŠ₯ 사업)",
266
- scale=8,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  )
268
- with gr.Column(scale=2):
269
- search_btn = gr.Button("검색", variant="primary")
270
- clear_btn = gr.Button("λŒ€ν™” μ§€μš°κΈ°", variant="secondary")
271
-
272
- search_btn.click(
273
- fn=handle_search,
274
- inputs=[query_in, chatbot, display, sort],
275
- outputs=[chatbot, query_in],
276
- api_name="search",
277
- )
278
- query_in.submit(
279
- fn=handle_search,
280
- inputs=[query_in, chatbot, display, sort],
281
- outputs=[chatbot, query_in],
282
- api_name="search_submit",
283
- )
284
 
285
- def clear_chat():
286
- return []
287
 
288
- clear_btn.click(fn=clear_chat, inputs=[], outputs=[chatbot], api_name="clear")
289
 
290
 
291
  if __name__ == "__main__":
 
4
  # HF Spaces Secrets μ„€μ •:
5
  # NAVER_CLIENT_ID = λ°œκΈ‰λ°›μ€ Client ID
6
  # NAVER_CLIENT_SECRET = λ°œκΈ‰λ°›μ€ Client Secret
7
+ # ALLOWED_IPS = 123.123.123.123,2406:xxxx:xxxx::1234
8
 
9
  import os
10
  import html
 
24
  return v
25
 
26
 
27
+ # βœ… ν—ˆμš© IP λͺ©λ‘ λ‘œλ“œ
28
+ raw_ips = _get_env("ALLOWED_IPS")
29
+ ALLOWED_IPS = [ip.strip() for ip in raw_ips.split(",") if ip.strip()]
30
+
31
+
32
+ # βœ… ν”„λ‘μ‹œ ν™˜κ²½ κ³ λ €: μ‹€μ œ ν΄λΌμ΄μ–ΈνŠΈ IP μΆ”μΆœ
33
+ def _get_client_ip(request: gr.Request) -> str:
34
+ xff = request.headers.get("x-forwarded-for")
35
+ if xff:
36
+ return xff.split(",")[0].strip()
37
+
38
+ xri = request.headers.get("x-real-ip")
39
+ if xri:
40
+ return xri.strip()
41
+
42
+ return request.client.host
43
+
44
+
45
+ # βœ… IP μ ‘κ·Ό 차단
46
+ def gatekeeper(request: gr.Request):
47
+ if not ALLOWED_IPS:
48
+ return gr.update(visible=True), ""
49
+
50
+ client_ip = _get_client_ip(request)
51
+
52
+ if client_ip not in ALLOWED_IPS:
53
+ return (
54
+ gr.update(visible=False),
55
+ f"β›” 접근이 ν—ˆμš©λ˜μ§€ μ•Šμ€ IPμž…λ‹ˆλ‹€. (ν˜„μž¬ IP: {client_ip})",
56
+ )
57
+
58
+ return gr.update(visible=True), ""
59
+
60
+
61
  def _strip_tags(text: str) -> str:
 
 
62
  if not text:
63
  return ""
64
  text = re.sub(r"<[^>]+>", "", text)
 
66
 
67
 
68
  def _format_pubdate(pub_date: str) -> str:
 
 
69
  if not pub_date:
70
  return ""
71
  try:
72
  dt = datetime.strptime(pub_date, "%a, %d %b %Y %H:%M:%S %z")
 
73
  return dt.strftime("%Y-%m-%d %H:%M:%S %z")
74
  except Exception:
75
  return pub_date
 
110
  timeout=timeout,
111
  )
112
 
 
113
  if r.status_code != 200:
114
  try:
115
  detail = r.json()
 
139
 
140
  lines.append(f"{i}. **{title}**")
141
 
 
142
  if pub:
143
  lines.append(f" - λ°œν–‰: {pub}")
144
  if origin:
 
155
 
156
 
157
  def dedup_items(all_items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
 
 
 
 
158
  seen = set()
159
  out = []
160
  for it in all_items:
 
178
  display: int,
179
  sort: str,
180
  ) -> Tuple[List[str], List[Dict[str, Any]], int]:
181
+
 
 
 
182
  queries = [sentence]
183
 
184
  all_items: List[Dict[str, Any]] = []
 
194
 
195
 
196
  def render_results_from_items(items: List[Dict[str, Any]]) -> str:
 
 
 
197
  lines: List[str] = []
198
+ lines.append("")
 
199
 
200
  for i, it in enumerate(items, start=1):
201
  title = _strip_tags(it.get("title", ""))
 
235
  try:
236
  _, items, total = aggregate_search(sentence=q, display=int(display), sort=sort)
237
 
 
 
238
  lines: List[str] = []
 
 
 
 
 
 
239
  lines.append("")
240
+ lines.append(render_results_from_items(items))
241
  lines.append("")
242
 
243
  assistant_text = "\n".join(lines).strip()
 
249
  return chat_history, ""
250
 
251
 
252
+ with gr.Blocks(title="Naver News Search (Chat UI)") as demo:
253
+
254
+ warning = gr.Markdown("")
255
+ main_ui = gr.Column(visible=True)
 
 
 
 
 
 
 
256
 
257
+ # βœ… νŽ˜μ΄μ§€ λ‘œλ“œμ‹œ IP 검사
258
+ demo.load(
259
+ fn=gatekeeper,
260
+ inputs=None,
261
+ outputs=[main_ui, warning],
262
  )
263
 
264
+ with main_ui:
265
+ with gr.Accordion("검색 옡μ…₯ - λ‰΄μŠ€ κ°œμˆ˜μ™€ μ •λ ¬ 방식을 μ„ νƒν•˜μ„Έμš”.", open=False):
266
+ with gr.Row():
267
+ display = gr.Slider(
268
+ minimum=1, maximum=100, value=30, step=1, label="λ‰΄μŠ€ 개수"
269
+ )
270
+ sort = gr.Dropdown(
271
+ choices=[("μ΅œμ‹ μˆœ", "date"), ("μ •ν™•λ„μˆœ(μ—°κ΄€λ„μˆœ)", "sim")],
272
+ value="date",
273
+ label="μ •λ ¬ 방식",
274
+ )
275
+
276
+ chatbot = gr.Chatbot(
277
+ value=[],
278
+ label="NewsChat_v0.1",
279
+ type="messages",
280
+ height=600,
281
+ )
282
+
283
+ with gr.Row():
284
+ query_in = gr.Textbox(
285
+ label="검색어λ₯Ό μž…λ ₯ν•˜μ„Έμš”.",
286
+ placeholder="(예: ν–‰μ•ˆλΆ€μ˜ 인곡지λŠ₯ 사업)",
287
+ scale=8,
288
+ )
289
+ with gr.Column(scale=2):
290
+ search_btn = gr.Button("검색", variant="primary")
291
+ clear_btn = gr.Button("λŒ€ν™” μ§€μš°κΈ°", variant="secondary")
292
+
293
+ search_btn.click(
294
+ fn=handle_search,
295
+ inputs=[query_in, chatbot, display, sort],
296
+ outputs=[chatbot, query_in],
297
+ api_name="search",
298
+ )
299
+ query_in.submit(
300
+ fn=handle_search,
301
+ inputs=[query_in, chatbot, display, sort],
302
+ outputs=[chatbot, query_in],
303
+ api_name="search_submit",
304
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
+ def clear_chat():
307
+ return []
308
 
309
+ clear_btn.click(fn=clear_chat, inputs=[], outputs=[chatbot], api_name="clear")
310
 
311
 
312
  if __name__ == "__main__":