taha-the-data-scientist commited on
Commit
5a35763
Β·
verified Β·
1 Parent(s): d548b2c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +279 -63
app.py CHANGED
@@ -1,64 +1,280 @@
 
 
 
 
 
 
1
  import gradio as gr
2
- from huggingface_hub import InferenceClient
3
-
4
- """
5
- 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
6
- """
7
- client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
8
-
9
-
10
- def respond(
11
- message,
12
- history: list[tuple[str, str]],
13
- system_message,
14
- max_tokens,
15
- temperature,
16
- top_p,
17
- ):
18
- messages = [{"role": "system", "content": system_message}]
19
-
20
- for val in history:
21
- if val[0]:
22
- messages.append({"role": "user", "content": val[0]})
23
- if val[1]:
24
- messages.append({"role": "assistant", "content": val[1]})
25
-
26
- messages.append({"role": "user", "content": message})
27
-
28
- response = ""
29
-
30
- for message in client.chat_completion(
31
- messages,
32
- max_tokens=max_tokens,
33
- stream=True,
34
- temperature=temperature,
35
- top_p=top_p,
36
- ):
37
- token = message.choices[0].delta.content
38
-
39
- response += token
40
- yield response
41
-
42
-
43
- """
44
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
45
- """
46
- demo = gr.ChatInterface(
47
- respond,
48
- additional_inputs=[
49
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
50
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
51
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
52
- gr.Slider(
53
- minimum=0.1,
54
- maximum=1.0,
55
- value=0.95,
56
- step=0.05,
57
- label="Top-p (nucleus sampling)",
58
- ),
59
- ],
60
- )
61
-
62
-
63
- if __name__ == "__main__":
64
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ import os
4
+ import re
5
+ import json
6
+ import requests
7
  import gradio as gr
