Maximofn commited on
Commit
3023539
·
1 Parent(s): f8b0147

Actualiza `app.py` para integrar la API de Gemini a través de OpenAI. Se elimina la dependencia de `huggingface_hub` y se refactoriza la función `respond` para manejar mensajes multimodales. Se implementa la función `_extract_text_and_files` para extraer texto y archivos adjuntos de los mensajes. Además, se crea una interfaz de chat personalizada que guía a los usuarios en la creación de claves API de Gmail y Outlook.

Browse files
Files changed (1) hide show
  1. app.py +153 -188
app.py CHANGED
@@ -1,197 +1,162 @@
 
1
  import gradio as gr
2
- from huggingface_hub import InferenceClient
3
- import base64
4
- from PIL import Image
5
- import io
6
-
7
-
8
- def encode_image(pil_image):
9
- """Convert PIL image to base64 string"""
10
- buffered = io.BytesIO()
11
- pil_image.save(buffered, format="PNG")
12
- return base64.b64encode(buffered.getvalue()).decode("utf-8")
13
-
14
-
15
- def respond(
16
- message,
17
- history: list[dict[str, str]],
18
- images=None,
19
- ):
20
- """
21
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  """
23
- # Configuración por defecto
24
- system_message = "You are a friendly Chatbot."
25
- max_tokens = 512
26
- temperature = 0.7
27
- top_p = 0.95
28
-
29
- # Try to get the token from environment variable first, then from current request
30
- import os
31
- token = os.environ.get('HF_TOKEN') or os.environ.get('HUGGINGFACE_API_TOKEN')
32
-
33
- if not token:
34
- # Try to get from current Gradio context
35
- try:
36
- import gradio as gr
37
- token = gr.get_current_token()
38
- except:
39
- pass
40
-
41
- if not token:
42
- raise gr.Error("Please log in with HuggingFace to use this chatbot. Click the login button in the sidebar.")
43
-
44
- client = InferenceClient(token=token, model="openbmb/MiniCPM-V-4_5")
45
-
46
- messages = [{"role": "system", "content": system_message}]
47
-
48
- # Convert history to messages format
49
- for msg in history:
50
- if isinstance(msg, dict):
51
- messages.append(msg)
52
-
53
- # Prepare user content - can include both text and images
54
- user_content = []
55
-
56
- # Add images if provided
57
- if images:
58
- for img in images:
59
- if isinstance(img, dict) and 'path' in img:
60
- # Handle file uploads from gallery
61
- pil_img = Image.open(img['path']).convert("RGB")
62
- base64_image = encode_image(pil_img)
63
- user_content.append({"image": f"data:image/png;base64,{base64_image}"})
64
-
65
- # Add text message
66
- if message:
67
- user_content.append({"text": message})
68
-
69
- messages.append({"role": "user", "content": user_content})
70
-
71
- response = ""
72
-
73
- for message in client.chat_completion(
74
- messages,
75
- max_tokens=max_tokens,
76
- stream=True,
77
- temperature=temperature,
78
- top_p=top_p,
79
- ):
80
- choices = message.choices
81
- token = ""
82
- if len(choices) and choices[0].delta.content:
83
- token = choices[0].delta.content
84
-
85
- response += token
86
- yield response
87
-
88
-
89
- def create_chat_interface():
90
- """Create a custom chat interface with image upload capability"""
91
- with gr.Blocks() as demo:
92
- with gr.Sidebar():
93
- gr.LoginButton()
94
- # OAuth token will be passed automatically through the request context
95
-
96
- gr.Markdown("# 🤖 Chat with Images")
97
- gr.Markdown("Upload images and ask questions about them!")
98
-
99
- with gr.Row():
100
- with gr.Column(scale=2):
101
- # Image upload area
102
- uploaded_images = gr.Gallery(
103
- label="📸 Upload Images",
104
- show_label=True,
105
- columns=3,
106
- rows=2,
107
- height="300px",
108
- allow_preview=True,
109
- interactive=True
110
- )
111
-
112
- # Chat message input
113
- message_input = gr.Textbox(
114
- label="💬 Your Message",
115
- placeholder="Ask me anything about the images or just chat...",
116
- lines=3,
117
- show_label=True
118
- )
119
-
120
- with gr.Column(scale=3):
121
- # Chat display
122
- chatbot = gr.Chatbot(
123
- label="💬 Chat",
124
- height="500px",
125
- show_label=True
126
- )
127
-
128
- with gr.Row():
129
- submit_btn = gr.Button("🚀 Send", variant="primary")
130
- clear_btn = gr.Button("🗑️ Clear Chat")
131
-
132
- # Store hf_token as state
133
- hf_token_state = gr.State()
134
-
135
- def update_token(token):
136
- return token
137
-
138
- # Handle message submission
139
- def user_message(message, history, images):
140
- if not message and not images:
141
- return "", history, []
142
- return "", history + [{"role": "user", "content": message}], images
143
-
144
- def bot_response(message, history, images):
145
- if not message and not images:
146
- yield history, images
147
- return
148
-
149
- # Add user message to history
150
- new_history = history + [{"role": "user", "content": message}]
151
-
152
- # Get bot response
153
- bot_message = ""
154
- for partial_response in respond(message, history, images):
155
- bot_message = partial_response
156
- yield new_history + [{"role": "assistant", "content": bot_message}], images
157
-
158
- # Event handlers
159
- message_input.submit(
160
- user_message,
161
- [message_input, chatbot, uploaded_images],
162
- [message_input, chatbot, uploaded_images],
163
- queue=False
164
- ).then(
165
- bot_response,
166
- [message_input, chatbot, uploaded_images],
167
- [chatbot, uploaded_images],
168
- queue=True
169
- )
170
 
