Update app-backup3.py
Browse files- 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
|
23 |
IMAGE_API_URL = "http://211.233.58.201:7896"
|
24 |
MAX_TOKENS = 7999
|
25 |
|
26 |
-
#
|
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
|
160 |
-
- Include
|
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
|
404 |
-
f"
|
|
|
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
|
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
|
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
|
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 |
-
#
|
1031 |
-
|
1032 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
#
|
1062 |
if valid_images:
|
1063 |
user_content += "\n\n# Available Images\n"
|
1064 |
-
for i, img in enumerate(valid_images
|
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
|
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 μ€νΈλ¦¬λ° νΈμΆ
|
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 |
-
#
|
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 |
-
|
1114 |
-
|
1115 |
-
|
1116 |
-
|
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 |
-
#
|
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 |
-
#
|
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 |
-
#
|
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.
|
1185 |
|
1186 |
-
#
|
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: 
|
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()
|