arad1367 commited on
Commit
a61d9a2
Β·
verified Β·
1 Parent(s): f441ba9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +242 -196
app.py CHANGED
@@ -1,213 +1,259 @@
1
  import gradio as gr
2
- import requests
3
- import base64
4
- import json
5
  import os
 
 
 
 
 
6
 
7
- # --- UI Constants ---
8
- TITLE = "πŸ€– Chatbot based on Responses API by Pejman Ebrahimi"
9
- FOOTER = "@2025 Pejman Ebrahimi - All rights reserved."
10
- LINKS = [
11
- ("[![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/"),
12
- ("[![HuggingFace](https://img.shields.io/badge/πŸ€—_Hugging_Face-FFD21E?style=for-the-badge)](https://huggingface.co/arad1367)", "https://huggingface.co/arad1367"),
13
- ("[![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/"),
14
- ("[![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"),
15
- ]
16
-
17
- # --- Helper Functions ---
18
 
19
- def check_api_key(api_key):
20
- """Check if the provided OpenAI API key is valid by making a minimal request."""
21
  try:
22
- url = "https://api.openai.com/v1/models"
23
- headers = {"Authorization": f"Bearer {api_key}"}
24
- r = requests.get(url, headers=headers, timeout=10)
25
- return r.status_code == 200
26
- except Exception:
27
- return False
 
 
 
28
 
29
- def openai_responses_api(api_key, messages, previous_response_id=None):
30
- """Call OpenAI Responses API for chat or image analysis."""
31
- url = "https://api.openai.com/v1/responses"
32
- headers = {
33
- "Authorization": f"Bearer {api_key}",
34
- "Content-Type": "application/json"
35
- }
36
- payload = {
37
- "model": "gpt-4o-mini",
38
- "input": messages
39
- }
40
- if previous_response_id:
41
- payload["previous_response_id"] = previous_response_id
42
- r = requests.post(url, headers=headers, json=payload, timeout=60)
43
- if r.status_code == 200:
44
- return r.json()
45
  else:
46
- try:
47
- return {"error": r.json().get("error", {}).get("message", "Unknown error")}
48
- except Exception:
49
- return {"error": "Unknown error"}
50
-
51
- def encode_image_to_base64(image):
52
- """Encode uploaded image to base64 string."""
53
- if image is None:
54
- return None
55
- with open(image, "rb") as f:
56
- return base64.b64encode(f.read()).decode("utf-8")
57
 
58
- def get_iframe_code(space_url):
59
- """Generate iframe code for embedding."""
60
- return f'<iframe src="{space_url}" width="100%" height="600" frameborder="0"></iframe>'
61
-
62
- # --- Gradio App Logic ---
63
-
64
- def set_api_key(api_key, state):
65
- """Set and validate API key in session state."""
66
- if not api_key or not api_key.startswith("sk-"):
67
- return gr.update(visible=True), "Please enter a valid OpenAI API key.", state
68
- if check_api_key(api_key):
69
- state["api_key"] = api_key
70
- return gr.update(visible=False), "", state
71
- else:
72
- return gr.update(visible=True), "API key is invalid. Please try again.", state
73
-
74
- def chat_fn(user_message, chat_history, image, state):
75
- """Main chat function: handles text and optional image."""
76
- api_key = state.get("api_key")
77
- if not api_key:
78
- return chat_history, state, gr.update(visible=True), "Please enter your OpenAI API key."
79
- if not user_message and image is None:
80
- return chat_history, state, gr.update(visible=False), ""
81
- # Prepare messages for API
82
- messages = chat_history.copy()
83
- # Add current user message
84
- if image is not None:
85
- base64_img = encode_image_to_base64(image)
86
- content = [
87
- {"type": "input_text", "text": user_message or "Please describe this image."},
88
- {"type": "input_image", "image_url": f"data:image/jpeg;base64,{base64_img}"}
89
- ]
90
- messages.append({"role": "user", "content": content})
91
- display_user = (user_message or "") + "\n[Image attached]"
92
- else:
93
- messages.append({"role": "user", "content": user_message})
94
- display_user = user_message
95
- # Call OpenAI Responses API
96
- response = openai_responses_api(api_key, messages, state.get("previous_response_id"))
97
- if "error" in response:
98
- return chat_history, state, gr.update(visible=True), f"API error: {response['error']}"
99
- output_text = response.get("output_text", "")
100
- state["previous_response_id"] = response.get("id")
101
- # Update chat history for display
102
- chat_history = chat_history + [
103
- {"role": "user", "content": display_user},
104
- {"role": "assistant", "content": output_text}
105
- ]
106
- return chat_history, state, gr.update(visible=False), ""
107
 