171
- submit_btn.click(
172
- user_message,
173
- [message_input, chatbot, uploaded_images],
174
- [message_input, chatbot, uploaded_images],
175
- queue=False
176
- ).then(
177
- bot_response,
178
- [message_input, chatbot, uploaded_images],
179
- [chatbot, uploaded_images],
180
- queue=True
181
  )
182
-
183
- clear_btn.click(
184
- lambda: ([], []),
185
- outputs=[chatbot, uploaded_images]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  )
187
 
188
- return demo
189
-
190
- # Create the interface
191
- demo = create_chat_interface()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
 
194
  if __name__ == "__main__":
195
- demo.launch(
196
- debug=True, # Habilita recarga automática y debugging
197
- )
 
1
+ import os
2
  import gradio as gr
3
+ from openai import OpenAI
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ # Configure Gemini via OpenAI-compatible endpoint
9
+ GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
10
+ GEMINI_MODEL = "gemini-2.5-flash"
11
+ _api_key = os.getenv("GEMINI_API_KEY")
12
+ _client = OpenAI(api_key=_api_key, base_url=GEMINI_BASE_URL) if _api_key else None
13
+
14
+
15
+ def _extract_text_and_files(message):
16
+ """Extract user text and attached files from a multimodal message value."""
17
+ if isinstance(message, str):
18
+ return message, []
19
+ # Common multimodal shapes: dict with keys, or list of parts
20
+ files = []
21
+ text_parts = []
22
+ try:
23
+ if isinstance(message, dict):
24
+ if "text" in message:
25
+ text_parts.append(message.get("text") or "")
26
+ if "files" in message and message["files"]:
27
+ files = message["files"] or []
28
+ elif isinstance(message, (list, tuple)):
29
+ for part in message:
30
+ if isinstance(part, str):
31
+ text_parts.append(part)
32
+ elif isinstance(part, dict):
33
+ # Heuristic: file-like dicts may have 'path' or 'name'
34
+ if any(k in part for k in ("path", "name", "mime_type")):
35
+ files.append(part)
36
+ elif "text" in part:
37
+ text_parts.append(part.get("text") or "")
38
+ except Exception:
39
+ pass
40
+ text_combined = " ".join([t for t in text_parts if t])
41
+ return text_combined, files
42
+
43
+
44
+ def respond(message, history: list[tuple[str, str]]):
45
+ """Stream assistant reply via Gemini using OpenAI-compatible API.
46
+
47
+ Yields partial text chunks so the UI shows a live stream.
48
  """
