arad1367 commited on
Commit
a32e403
·
verified ·
1 Parent(s): 17e286e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -0
app.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import base64
4
+ import os
5
+
6
+ # --- UI Constants ---
7
+ TITLE = "🤖 Chatbot based on Responses API by Pejman Ebrahimi"
8
+ FOOTER = "@2025 Pejman Ebrahimi - All rights reserved."
9
+ LINKS = [
10
+ ("[![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/pejman-ebrahimi-4a60151a7/)", "https://www.linkedin.com/in/pejman-ebrahimi-4a60151a7/"),
11
+ ("[![HuggingFace](https://img.shields.io/badge/🤗_Hugging_Face-FFD21E?style=for-the-badge)](https://huggingface.co/arad1367)", "https://huggingface.co/arad1367"),
12
+ ("[![Website](https://img.shields.io/badge/Website-008080?style=for-the-badge&logo=About.me&logoColor=white)](https://arad1367.github.io/pejman-ebrahimi/)", "https://arad1367.github.io/pejman-ebrahimi/"),
13
+ ("[![University](https://img.shields.io/badge/University-00205B?style=for-the-badge&logo=academia&logoColor=white)](https://www.uni.li/pejman.ebrahimi?set_language=en)", "https://www.uni.li/pejman.ebrahimi?set_language=en"),
14
+ ]
15
+
16
+ # --- Helper Functions ---
17
+
18
+ def check_api_key(api_key):
19
+ """Check if the provided OpenAI API key is valid by making a minimal request."""
20
+ try:
21
+ url = "https://api.openai.com/v1/models"
22
+ headers = {"Authorization": f"Bearer {api_key}"}
23
+ r = requests.get(url, headers=headers, timeout=10)
24
+ return r.status_code == 200
25
+ except Exception:
26
+ return False
27
+
28
+ def openai_responses_api(api_key, messages, previous_response_id=None):
29
+ """Call OpenAI Responses API for chat or image analysis."""
30
+ url = "https://api.openai.com/v1/responses"
31
+ headers = {
32
+ "Authorization": f"Bearer {api_key}",
33
+ "Content-Type": "application/json"
34
+ }
35
+ payload = {
36
+ "model": "gpt-4o-mini",
37
+ "input": messages
38
+ }
39
+ if previous_response_id:
40
+ payload["previous_response_id"] = previous_response_id
41
+ r = requests.post(url, headers=headers, json=payload, timeout=60)
42
+ if r.status_code == 200:
43
+ return r.json()
44
+ else:
45
+ raise Exception(r.json().get("error", {}).get("message", "Unknown error"))
46
+
47
+ def encode_image_to_base64(image):
48
+ """Encode uploaded image to base64 string."""
49
+ if image is None:
50
+ return None
51
+ with open(image, "rb") as f:
52
+ return base64.b64encode(f.read()).decode("utf-8")
53
+
54
+ def get_iframe_code(space_url):
55
+ """Generate iframe code for embedding."""
56
+ return f'<iframe src="{space_url}" width="100%" height="600" frameborder="0"></iframe>'
57
+
58
+ # --- Gradio App Logic ---
59
+
60
+ def set_api_key(api_key, state):
61
+ """Set and validate API key in session state."""
62
+ if not api_key or not api_key.startswith("sk-"):
63
+ return gr.update(visible=True), "Please enter a valid OpenAI API key.", state
64
+ if check_api_key(api_key):
65
+ state["api_key"] = api_key
66
+ return gr.update(visible=False), "", state
67
+ else:
68
+ return gr.update(visible=True), "API key is invalid. Please try again.", state
69
+
70
+ def chat_fn(user_message, chat_history, image, state):
71
+ """Main chat function: handles text and optional image."""
72
+ api_key = state.get("api_key")
73
+ if not api_key:
74
+ return chat_history, state, gr.update(visible=True), "Please enter your OpenAI API key."
75
+ # Prepare messages for API
76
+ messages = []
77
+ for turn in chat_history:
78
+ messages.append({"role": "user", "content": turn[0]})
79
+ messages.append({"role": "assistant", "content": turn[1]})
80
+ # Add current user message
81
+ if image is not None:
82
+ # Image attached: send as multimodal input
83
+ base64_img = encode_image_to_base64(image)
84
+ content = [
85
+ {"type": "input_text", "text": user_message or "Please describe this image."},
86
+ {"type": "input_image", "image_url": f"data:image/jpeg;base64,{base64_img}"}
87
+ ]
88
+ messages.append({"role": "user", "content": content})
89
+ else:
90
+ messages.append({"role": "user", "content": user_message})
91
+ # Call OpenAI Responses API
92
+ try:
93
+ response = openai_responses_api(api_key, messages)
94
+ output_text = response.get("output_text", "")
95
+ chat_history.append((user_message, output_text))
96
+ state["previous_response_id"] = response.get("id")
97
+ return chat_history, state, gr.update(visible=False), ""
98
+ except Exception as e:
99
+ return chat_history, state, gr.update(visible=True), f"API error: {str(e)}"
100
+
101
+ def image_analysis_fn(image, state):
102
+ """Analyze uploaded image using OpenAI Responses API."""
103
+ api_key = state.get("api_key")
104
+ if not api_key:
105
+ return "", gr.update(visible=True), "Please enter your OpenAI API key."
106
+ if image is None:
107
+ return "Please upload an image.", gr.update(visible=False), ""
108
+ base64_img = encode_image_to_base64(image)
109
+ messages = [
110
+ {
111
+ "role": "user",
112
+ "content": [
113
+ {"type": "input_text", "text": "Please describe this image."},
114
+ {"type": "input_image", "image_url": f"data:image/jpeg;base64,{base64_img}"}
115
+ ]
116
+ }
117
+ ]
118
+ try:
119
+ response = openai_responses_api(api_key, messages)
120
+ return response.get("output_text", ""), gr.update(visible=False), ""
121
+ except Exception as e:
122
+ return f"API error: {str(e)}", gr.update(visible=True), f"API error: {str(e)}"
123
+
124
+ def export_chat(chat_history):
125
+ """Export chat history as JSON."""
126
+ import json
127
+ chat_json = []
128
+ for user, bot in chat_history:
129
+ chat_json.append({"user": user, "bot": bot})
130
+ return gr.File.update(value=("chat_history.json", json.dumps(chat_json, indent=2)), visible=True)
131
+
132
+ def show_iframe(space_url):
133
+ """Show iframe code for embedding."""
134
+ return get_iframe_code(space_url)
135
+
136
+ # --- Gradio UI ---
137
+
138
+ with gr.Blocks(theme=gr.themes.Soft(), css=".footer {text-align: center; font-size: 14px; color: gray;}") as demo:
139
+ # --- Header ---
140
+ gr.Markdown(f"<h1 style='text-align:center'>{TITLE}</h1>")
141
+ with gr.Row():
142
+ for md, url in LINKS:
143
+ gr.Markdown(f"<a href='{url}' target='_blank'>{md}</a>")
144
+ gr.Markdown("---")
145
+
146
+ # --- API Key Modal ---
147
+ state = gr.State({"api_key": None, "previous_response_id": None})
148
+ with gr.Row(visible=True) as api_key_row:
149
+ api_key_box = gr.Textbox(label="Enter your OpenAI API Key", type="password", placeholder="sk-...", show_label=True)
150
+ api_key_btn = gr.Button("Submit")
151
+ api_key_error = gr.Markdown("", visible=False)
152
+ def _hide_api_key_row():
153
+ return gr.update(visible=False)
154
+ api_key_box.submit(set_api_key, [api_key_box, state], [api_key_row, api_key_error, state])
155
+ api_key_btn.click(set_api_key, [api_key_box, state], [api_key_row, api_key_error, state])
156
+
157
+ # --- Tabs ---
158
+ with gr.Tabs():
159
+ # --- Chat Tab ---
160
+ with gr.Tab("💬 Chatbot"):
161
+ chatbot = gr.Chatbot(label="Chatbot", height=400)
162
+ with gr.Row():
163
+ user_input = gr.Textbox(label="Your message", placeholder="Type your message here...", scale=4)
164
+ image_input = gr.Image(type="filepath", label="Attach image (optional)", scale=1)
165
+ send_btn = gr.Button("Send", scale=1)
166
+ chat_error = gr.Markdown("", visible=False)
167
+ with gr.Row():
168
+ export_btn = gr.Button("Export chat as JSON")
169
+ export_file = gr.File(label="Download chat JSON", visible=False)
170
+ iframe_btn = gr.Button("Get iframe code")
171
+ iframe_code = gr.Textbox(label="Embed code", interactive=False, visible=False)
172
+ # Chat logic
173
+ send_btn.click(chat_fn, [user_input, chatbot, image_input, state], [chatbot, state, chat_error, api_key_error])
174
+ user_input.submit(chat_fn, [user_input, chatbot, image_input, state], [chatbot, state, chat_error, api_key_error])
175
+ export_btn.click(export_chat, chatbot, export_file)
176
+ iframe_btn.click(show_iframe, gr.Textbox(value="https://huggingface.co/spaces/your-space-name", visible=False), iframe_code)
177
+ # --- Image Analysis Tab ---
178
+ with gr.Tab("🖼️ Image Analysis"):
179
+ image_upload = gr.Image(type="filepath", label="Upload image")
180
+ analyze_btn = gr.Button("Analyze")
181
+ image_result = gr.Textbox(label="Analysis result", lines=4)
182
+ image_error = gr.Markdown("", visible=False)
183
+ analyze_btn.click(image_analysis_fn, [image_upload, state], [image_result, api_key_error, image_error])
184
+
185
+ # --- Theme Toggle ---
186
+ gr.ThemeSwitcher()
187
+
188
+ # --- Footer ---
189
+ gr.Markdown(f"<div class='footer'>{FOOTER}</div>")
190
+
191
+ # --- Launch ---
192
+ if __name__ == "__main__":
193
+ demo.launch()