8
+ from google.oauth2.credentials import Credentials
9
+ from googleapiclient.discovery import build
10
+ from agentpro import create_model, ReactAgent
11
+ from agentpro.tools import AresInternetTool
12
+
13
+ # ──────────────────────────────────────────────────────────────────────────────
14
+ # 1) READ ENVIRONMENT VARIABLES (set in HF Space Secrets)
15
+ # ──────────────────────────────────────────────────────────────────────────────
16
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
17
+ GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
18
+ GOOGLE_CLIENT_SECRET= os.getenv("GOOGLE_CLIENT_SECRET")
19
+ ARES_API_KEY = os.getenv("ARES_API_KEY") # if you are using AresInternetTool
20
+
21
+ # Your HF Space URL (must match what you registered as redirect URI in Google Cloud Console)
22
+ HF_SPACE_URL = "https://huggingface.co/spaces/case-llm-traversaal/calendar-chatbot"
23
+
24
+ # ──────────────────────────────────────────────────────────────────────────────
25
+ # 2) GLOBAL IN-MEMORY STORAGE FOR TOKENS (basic demo)
26
+ # ──────────────────────────────────────────────────────────────────────────────
27
+ # In a production Β­grade multiuser app, key by user ID or IP. Here, we keep one β€œdefault” slot.
28
+ user_tokens = {
29
+ "default": None # Will hold a dict { "access_token": ..., "refresh_token": ..., "expiry": ... }
30
+ }
31
+
32
+ # ──────────────────────────────────────────────────────────────────────────────
33
+ # 3) HELPERS: Build Google Auth URL / Extract & Exchange Code / Build Service
34
+ # ──────────────────────────────────────────────────────────────────────────────
35
+
36
+ def build_auth_url():
37
+ """
38
+ Construct the Google OAuth2 authorization URL. When the user clicks 'Connect',
39
+ we open this in a new tab. Google will redirect to HF_SPACE_URL?code=XXXX.
40
+ """
41
+ base = "https://accounts.google.com/o/oauth2/v2/auth"
42
+ scope = "https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events"
43
+ params = {
44
+ "client_id": GOOGLE_CLIENT_ID,
45
+ "redirect_uri": HF_SPACE_URL,
46
+ "response_type": "code",
47
+ "scope": scope,
48
+ "access_type": "offline", # get a refresh token
49
+ "prompt": "consent" # always ask consent to receive a refresh token
50
+ }
51
+ # Build query string manually
52
+ q = "&".join([f"{k}={requests.utils.quote(v)}" for k, v in params.items()])
53
+ return f"{base}?{q}"
54
+
55
+ def extract_auth_code(full_redirect_url: str) -> str:
56
+ """
57
+ The user pastes the entire redirect URL (which contains '?code=XYZ&scope=...' etc.).
58
+ We pull out the 'XYZ' (authorization code) to exchange for tokens.
59
+ """
60
+ match = re.search(r"[?&]code=([^&]+)", full_redirect_url)
61
+ if match:
62
+ return match.group(1)
63
+ return None
64
+
65
+ def exchange_code_for_tokens(auth_code: str):
66
+ """
67
+ Given the single‐use auth_code from Google, exchange it at Google's token endpoint
68
+ for access_token + refresh_token + expiry. Then store it in user_tokens["default"].
69
+ """
70
+ token_url = "https://oauth2.googleapis.com/token"
71
+ data = {
72
+ "code": auth_code,
73
+ "client_id": GOOGLE_CLIENT_ID,
74
+ "client_secret": GOOGLE_CLIENT_SECRET,
75
+ "redirect_uri": HF_SPACE_URL,
76
+ "grant_type": "authorization_code",
77
+ }
78
+ resp = requests.post(token_url, data=data)
79
+ if resp.status_code != 200:
80
+ return None, resp.text
81
+
82
+ token_data = resp.json()
83
+ # Example token_data: { "access_token": "...", "expires_in": 3599, "refresh_token": "...", "scope": "...", "token_type": "Bearer" }
84
+ user_tokens["default"] = {
85
+ "access_token": token_data.get("access_token"),
86
+ "refresh_token": token_data.get("refresh_token"),
87
+ "token_uri": token_url,
88
+ "client_id": GOOGLE_CLIENT_ID,
89
+ "client_secret": GOOGLE_CLIENT_SECRET,
90
+ "scopes": [
91
+ "https://www.googleapis.com/auth/calendar.readonly",
92
+ "https://www.googleapis.com/auth/calendar.events"
93
+ ]
94
+ # Note: You could also store expiry/time; google.oauth2.credentials will handle refreshing automatically.
95
+ }
96
+ return token_data, None
97
+
98
+ def get_calendar_service():
99
+ """
100
+ Build a googleapiclient β€˜service’ object using the stored tokens.
101
+ If tokens exist, we can construct google.oauth2.credentials.Credentials,
102
+ which will auto-refresh if expired.
103
+ """
104
+ token_info = user_tokens.get("default")
105
+ if not token_info:
106
+ return None # Not authenticated yet
107
+
108
+ creds = Credentials(
109
+ token=token_info["access_token"],
110
+ refresh_token=token_info["refresh_token"],
111
+ token_uri=token_info["token_uri"],
112
+ client_id=token_info["client_id"],
113
+ client_secret=token_info["client_secret"],
114
+ scopes=token_info["scopes"],
115
+ )
116
+ try:
117
+ service = build("calendar", "v3", credentials=creds)
118
+ except Exception as e:
119
+ return None
120
+ return service
121
+
122
+ # ──────────────────────────────────────────────────────────────────────────────
123
+ # 4) IMPORT YOUR EXISTING TOOLS
124
+ # (Assumes you placed them in a subfolder named β€˜tools/’)
125
+ # ──────────────────────────────────────────────────────────────────────────────
126
+
127
+ from tools.current_datetime_tool import CurrentDateTimeTool
128
+ from tools.daily_event_summary_tool import DailyEventSummaryTool
129
+ from tools.weekly_event_summary_tool import WeeklyEventSummaryTool
130
+ from tools.modify_event_tool import ModifyEventTool
131
+
132
+ # ──────────────────────────────────────────────────────────────────────────────
133
+ # 5) FUNCTION TO BUILD A FRESH AGENT FOR EACH USER
134
+ # (Because the β€˜service’ object is user-specific once authenticated)
135
+ # ──────────────────────────────────────────────────────────────────────────────
136
+
137
+ def build_agent_for_user():
138
+ """
139
+ 1. Build the OpenAI model via AgentPro.
140
+ 2. Build a user-specific Google Calendar β€˜service’ via stored tokens.
141
+ 3. Instantiate each tool with that service (where required).
142
+ 4. Return a ReactAgent ready to run queries.
143
+ """
144
+ # 5.1) Create the LLM model
145
+ model = create_model(
146
+ provider = "openai",
147
+ model_name = "gpt-3.5-turbo",
148
+ api_key = OPENAI_API_KEY,
149
+ temperature = 0.3
150
+ )
151
+
152
+ # 5.2) Get the user’s Google Calendar service
153
+ service = get_calendar_service()
154
+ if service is None:
155
+ return None # Not authenticated yet
156
+
157
+ # 5.3) Instantiate each tool, passing β€˜service’ where needed
158
+ daily_planner_tool = DailyEventSummaryTool(service=service)
159
+ weekly_planner_tool = WeeklyEventSummaryTool(service=service)
160
+ modify_event_tool = ModifyEventTool(service=service)
161
+ current_dt_tool = CurrentDateTimeTool()
162
+ ares_tool = AresInternetTool(ARES_API_KEY)
163
+
164
+ tools_list = [
165
+ daily_planner_tool,
166
+ weekly_planner_tool,
167
+ current_dt_tool,
168
+ modify_event_tool,
169
+ ares_tool,
170
+ ]
171
+
172
+ # 5.4) Create and return the ReactAgent
173
+ agent = ReactAgent(model=model, tools=tools_list)
174
+ return agent
175
+
176
+ # ──────────────────────────────────────────────────────────────────────────────
177
+ # 6) GRADIO CALLBACKS FOR AUTHENTICATION & EXCHANGE
178
+ # ──────────────────────────────────────────────────────────────────────────────
179
+
180
+ def open_google_oauth():
181
+ """
182
+ Called when user clicks β€œConnect Google Calendar.” Returns a message telling
183
+ them to copy the URL that opens.
184
+ """
185
+ url = build_auth_url()
186
+ # Attempt to open in a new tab. (Browsers may block this; user can copy manually.)
187
+ try:
188
+ import webbrowser
189
+ webbrowser.open_new_tab(url)
190
+ except:
191
+ pass
192
+ return (
193
+ "β˜… A new tab (or popup) should have opened for you to log in.\n\n"
194
+ "If it did not, click this link manually:\n\n"
195
+ f"{url}\n\n"
196
+ "After granting access, you’ll end up on an error page. Copy the \
197
+ entire URL from your browser’s address bar (it contains β€œ?code=…”) \
198
+ and paste it below."
199
+ )
200
+
201
+ def handle_auth_code(full_redirect_url: str):
202
+ """
203
+ Called when user pastes the entire redirect URL back into our textbox.
204
+ We parse out β€˜code=…’, exchange it, and report success or failure.
205
+ """
206
+ code = extract_auth_code(full_redirect_url)
207
+ if code is None:
208
+ return "❌ Could not find β€˜code’ in the URL you pasted. Please paste the exact URL you were redirected to."
209
+
210
+ token_data, error = exchange_code_for_tokens(code)
211
+ if error:
212
+ return f"❌ Token exchange failed: {error}"
213
+ return "βœ… Successfully connected your Google Calendar! You can now ask about your events."
214
+
215
+ # ──────────────────────────────────────────────────────────────────────────────
216
+ # 7) GRADIO CHAT FUNCTION (uses user-specific agent)
217
+ # ──────────────────────────────────────────────────────────────────────────────
218
+
219
+ def chat_with_agent(user_input):
220
+ """
221
+ This is called whenever the user sends a chat query.
222
+ If not authenticated, prompt them to connect first. Otherwise,
223
+ build a fresh agent for this user and get a response.
224
+ """
225
+ # 7.1) Do we have a Calendar β€˜service’ for this user yet?
226
+ service = get_calendar_service()
227
+ if service is None:
228
+ return "❌ Please connect your Google Calendar first (use the button above)."
229
+
230
+ # 7.2) Build a new Agent with the user’s Calendar service
231
+ agent = build_agent_for_user()
232
+ if agent is None:
233
+ return "❌ Something went wrong building the agent. Ensure you completed Calendar authentication."
234
+
235
+ # 7.3) Run the agent on the user’s input
236
+ try:
237
+ response = agent.run(user_input)
238
+ return response.final_answer
239
+ except Exception as e:
240
+ return "❌ Exception in agent.run():\n" + repr(e)
241
+
242
+ # ──────────────────────────────────────────────────────────────────────────────
243
+ # 8) ASSEMBLE GRADIO INTERFACE
244
+ # ──────────────────────────────────────────────────────────────────────────────
245
+
246
+ with gr.Blocks() as demo:
247
+ gr.Markdown("## πŸ—“οΈ Calendar Chatbot (Gradio + HF Spaces)")
248
+
249
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
250
+ # A) CONNECT GOOGLE CALENDAR SECTION
251
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
252
+ with gr.Row():
253
+ connect_btn = gr.Button("πŸ”— Connect Google Calendar")
254
+ auth_message = gr.Textbox(label="Auth Instructions / Status", interactive=False)
255
+
256
+ connect_btn.click(fn=open_google_oauth, outputs=auth_message)
257
+
258
+ auth_code_input = gr.Textbox(
259
+ lines=1,
260
+ placeholder="Paste full Google redirect URL here (contains β€˜code=…’)",
261
+ label="Step 2: Paste full redirect URL"
262
+ )
263
+ auth_status = gr.Textbox(label="Authentication Result", interactive=False)
264
+ auth_code_input.submit(fn=handle_auth_code, inputs=auth_code_input, outputs=auth_status)
265
+
266
+ gr.Markdown("---")
267
+
268
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
269
+ # B) CHAT SECTION
270
+ # β€”β€”β€”β€”β€”β€”β€”οΏ½οΏ½β€”β€”β€”β€”β€”β€”β€”β€”
271
+ chat_input = gr.Textbox(lines=2, placeholder="E.g., What’s on my calendar today?")
272
+ chat_output = gr.Textbox(label="Response", interactive=False)
273
+
274
+ chat_input.submit(fn=chat_with_agent, inputs=chat_input, outputs=chat_output)
275
+
276
+ # You can also add a β€œSend” button if you like:
277
+ # send_btn = gr.Button("βœ‰οΈ Send")
278
+ # send_btn.click(fn=chat_with_agent, inputs=chat_input, outputs=chat_output)
279
+
280
+ demo.launch()