49
+ user_text, files = _extract_text_and_files(message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ if not _client:
52
+ yield (
53
+ "Gemini API key not configured. Set environment variable GEMINI_API_KEY "
54
+ "and restart the app."
 
 
 
 
 
 
55
  )
56
+ return
57
+
58
+ # Build OpenAI-style messages from history
59
+ messages = [
60
+ {
61
+ "role": "system",
62
+ "content": (
63
+ "You are a helpful assistant that guides users to create Gmail and Outlook API keys. "
64
+ "Answer in Spanish unless asked otherwise."
65
+ ),
66
+ }
67
+ ]
68
+ for user_turn, assistant_turn in history or []:
69
+ if user_turn:
70
+ messages.append({"role": "user", "content": user_turn})
71
+ if assistant_turn:
72
+ messages.append({"role": "assistant", "content": assistant_turn})
73
+
74
+ # Include a short mention about attached files (no uploading to remote in this demo)
75
+ if files:
76
+ filenames = []
77
+ for f in files:
78
+ if isinstance(f, dict):
79
+ name = f.get("name") or f.get("path") or "file"
80
+ filenames.append(str(name))
81
+ if filenames:
82
+ user_text = (user_text or "").strip()
83
+ user_text = f"{user_text}\n\n[Adjuntos: {', '.join(filenames)}]" if user_text else f"[Adjuntos: {', '.join(filenames)}]"
84
+
85
+ # If user provided no text, provide a nudge
86
+ final_user_text = user_text or "Quiero ayuda para crear una API Key."
87
+ messages.append({"role": "user", "content": final_user_text})
88
+
89
+ try:
90
+ stream = _client.chat.completions.create(
91
+ model=GEMINI_MODEL,
92
+ messages=messages,
93
+ stream=True,
94
  )
95
 
96
+ accumulated = ""
97
+ for chunk in stream:
98
+ try:
99
+ choice = chunk.choices[0]
100
+ delta_text = None
101
+ # OpenAI v1: delta.content
102
+ if getattr(choice, "delta", None) is not None:
103
+ delta_text = getattr(choice.delta, "content", None)
104
+ # Fallback: some providers emit message.content in chunks
105
+ if delta_text is None and getattr(choice, "message", None) is not None:
106
+ delta_text = choice.message.get("content") if isinstance(choice.message, dict) else None
107
+ if not delta_text:
108
+ continue
109
+ accumulated += delta_text
110
+ yield accumulated
111
+ except Exception:
112
+ continue
113
+
114
+ if not accumulated:
115
+ yield "(Sin contenido de respuesta)"
116
+ except Exception as e:
117
+ yield f"Ocurrió un error al llamar a Gemini: {e}"
118
+
119
+
120
+ chat = gr.ChatInterface(
121
+ fn=respond,
122
+ # default type keeps string message, keeps compatibility across versions
123
+ title="Gmail & Outlook API Helper",
124
+ description="Chat similar a ChatGPT para guiarte en la creación de API Keys.",
125
+ textbox=gr.MultimodalTextbox(file_types=[".pdf", ".txt"]),
126
+ multimodal=True,
127
+ fill_height=True,
128
+ examples=[
129
+ "¿Cómo creo una API Key de Gmail?",
130
+ "Guíame para obtener credenciales de Outlook",
131
+ "¿Qué permisos necesito para enviar correos?",
132
+ ],
133
+ theme=gr.themes.Monochrome(),
134
+ css="""
135
+ /* Force dark appearance similar to ChatGPT */
136
+ :root, .gradio-container { color-scheme: dark; }
137
+ body, .gradio-container { background: #0b0f16; }
138
+ .prose, .gr-text, .gr-form { color: #e5e7eb; }
139
+ /* Chat bubbles */
140
+ .message.user { background: #111827; border-radius: 10px; }
141
+ .message.assistant { background: #0f172a; border-radius: 10px; }
142
+ /* Input */
143
+ textarea, .gr-textbox textarea {
144
+ background: #0f172a !important;
145
+ color: #e5e7eb !important;
146
+ border-color: #1f2937 !important;
147
+ }
148
+ /* Buttons */
149
+ button {
150
+ background: #1f2937 !important;
151
+ color: #e5e7eb !important;
152
+ border: 1px solid #374151 !important;
153
+ }
154
+ button:hover { background: #374151 !important; }
155
+ """,
156
+ )
157
 
158
 
159
  if __name__ == "__main__":
160
+ chat.launch()
161
+
162
+