108
- def image_analysis_fn(image, state):
109
- """Analyze uploaded image using OpenAI Responses API."""
110
- api_key = state.get("api_key")
111
- if not api_key:
112
- return "", gr.update(visible=True), "Please enter your OpenAI API key."
113
- if image is None:
114
- return "Please upload an image.", gr.update(visible=False), ""
115
- base64_img = encode_image_to_base64(image)
116
- messages = [
117
- {
118
- "role": "user",
119
- "content": [
120
- {"type": "input_text", "text": "Please describe this image."},
121
- {"type": "input_image", "image_url": f"data:image/jpeg;base64,{base64_img}"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  ]
123
- }
124
- ]
125
- response = openai_responses_api(api_key, messages)
126
- if "error" in response:
127
- return f"API error: {response['error']}", gr.update(visible=True), f"API error: {response['error']}"
128
- return response.get("output_text", ""), gr.update(visible=False), ""
129
-
130
- def export_chat(chat_history):
131
- """Export chat history as JSON."""
132
- chat_json = chat_history
133
- with open("chat_history.json", "w", encoding="utf-8") as f:
134
- json.dump(chat_json, f, indent=2, ensure_ascii=False)
135
- return "chat_history.json"
136
-
137
- def show_iframe(space_url):
138
- """Show iframe code for embedding."""
139
- return get_iframe_code(space_url)
140
-
141
- # --- Gradio UI ---
 
 
 
 
 
 
 
142
 
143
- with gr.Blocks(theme=gr.themes.Soft(), css=".footer {text-align: center; font-size: 14px; color: gray;}") as demo:
144
- # --- Header ---
145
- gr.Markdown(f"<h1 style='text-align:center'>{TITLE}</h1>")
146
- with gr.Row():
147
- for md, url in LINKS:
148
- gr.Markdown(f"<a href='{url}' target='_blank'>{md}</a>")
149
- gr.Markdown("---")
 
 
 
 
 
 
 
150
 
151
- # --- API Key Modal ---
152
- state = gr.State({"api_key": None, "previous_response_id": None})
153
- with gr.Row(visible=True) as api_key_row:
154
- api_key_box = gr.Textbox(label="Enter your OpenAI API Key", type="password", placeholder="sk-...", show_label=True)
155
- api_key_btn = gr.Button("Submit")
156
- api_key_error = gr.Markdown("", visible=False)
157
- api_key_box.submit(set_api_key, [api_key_box, state], [api_key_row, api_key_error, state])
158
- api_key_btn.click(set_api_key, [api_key_box, state], [api_key_row, api_key_error, state])
159
 
160
- # --- Tabs ---
161
- with gr.Tabs():
162
- # --- Chat Tab ---
163
- with gr.Tab("πŸ’¬ Chatbot"):
164
- chatbot = gr.Chatbot(label="Chatbot", height=400, type="messages")
165
- with gr.Row():
166
- user_input = gr.Textbox(label="Your message", placeholder="Type your message here...", scale=4)
167
- image_input = gr.Image(type="filepath", label="Attach image (optional)", scale=1)
168
- send_btn = gr.Button("Send", scale=1)
169
- chat_error = gr.Markdown("", visible=False)
170
- with gr.Row():
171
- export_btn = gr.Button("Export chat as JSON")
172
- export_file = gr.File(label="Download chat JSON", visible=False)
173
- iframe_btn = gr.Button("Get iframe code")
174
- iframe_code = gr.Textbox(label="Embed code", interactive=False, visible=False)
175
- # Chat logic
176
- send_btn.click(
177
- chat_fn,
178
- [user_input, chatbot, image_input, state],
179
- [chatbot, state, chat_error, api_key_error]
180
- )
181
- user_input.submit(
182
- chat_fn,
183
- [user_input, chatbot, image_input, state],
184
- [chatbot, state, chat_error, api_key_error]
185
- )
186
- export_btn.click(
187
- export_chat,
188
- chatbot,
189
- export_file
190
- )
191
- iframe_btn.click(
192
- show_iframe,
193
- gr.Textbox(value="https://huggingface.co/spaces/your-space-name", visible=False),
194
- iframe_code
195
- )
196
- # --- Image Analysis Tab ---
197
- with gr.Tab("πŸ–ΌοΈ Image Analysis"):
198
- image_upload = gr.Image(type="filepath", label="Upload image")
199
- analyze_btn = gr.Button("Analyze")
200
- image_result = gr.Textbox(label="Analysis result", lines=4)
201
- image_error = gr.Markdown("", visible=False)
202
- analyze_btn.click(
203
- image_analysis_fn,
204
- [image_upload, state],
205
- [image_result, api_key_error, image_error]
206
- )
207
 
208
- # --- Footer ---
209
- gr.Markdown(f"<div class='footer'>{FOOTER}</div>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
- # --- Launch ---
212
  if __name__ == "__main__":
 
213
  demo.launch()
 
1
  import gradio as gr
 
 
 
2
  import os
3
+ import json
4
+ import base64
5
+ from datetime import datetime
6
+ from openai import OpenAI
7
+ from tempfile import NamedTemporaryFile
8
 
9
+ # Global variables
10
+ api_key = None
11
+ client = None
12
+ chat_history = []
13
+ current_response_id = None
 
 
 
 
 
 
14
 
15
+ def validate_api_key(key):
16
+ """Validate OpenAI API key by making a simple request"""
17
  try:
18
+ temp_client = OpenAI(api_key=key)
19
+ # Make a minimal request to check if the key works
20
+ response = temp_client.responses.create(
21
+ model="gpt-4o-mini",
22
+ input="Hello"
23
+ )
24
+ return True, "API key is valid!"
25
+ except Exception as e:
26
+ return False, f"Invalid API key: {str(e)}"
27
 
28
+ def set_api_key(key):
29
+ """Set the OpenAI API key and initialize client"""
30
+ global api_key, client
31
+ is_valid, message = validate_api_key(key)
32
+ if is_valid:
33
+ api_key = key
34
+ client = OpenAI(api_key=api_key)
35
+ return gr.update(visible=False), gr.update(visible=True), message
 
 
 
 
 
 
 
 
36
  else:
37
+ return gr.update(visible=True), gr.update(visible=False), message
 
 
 
 
 
 
 
 
 
 
38
 
39
+ def encode_image(image_path):
40
+ """Encode image to base64"""
41
+ with open(image_path, 'rb') as image_file:
42
+ return base64.b64encode(image_file.read()).decode("utf-8")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ def chat(message, history, image=None):
45
+ """Process chat messages with optional image"""
46
+ global client, current_response_id, chat_history
47
+
48
+ if not client:
49
+ return "Please set your OpenAI API key first."
50
+
51
+ try:
52
+ if image is not None:
53
+ # Save the uploaded image to a temporary file
54
+ temp_file = NamedTemporaryFile(delete=False, suffix=".png")
55
+ image_path = temp_file.name
56
+ image.save(image_path)
57
+
58
+ # Encode the image
59
+ base64_image = encode_image(image_path)
60
+
61
+ # Create input with text and image
62
+ input_messages = [
63
+ {
64
+ "role": "user",
65
+ "content": [
66
+ {
67
+ "type": "input_text",
68
+ "text": message
69
+ },
70
+ {
71
+ "type": "input_image",
72
+ "image_url": f"data:image/png;base64,{base64_image}"
73
+ }
74
+ ]
75
+ }
76
  ]
77
+
78
+ response = client.responses.create(
79
+ model="gpt-4o-mini",
80
+ input=input_messages,
81
+ previous_response_id=current_response_id
82
+ )
83
+
84
+ # Clean up the temporary file
85
+ os.unlink(image_path)
86
+ else:
87
+ # Text-only message
88
+ response = client.responses.create(
89
+ model="gpt-4o-mini",
90
+ input=message,
91
+ previous_response_id=current_response_id
92
+ )
93
+
94
+ current_response_id = response.id
95
+
96
+ # Update chat history for export
97
+ chat_history.append({"role": "user", "content": message})
98
+ chat_history.append({"role": "assistant", "content": response.output_text})
99
+
100
+ return response.output_text
101
+ except Exception as e:
102
+ return f"Error: {str(e)}"
103
 
104
+ def export_chat():
105
+ """Export chat history to JSON file"""
106
+ global chat_history
107
+
108
+ if not chat_history:
109
+ return "No chat history to export."
110
+
111
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
112
+ filename = f"chat_export_{timestamp}.json"
113
+
114
+ with open(filename, "w") as f:
115
+ json.dump(chat_history, f, indent=2)
116
+
117
+ return f"Chat exported to {filename}"
118
 
119
+ def clear_chat():
120
+ """Clear chat history and reset response ID"""
121
+ global chat_history, current_response_id
122
+ chat_history = []
123
+ current_response_id = None
124
+ return None
 
 
125
 
126
+ def generate_iframe_code(request: gr.Request):
127
+ """Generate iframe code for embedding the app"""
128
+ app_url = request.headers.get('host', 'localhost')
129
+ if not app_url.startswith('http'):
130
+ app_url = f"https://{app_url}"
131
+
132
+ iframe_code = f"""<iframe
133
+ src="{app_url}"
134
+ width="100%"
135
+ height="500px"
136
+ style="border: 1px solid #ddd; border-radius: 8px;"
137
+ allow="microphone"
138
+ ></iframe>"""
139
+
140
+ return iframe_code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
+ def create_ui():
143
+ """Create the Gradio UI"""
144
+ with gr.Blocks(theme=gr.themes.Soft(), css="""
145
+ .container { max-width: 900px; margin: auto; }
146
+ .title { text-align: center; margin-bottom: 10px; }
147
+ .subtitle { text-align: center; margin-bottom: 20px; color: #666; }
148
+ .social-links { display: flex; justify-content: center; gap: 10px; margin-bottom: 20px; }
149
+ .footer { text-align: center; margin-top: 20px; padding: 10px; border-top: 1px solid #eee; }
150
+ .tab-content { padding: 20px; }
151
+ """) as demo:
152
+ with gr.Column(elem_classes="container"):
153
+ gr.Markdown("# Responses API Chatbot", elem_classes="title")
154
+ gr.Markdown("### Created by Pejman Ebrahimi", elem_classes="subtitle")
155
+
156
+ with gr.Row(elem_classes="social-links"):
157
+ gr.HTML("""
158
+ <a href="https://www.linkedin.com/in/pejman-ebrahimi-4a60151a7/" target="_blank">
159
+ <img src="https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white" alt="LinkedIn">
160
+ </a>
161
+ <a href="https://huggingface.co/arad1367" target="_blank">
162
+ <img src="https://img.shields.io/badge/πŸ€—_Hugging_Face-FFD21E?style=for-the-badge" alt="HuggingFace">
163
+ </a>
164
+ <a href="https://arad1367.github.io/pejman-ebrahimi/" target="_blank">
165
+ <img src="https://img.shields.io/badge/Website-008080?style=for-the-badge&logo=About.me&logoColor=white" alt="Website">
166
+ </a>
167
+ <a href="https://www.uni.li/pejman.ebrahimi?set_language=en" target="_blank">
168
+ <img src="https://img.shields.io/badge/University-00205B?style=for-the-badge&logo=academia&logoColor=white" alt="University">
169
+ </a>
170
+ """)
171
+
172
+ # API Key Input Section
173
+ with gr.Group(visible=True) as api_key_group:
174
+ with gr.Row():
175
+ api_key_input = gr.Textbox(
176
+ label="Enter your OpenAI API Key",
177
+ placeholder="sk-...",
178
+ type="password",
179
+ scale=4
180
+ )
181
+ api_key_button = gr.Button("Set API Key", scale=1)
182
+ api_key_message = gr.Markdown("")
183
+
184
+ # Main Chat Interface (hidden until API key is set)
185
+ with gr.Group(visible=False) as chat_interface:
186
+ with gr.Tabs() as tabs:
187
+ with gr.TabItem("Chat", elem_classes="tab-content"):
188
+ chatbot = gr.Chatbot(height=400)
189
+ with gr.Row():
190
+ with gr.Column(scale=4):
191
+ msg = gr.Textbox(
192
+ placeholder="Type your message here...",
193
+ show_label=False,
194
+ container=False
195
+ )
196
+ with gr.Column(scale=1):
197
+ with gr.Row():
198
+ image_btn = gr.UploadButton("πŸ“Ž", file_types=["image"])
199
+
200
+ with gr.Row():
201
+ clear_btn = gr.Button("Clear Chat")
202
+ export_btn = gr.Button("Export Chat")
203
+ theme_btn = gr.Button("Toggle Theme")
204
+
205
+ export_message = gr.Markdown("")
206
+
207
+ with gr.TabItem("Embed", elem_classes="tab-content"):
208
+ gr.Markdown("### Embed this chatbot on your website")
209
+ iframe_code = gr.Code(language="html")
210
+ generate_iframe_btn = gr.Button("Generate Embed Code")
211
+
212
+ gr.Markdown("Β© 2025 Pejman Ebrahimi - All Rights Reserved", elem_classes="footer")
213
+
214
+ # Set up event handlers
215
+ api_key_button.click(
216
+ set_api_key,
217
+ inputs=[api_key_input],
218
+ outputs=[api_key_group, chat_interface, api_key_message]
219
+ )
220
+
221
+ msg.submit(
222
+ chat,
223
+ inputs=[msg, chatbot, None],
224
+ outputs=[chatbot]
225
+ )
226
+
227
+ image_btn.upload(
228
+ lambda img: gr.update(value=f"[Uploaded image] "),
229
+ inputs=[image_btn],
230
+ outputs=[msg]
231
+ )
232
+
233
+ clear_btn.click(
234
+ clear_chat,
235
+ outputs=[chatbot]
236
+ )
237
+
238
+ export_btn.click(
239
+ export_chat,
240
+ outputs=[export_message]
241
+ )
242
+
243
+ theme_btn.click(
244
+ lambda: gr.Theme.toggle_theme(),
245
+ None,
246
+ None
247
+ )
248
+
249
+ generate_iframe_btn.click(
250
+ generate_iframe_code,
251
+ None,
252
+ iframe_code
253
+ )
254
+
255
+ return demo
256
 
 
257
  if __name__ == "__main__":
258
+ demo = create_ui()
259
  demo.launch()