ginipick commited on
Commit
44bda5c
Β·
verified Β·
1 Parent(s): 435ea16

Update app-backup3.py

Browse files
Files changed (1) hide show
  1. app-backup3.py +62 -183
app-backup3.py CHANGED
@@ -1,4 +1,3 @@
1
- # ──────────────────────────────── Imports ────────────────────────────────
2
  import os, json, re, logging, requests, markdown, time, io
3
  from datetime import datetime
4
  import random
@@ -19,25 +18,11 @@ BRAVE_KEY = os.getenv("SERPHOUSE_API_KEY", "") # Keep this name
19
  BRAVE_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"
20
  BRAVE_IMAGE_ENDPOINT = "https://api.search.brave.com/res/v1/images/search"
21
  BRAVE_VIDEO_ENDPOINT = "https://api.search.brave.com/res/v1/videos/search"
22
- BRAVE_NEWS_ENDPOINT = "https://api.search.brave.com/res/v1/news/search"
23
  IMAGE_API_URL = "http://211.233.58.201:7896"
24
  MAX_TOKENS = 7999
25
 
26
- # μ•ˆμ •μ μΈ λŒ€μ²΄ 이미지 URL λͺ©λ‘
27
- FALLBACK_IMAGES = [
28
- "https://images.pexels.com/photos/2559941/pexels-photo-2559941.jpeg?auto=compress&cs=tinysrgb&w=600",
29
- "https://images.pexels.com/photos/417074/pexels-photo-417074.jpeg?auto=compress&cs=tinysrgb&w=600",
30
- "https://images.pexels.com/photos/312839/pexels-photo-312839.jpeg?auto=compress&cs=tinysrgb&w=600",
31
- "https://images.pexels.com/photos/3844788/pexels-photo-3844788.jpeg?auto=compress&cs=tinysrgb&w=600",
32
- "https://images.pexels.com/photos/33041/antelope-canyon-lower-canyon-arizona.jpg?auto=compress&cs=tinysrgb&w=600",
33
- "https://images.pexels.com/photos/572897/pexels-photo-572897.jpeg?auto=compress&cs=tinysrgb&w=600",
34
- "https://images.pexels.com/photos/773471/pexels-photo-773471.jpeg?auto=compress&cs=tinysrgb&w=600",
35
- "https://images.pexels.com/photos/1366630/pexels-photo-1366630.jpeg?auto=compress&cs=tinysrgb&w=600",
36
- "https://images.pexels.com/photos/1237119/pexels-photo-1237119.jpeg?auto=compress&cs=tinysrgb&w=600",
37
- "https://images.pexels.com/photos/1429567/pexels-photo-1429567.jpeg?auto=compress&cs=tinysrgb&w=600",
38
- ]
39
-
40
- # Search modes and style definitions (in English)
41
  SEARCH_MODES = {
42
  "comprehensive": "Comprehensive answer with multiple sources",
43
  "academic": "Academic and research-focused results",
@@ -156,8 +141,8 @@ Guidelines for Using Search Results:
156
  - Include source links directly in your response using markdown: [Source Name](URL)
157
  - For each major claim or piece of information, indicate its source
158
  - If sources conflict, explain the different perspectives and their reliability
159
- - Include 3-5 relevant images by writing: ![Image description](image_url)
160
- - Include 1-2 relevant video links when appropriate by writing: [Video: Title](video_url)
161
  - Format search information into a cohesive, well-structured response
162
  - Include a "References" section at the end listing all major sources with links
163
  """
@@ -397,11 +382,12 @@ def brave_news_search(query: str, count: int = 5):
397
  return []
398
 
399
  def mock_results(query: str) -> str:
400
- """Fallback search results if API fails"""
401
  ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
402
  return (f"# Fallback Search Content (Generated: {ts})\n\n"
403
- f"The search API request failed. Please generate a response based on any pre-existing knowledge about '{query}'.\n\n"
404
- f"You may consider the following points:\n\n"
 
405
  f"- Basic concepts and importance of {query}\n"
406
  f"- Commonly known related statistics or trends\n"
407
  f"- Typical expert opinions on this subject\n"
@@ -608,112 +594,8 @@ def process_uploaded_files(files):
608
  return result
609
 
610
  # ──────────────────────────────── Image & Utility ─────────────────────────
611
- def create_placeholder_image(text, width=600, height=400):
612
- """Create a placeholder image with text."""
613
- try:
614
- # 이미지 생성
615
- from PIL import Image, ImageDraw, ImageFont
616
- import numpy as np
617
-
618
- # 랜덀 컬러 생성
619
- r = random.randint(100, 240)
620
- g = random.randint(100, 240)
621
- b = random.randint(100, 240)
622
-
623
- # 이미지 생성 및 배경색 μ„€μ •
624
- img = Image.new('RGB', (width, height), color=(r, g, b))
625
- draw = ImageDraw.Draw(img)
626
-
627
- # ν…μŠ€νŠΈ μΆ”κ°€ (ν°νŠΈκ°€ μ—†μœΌλ©΄ κΈ°λ³Έ 폰트 μ‚¬μš©)
628
- try:
629
- font = ImageFont.truetype("arial.ttf", 20)
630
- except:
631
- font = ImageFont.load_default()
632
-
633
- # ν…μŠ€νŠΈκ°€ λ„ˆλ¬΄ κΈΈλ©΄ μ€„λ°”κΏˆ
634
- words = text.split()
635
- lines = []
636
- current_line = []
637
-
638
- for word in words:
639
- current_line.append(word)
640
- if len(' '.join(current_line)) > 30: # μ λ‹Ήν•œ κΈΈμ΄μ—μ„œ μ€„λ°”κΏˆ
641
- lines.append(' '.join(current_line[:-1]))
642
- current_line = [word]
643
-
644
- if current_line:
645
- lines.append(' '.join(current_line))
646
-
647
- text_to_draw = '\n'.join(lines)
648
-
649
- # ν…μŠ€νŠΈ μœ„μΉ˜ 계산 (쀑앙)
650
- textsize = draw.textsize(text_to_draw, font=font)
651
- text_x = (width - textsize[0]) / 2
652
- text_y = (height - textsize[1]) / 2
653
-
654
- # ν…μŠ€νŠΈ 그리기
655
- draw.text((text_x, text_y), text_to_draw, fill=(255, 255, 255), font=font)
656
-
657
- # 이미지λ₯Ό base64둜 인코딩
658
- buffered = BytesIO()
659
- img.save(buffered, format="JPEG")
660
- img_str = base64.b64encode(buffered.getvalue()).decode()
661
-
662
- return img_str
663
- except Exception as e:
664
- logging.error(f"Error creating placeholder image: {e}")
665
- return None
666
-
667
- def get_random_fallback_image():
668
- """Get a random fallback image from the list."""
669
- return random.choice(FALLBACK_IMAGES)
670
-
671
- def extract_image_urls_from_search(image_results, query):
672
- """Extract valid image URLs from Brave image search results, with fallbacks."""
673
- # μ•ˆμ •μ μΈ λŒ€μ²΄ μ΄λ―Έμ§€λ‘œ μ‹œμž‘ (μ΅œμ†Œ 3개 보μž₯)
674
- valid_urls = [
675
- {
676
- 'url': get_random_fallback_image(),
677
- 'title': f"Related to: {query} ({i+1})",
678
- 'source': "https://www.pexels.com/"
679
- } for i in range(3)
680
- ]
681
-
682
- # API κ²°κ³Όμ—μ„œ κ²€μ¦λœ 이미지 μΆ”κ°€
683
- if image_results:
684
- for img in image_results:
685
- url = img.get('image_url')
686
- if url and url.startswith('http'):
687
- # 이미 μΆ”κ°€λœ URL κ°œμˆ˜κ°€ 5개 미만인 κ²½μš°μ—λ§Œ μΆ”κ°€
688
- if len(valid_urls) < 5:
689
- valid_urls.append({
690
- 'url': url,
691
- 'title': img.get('title', f"Related to: {query}"),
692
- 'source': img.get('source_url', '')
693
- })
694
-
695
- return valid_urls
696
-
697
- def extract_video_data_from_search(video_results):
698
- """Extract valid video data from Brave video search results."""
699
- if not video_results:
700
- return []
701
-
702
- valid_videos = []
703
- for vid in video_results:
704
- url = vid.get('video_url')
705
- if url and url.startswith('http'):
706
- valid_videos.append({
707
- 'url': url,
708
- 'title': vid.get('title', 'Video'),
709
- 'thumbnail': vid.get('thumbnail_url', ''),
710
- 'source': vid.get('source', 'Video source')
711
- })
712
-
713
- return valid_videos
714
-
715
  def generate_image(prompt, w=768, h=768, g=3.5, steps=30, seed=3):
716
- """Image generation function."""
717
  if not prompt:
718
  return None, "Insufficient prompt"
719
  try:
@@ -734,7 +616,6 @@ def extract_image_prompt(response_text: str, topic: str):
734
  Generate a single-line English image prompt from the response content.
735
  """
736
  client = get_openai_client()
737
-
738
  try:
739
  response = client.chat.completions.create(
740
  model="gpt-4.1-mini",
@@ -757,7 +638,7 @@ def md_to_html(md: str, title="Perplexity-like Response"):
757
  return f"<!DOCTYPE html><html><head><title>{title}</title><meta charset='utf-8'></head><body>{markdown.markdown(md)}</body></html>"
758
 
759
  def keywords(text: str, top=5):
760
- """Simple keyword extraction."""
761
  cleaned = re.sub(r"[^κ°€-힣a-zA-Z0-9\s]", "", text)
762
  return " ".join(cleaned.split()[:top])
763
 
@@ -921,10 +802,9 @@ def perplexity_app():
921
  # Display existing messages
922
  for m in st.session_state.messages:
923
  with st.chat_message(m["role"]):
924
- # Process markdown to allow clickable links and properly rendered content
925
  st.markdown(m["content"], unsafe_allow_html=True)
926
 
927
- # Display images if present
928
  if "images" in m and m["images"]:
929
  st.subheader("Related Images")
930
  cols = st.columns(min(3, len(m["images"])))
@@ -949,7 +829,7 @@ def perplexity_app():
949
  video_url = video.get('url', '')
950
  thumbnail = video.get('thumbnail', '')
951
 
952
- # Display video information with thumbnail if available
953
  if thumbnail:
954
  col1, col2 = st.columns([1, 3])
955
  with col1:
@@ -994,7 +874,7 @@ def process_input(query: str, uploaded_files):
994
  has_uploaded_files = bool(uploaded_files) and len(uploaded_files) > 0
995
 
996
  try:
997
- # μƒνƒœ ν‘œμ‹œλ₯Ό μœ„ν•œ μƒνƒœ μ»΄ν¬λ„ŒνŠΈ
998
  status = st.status("Preparing to answer your query...")
999
  status.update(label="Initializing client...")
1000
 
@@ -1011,12 +891,12 @@ def process_input(query: str, uploaded_files):
1011
  with st.spinner("Searching the web..."):
1012
  search_content = do_web_search(keywords(query, top=5))
1013
 
1014
- # Perform specific searches for media
1015
  try:
1016
  status.update(label="Finding images and videos...")
1017
  image_results = brave_image_search(query, 5)
1018
  video_results = brave_video_search(query, 2)
1019
- news_results = brave_news_search(query, 3)
1020
  except Exception as search_err:
1021
  logging.error(f"Media search error: {search_err}")
1022
 
@@ -1027,9 +907,28 @@ def process_input(query: str, uploaded_files):
1027
  with st.spinner("Analyzing files..."):
1028
  file_content = process_uploaded_files(uploaded_files)
1029
 
1030
- # Extract usable image and video data with fallbacks
1031
- valid_images = extract_image_urls_from_search(image_results, query)
1032
- valid_videos = extract_video_data_from_search(video_results)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1033
 
1034
  # Build system prompt
1035
  status.update(label="Preparing comprehensive answer...")
@@ -1040,9 +939,6 @@ def process_input(query: str, uploaded_files):
1040
  include_uploaded_files=has_uploaded_files
1041
  )
1042
 
1043
- # OpenAI API 호좜 μ€€λΉ„
1044
- status.update(label="Generating response...")
1045
-
1046
  # λ©”μ‹œμ§€ ꡬ성
1047
  api_messages = [
1048
  {"role": "system", "content": sys_prompt}
@@ -1050,54 +946,52 @@ def process_input(query: str, uploaded_files):
1050
 
1051
  user_content = query
1052
 
1053
- # 검색 κ²°κ³Όκ°€ 있으면 μ‚¬μš©μž ν”„λ‘¬ν”„νŠΈμ— μΆ”κ°€
1054
  if search_content:
1055
  user_content += "\n\n" + search_content
1056
 
1057
- # 파일 λ‚΄μš©μ΄ 있으면 μ‚¬μš©μž ν”„λ‘¬ν”„νŠΈμ— μΆ”κ°€
1058
  if file_content:
1059
  user_content += "\n\n" + file_content
1060
 
1061
- # Include specific image information
1062
  if valid_images:
1063
  user_content += "\n\n# Available Images\n"
1064
- for i, img in enumerate(valid_images[:5]):
1065
  user_content += f"\n{i+1}. ![{img['title']}]({img['url']})\n"
1066
  if img['source']:
1067
  user_content += f" Source: {img['source']}\n"
1068
 
1069
- # Include specific video information
1070
  if valid_videos:
1071
  user_content += "\n\n# Available Videos\n"
1072
- for i, vid in enumerate(valid_videos[:2]):
1073
  user_content += f"\n{i+1}. **{vid['title']}** - [{vid['source']}]({vid['url']})\n"
1074
-
1075
- # μ‚¬μš©μž λ©”μ‹œμ§€ μΆ”κ°€
1076
  api_messages.append({"role": "user", "content": user_content})
1077
 
1078
- # OpenAI API 슀트리밍 호좜 - κ³ μ • λͺ¨λΈ "gpt-4.1-mini" μ‚¬μš©
1079
  try:
1080
- # 슀트리밍 λ°©μ‹μœΌλ‘œ API 호좜
1081
  stream = client.chat.completions.create(
1082
- model="gpt-4.1-mini", # κ³ μ • λͺ¨λΈ μ‚¬μš©
1083
  messages=api_messages,
1084
  temperature=1,
1085
  max_tokens=MAX_TOKENS,
1086
  top_p=1,
1087
- stream=True # 슀트리밍 ν™œμ„±ν™”
1088
  )
1089
 
1090
- # 슀트리밍 응닡 처리
1091
  for chunk in stream:
1092
  if chunk.choices and len(chunk.choices) > 0 and chunk.choices[0].delta.content is not None:
1093
  content_delta = chunk.choices[0].delta.content
1094
  full_response += content_delta
1095
  message_placeholder.markdown(full_response + "β–Œ", unsafe_allow_html=True)
1096
 
1097
- # μ΅œμ’… 응닡 ν‘œμ‹œ (μ»€μ„œ 제거)
1098
  message_placeholder.markdown(full_response, unsafe_allow_html=True)
1099
 
1100
- # Display related images if available
1101
  if valid_images:
1102
  st.subheader("Related Images")
1103
  image_cols = st.columns(min(3, len(valid_images)))
@@ -1106,27 +1000,15 @@ def process_input(query: str, uploaded_files):
1106
  col_idx = i % len(image_cols)
1107
  try:
1108
  with image_cols[col_idx]:
1109
- # 이미지 URL 체크
1110
  img_url = img_data['url']
1111
  caption = img_data['title']
1112
-
1113
- try:
1114
- # 이미지 ν‘œμ‹œ μ‹œλ„
1115
- st.image(img_url, caption=caption, use_column_width=True)
1116
- if img_data.get('source'):
1117
- st.markdown(f"[Source]({img_data['source']})")
1118
- except Exception as img_err:
1119
- # μ‹€νŒ¨ μ‹œ λŒ€μ²΄ 이미지 (Pexels μ•ˆμ •μ μΈ 이미지)
1120
- st.image(get_random_fallback_image(),
1121
- caption=f"{caption} (Fallback image)",
1122
- use_column_width=True)
1123
- st.markdown("[Source: Pexels](https://www.pexels.com/)")
1124
- logging.warning(f"Using fallback image: {img_err}")
1125
- except Exception as col_err:
1126
- logging.error(f"Error displaying image in column: {col_err}")
1127
- continue
1128
 
1129
- # Display related videos if available
1130
  if valid_videos:
1131
  st.subheader("Related Videos")
1132
  for video in valid_videos:
@@ -1134,7 +1016,6 @@ def process_input(query: str, uploaded_files):
1134
  video_url = video.get('url', '')
1135
  thumbnail = video.get('thumbnail', '')
1136
 
1137
- # Display video information with thumbnail if available
1138
  if thumbnail:
1139
  try:
1140
  col1, col2 = st.columns([1, 3])
@@ -1147,16 +1028,15 @@ def process_input(query: str, uploaded_files):
1147
  st.markdown(f"**[{video_title}]({video_url})**")
1148
  st.write(f"Source: {video.get('source', 'Unknown')}")
1149
  except Exception as vid_err:
1150
- # 였λ₯˜μ‹œ κΈ°λ³Έ ν˜•μ‹μœΌλ‘œ ν‘œμ‹œ
1151
  st.markdown(f"🎬 **[{video_title}]({video_url})**")
1152
  st.write(f"Source: {video.get('source', 'Unknown')}")
1153
  else:
1154
  st.markdown(f"🎬 **[{video_title}]({video_url})**")
1155
  st.write(f"Source: {video.get('source', 'Unknown')}")
1156
-
1157
  status.update(label="Response completed!", state="complete")
1158
 
1159
- # Save the response with images and videos in the session state
1160
  st.session_state.messages.append({
1161
  "role": "assistant",
1162
  "content": full_response,
@@ -1170,7 +1050,7 @@ def process_input(query: str, uploaded_files):
1170
  status.update(label=f"Error: {error_message}", state="error")
1171
  raise Exception(f"Response generation error: {error_message}")
1172
 
1173
- # Additional image generation if enabled
1174
  if st.session_state.generate_image and full_response:
1175
  with st.spinner("Generating custom image..."):
1176
  try:
@@ -1181,9 +1061,9 @@ def process_input(query: str, uploaded_files):
1181
  st.image(img, caption=cap)
1182
  except Exception as img_error:
1183
  logging.error(f"Image generation error: {str(img_error)}")
1184
- st.warning("Custom image generation failed. Using web images only.")
1185
 
1186
- # Download buttons
1187
  if full_response:
1188
  st.subheader("Download This Response")
1189
  c1, c2 = st.columns(2)
@@ -1216,10 +1096,9 @@ def process_input(query: str, uploaded_files):
1216
  ans = f"An error occurred while processing your request: {error_message}"
1217
  st.session_state.messages.append({"role": "assistant", "content": ans})
1218
 
1219
-
1220
  # ──────────────────────────────── main ────────────────────────────────────
1221
  def main():
1222
  perplexity_app()
1223
 
1224
  if __name__ == "__main__":
1225
- main()
 
 
1
  import os, json, re, logging, requests, markdown, time, io
2
  from datetime import datetime
3
  import random
 
18
  BRAVE_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"
19
  BRAVE_IMAGE_ENDPOINT = "https://api.search.brave.com/res/v1/images/search"
20
  BRAVE_VIDEO_ENDPOINT = "https://api.search.brave.com/res/v1/videos/search"
21
+ BRAVE_NEWS_ENDPOINT = "https://api.search.brave.com/res/v1/news/search"
22
  IMAGE_API_URL = "http://211.233.58.201:7896"
23
  MAX_TOKENS = 7999
24
 
25
+ # Brave Search modes and style definitions (in English)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  SEARCH_MODES = {
27
  "comprehensive": "Comprehensive answer with multiple sources",
28
  "academic": "Academic and research-focused results",
 
141
  - Include source links directly in your response using markdown: [Source Name](URL)
142
  - For each major claim or piece of information, indicate its source
143
  - If sources conflict, explain the different perspectives and their reliability
144
+ - Include relevant images by writing: ![Image description](image_url)
145
+ - Include relevant video links when appropriate by writing: [Video: Title](video_url)
146
  - Format search information into a cohesive, well-structured response
147
  - Include a "References" section at the end listing all major sources with links
148
  """
 
382
  return []
383
 
384
  def mock_results(query: str) -> str:
385
+ """Fallback search results if API fails or returns empty."""
386
  ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
387
  return (f"# Fallback Search Content (Generated: {ts})\n\n"
388
+ f"The search API request failed or returned no results for '{query}'. "
389
+ f"Please generate a response based on any pre-existing knowledge.\n\n"
390
+ f"Consider these points:\n\n"
391
  f"- Basic concepts and importance of {query}\n"
392
  f"- Commonly known related statistics or trends\n"
393
  f"- Typical expert opinions on this subject\n"
 
594
  return result
595
 
596
  # ──────────────────────────────── Image & Utility ─────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
  def generate_image(prompt, w=768, h=768, g=3.5, steps=30, seed=3):
598
+ """Image generation function (via Gradio endpoint)."""
599
  if not prompt:
600
  return None, "Insufficient prompt"
601
  try:
 
616
  Generate a single-line English image prompt from the response content.
617
  """
618
  client = get_openai_client()
 
619
  try:
620
  response = client.chat.completions.create(
621
  model="gpt-4.1-mini",
 
638
  return f"<!DOCTYPE html><html><head><title>{title}</title><meta charset='utf-8'></head><body>{markdown.markdown(md)}</body></html>"
639
 
640
  def keywords(text: str, top=5):
641
+ """Simple keyword extraction for query. Returns first N words (roughly)."""
642
  cleaned = re.sub(r"[^κ°€-힣a-zA-Z0-9\s]", "", text)
643
  return " ".join(cleaned.split()[:top])
644
 
 
802
  # Display existing messages
803
  for m in st.session_state.messages:
804
  with st.chat_message(m["role"]):
 
805
  st.markdown(m["content"], unsafe_allow_html=True)
806
 
807
+ # Display images if present in the message
808
  if "images" in m and m["images"]:
809
  st.subheader("Related Images")
810
  cols = st.columns(min(3, len(m["images"])))
 
829
  video_url = video.get('url', '')
830
  thumbnail = video.get('thumbnail', '')
831
 
832
+ # Display video with thumbnail if available
833
  if thumbnail:
834
  col1, col2 = st.columns([1, 3])
835
  with col1:
 
874
  has_uploaded_files = bool(uploaded_files) and len(uploaded_files) > 0
875
 
876
  try:
877
+ # μƒνƒœ ν‘œμ‹œ
878
  status = st.status("Preparing to answer your query...")
879
  status.update(label="Initializing client...")
880
 
 
891
  with st.spinner("Searching the web..."):
892
  search_content = do_web_search(keywords(query, top=5))
893
 
894
+ # Perform specific searches for images, videos, news
895
  try:
896
  status.update(label="Finding images and videos...")
897
  image_results = brave_image_search(query, 5)
898
  video_results = brave_video_search(query, 2)
899
+ news_results = brave_news_search(query, 3)
900
  except Exception as search_err:
901
  logging.error(f"Media search error: {search_err}")
902
 
 
907
  with st.spinner("Analyzing files..."):
908
  file_content = process_uploaded_files(uploaded_files)
909
 
910
+ # μ΅œμ’…μ μœΌλ‘œ μ‚¬μš©ν•  이미지/λΉ„λ””μ˜€ λͺ©λ‘ ꡬ성
911
+ # (μ΄λ²ˆμ—λŠ” "fallback" 없이 Brave 검색 결과만 μ‚¬μš©)
912
+ valid_images = []
913
+ for img in image_results:
914
+ url = img.get('image_url')
915
+ if url and url.startswith('http'):
916
+ valid_images.append({
917
+ 'url': url,
918
+ 'title': img.get('title', f"Related to: {query}"),
919
+ 'source': img.get('source_url', '')
920
+ })
921
+
922
+ valid_videos = []
923
+ for vid in video_results:
924
+ url = vid.get('video_url')
925
+ if url and url.startswith('http'):
926
+ valid_videos.append({
927
+ 'url': url,
928
+ 'title': vid.get('title', 'Video'),
929
+ 'thumbnail': vid.get('thumbnail_url', ''),
930
+ 'source': vid.get('source', 'Video source')
931
+ })
932
 
933
  # Build system prompt
934
  status.update(label="Preparing comprehensive answer...")
 
939
  include_uploaded_files=has_uploaded_files
940
  )
941
 
 
 
 
942
  # λ©”μ‹œμ§€ ꡬ성
943
  api_messages = [
944
  {"role": "system", "content": sys_prompt}
 
946
 
947
  user_content = query
948
 
949
+ # 검색 κ²°κ³Ό μΆ”κ°€
950
  if search_content:
951
  user_content += "\n\n" + search_content
952
 
953
+ # 파일 λ‚΄μš© μΆ”κ°€
954
  if file_content:
955
  user_content += "\n\n" + file_content
956
 
957
+ # 이미지/λΉ„λ””μ˜€ 메타정보λ₯Ό user_content에 μΆ”κ°€
958
  if valid_images:
959
  user_content += "\n\n# Available Images\n"
960
+ for i, img in enumerate(valid_images):
961
  user_content += f"\n{i+1}. ![{img['title']}]({img['url']})\n"
962
  if img['source']:
963
  user_content += f" Source: {img['source']}\n"
964
 
 
965
  if valid_videos:
966
  user_content += "\n\n# Available Videos\n"
967
+ for i, vid in enumerate(valid_videos):
968
  user_content += f"\n{i+1}. **{vid['title']}** - [{vid['source']}]({vid['url']})\n"
969
+
970
+ # OpenAI API에 전달할 μ΅œμ’… λ©”μ‹œμ§€
971
  api_messages.append({"role": "user", "content": user_content})
972
 
973
+ # OpenAI API 슀트리밍 호좜
974
  try:
 
975
  stream = client.chat.completions.create(
976
+ model="gpt-4.1-mini",
977
  messages=api_messages,
978
  temperature=1,
979
  max_tokens=MAX_TOKENS,
980
  top_p=1,
981
+ stream=True
982
  )
983
 
984
+ # 슀트리밍으둜 partial content μˆ˜μ‹ 
985
  for chunk in stream:
986
  if chunk.choices and len(chunk.choices) > 0 and chunk.choices[0].delta.content is not None:
987
  content_delta = chunk.choices[0].delta.content
988
  full_response += content_delta
989
  message_placeholder.markdown(full_response + "β–Œ", unsafe_allow_html=True)
990
 
991
+ # μ΅œμ’… 응닡 ν‘œμ‹œ
992
  message_placeholder.markdown(full_response, unsafe_allow_html=True)
993
 
994
+ # μ‹€μ œ κ²€μƒ‰λœ 이미지λ₯Ό UI에 ν‘œμ‹œ
995
  if valid_images:
996
  st.subheader("Related Images")
997
  image_cols = st.columns(min(3, len(valid_images)))
 
1000
  col_idx = i % len(image_cols)
1001
  try:
1002
  with image_cols[col_idx]:
 
1003
  img_url = img_data['url']
1004
  caption = img_data['title']
1005
+ st.image(img_url, caption=caption, use_column_width=True)
1006
+ if img_data.get('source'):
1007
+ st.markdown(f"[Source]({img_data['source']})")
1008
+ except Exception as img_err:
1009
+ logging.warning(f"Error displaying image: {img_err}")
 
 
 
 
 
 
 
 
 
 
 
1010
 
1011
+ # μ‹€μ œ κ²€μƒ‰λœ λΉ„λ””μ˜€λ₯Ό UI에 ν‘œμ‹œ
1012
  if valid_videos:
1013
  st.subheader("Related Videos")
1014
  for video in valid_videos:
 
1016
  video_url = video.get('url', '')
1017
  thumbnail = video.get('thumbnail', '')
1018
 
 
1019
  if thumbnail:
1020
  try:
1021
  col1, col2 = st.columns([1, 3])
 
1028
  st.markdown(f"**[{video_title}]({video_url})**")
1029
  st.write(f"Source: {video.get('source', 'Unknown')}")
1030
  except Exception as vid_err:
 
1031
  st.markdown(f"🎬 **[{video_title}]({video_url})**")
1032
  st.write(f"Source: {video.get('source', 'Unknown')}")
1033
  else:
1034
  st.markdown(f"🎬 **[{video_title}]({video_url})**")
1035
  st.write(f"Source: {video.get('source', 'Unknown')}")
1036
+
1037
  status.update(label="Response completed!", state="complete")
1038
 
1039
+ # μ„Έμ…˜ μ €μž₯
1040
  st.session_state.messages.append({
1041
  "role": "assistant",
1042
  "content": full_response,
 
1050
  status.update(label=f"Error: {error_message}", state="error")
1051
  raise Exception(f"Response generation error: {error_message}")
1052
 
1053
+ # μΆ”κ°€ 이미지 생성(μ˜΅μ…˜)
1054
  if st.session_state.generate_image and full_response:
1055
  with st.spinner("Generating custom image..."):
1056
  try:
 
1061
  st.image(img, caption=cap)
1062
  except Exception as img_error:
1063
  logging.error(f"Image generation error: {str(img_error)}")
1064
+ st.warning("Custom image generation failed.")
1065
 
1066
+ # λ‹€μš΄λ‘œλ“œ λ²„νŠΌ
1067
  if full_response:
1068
  st.subheader("Download This Response")
1069
  c1, c2 = st.columns(2)
 
1096
  ans = f"An error occurred while processing your request: {error_message}"
1097
  st.session_state.messages.append({"role": "assistant", "content": ans})
1098
 
 
1099
  # ──────────────────────────────── main ────────────────────────────────────
1100
  def main():
1101
  perplexity_app()
1102
 
1103
  if __name__ == "__main__":
1104
+ main()