Gulzat commited on
Commit
1cdc82d
·
verified ·
1 Parent(s): a27e62c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -54
app.py CHANGED
@@ -1,25 +1,37 @@
1
- import datetime
2
- from typing import List, Dict, Tuple
 
 
3
 
4
- import pytz
5
- from langchain.tools import tool # or whatever decorator you use
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- # ────────────────────────────────────────────────────────────────────────────
8
- # 1️⃣ Keep your existing helper (unchanged)
9
- # ────────────────────────────────────────────────────────────────────────────
10
  @tool
11
  def get_current_time_in_timezone(timezone: str) -> str:
12
- """Return the current local time in a given timezone (YYYY-MM-DD HH:MM:SS)."""
13
  try:
14
  tz = pytz.timezone(timezone)
15
- local_time = datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
16
- return local_time
17
  except Exception as e:
18
  return f"Error: {e}"
19
 
20
- # ────────────────────────────────────────────────────────────────────────────
21
- # 2️⃣ New tool: find the first 30-minute slot that fits everyone’s workday
22
- # ────────────────────────────────────────────────────────────────────────────
23
  @tool
24
  def find_overlap_slot(
25
  timezones: List[str],
@@ -28,19 +40,14 @@ def find_overlap_slot(
28
  slot_minutes: int = 30
29
  ) -> str:
30
  """
31
- Find the next common slot for a stand-up.
32
-
33
  Args:
34
- timezones: List of IANA tz strings (e.g. ["Europe/Berlin", "Asia/Bishkek"])
35
- workday_start: Local workday start hour (24h clock, default 09)
36
- workday_end: Local workday end hour (default 18)
37
- slot_minutes: Length of slot to find (default 30)
38
- Returns:
39
- Human-readable description of the first viable slot, or error msg.
40
  """
41
- # 1. Build a list of "free intervals" for each participant
42
  now_utc = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
43
- candidates: List[Tuple[datetime.datetime, datetime.datetime]] = []
44
 
45
  for tz_name in timezones:
46
  try:
@@ -49,51 +56,73 @@ def find_overlap_slot(
49
  return f"Unknown timezone: {tz_name}"
50
 
51
  local_now = now_utc.astimezone(tz)
52
- # Next work-day window
53
  start_local = local_now.replace(hour=workday_start, minute=0, second=0, microsecond=0)
54
  end_local = local_now.replace(hour=workday_end, minute=0, second=0, microsecond=0)
55
- if local_now > end_local:
56
- # Move to tomorrow
 
57
  start_local += datetime.timedelta(days=1)
58
  end_local += datetime.timedelta(days=1)
59
  elif local_now > start_local:
60
- # Move start forward so we don't schedule in the past
61
- start_local = local_now
62
 
63
- # Convert to UTC for overlap maths
64
- candidates.append((
65
- start_local.astimezone(pytz.utc),
66
- end_local.astimezone(pytz.utc)
67
- ))
68
 
69
- # 2. Intersect all availability windows
70
- slot_start = max(interval[0] for interval in candidates)
71
- slot_end = min(interval[1] for interval in candidates)
72
 
73
  if slot_end - slot_start < datetime.timedelta(minutes=slot_minutes):
74
  return "No overlapping work-hour slot found in the next day."
75
 
76
- # 3. Return the first slot of the requested length
77
- chosen_start = slot_start
78
- chosen_end = slot_start + datetime.timedelta(minutes=slot_minutes)
79
 
80
- # 4. Build a friendly summary
81
- summary_lines = [
82
- f" Proposed stand-up slot ({slot_minutes} min)",
83
- f" • UTC: {chosen_start.strftime('%Y-%m-%d %H:%M')} – {chosen_end.strftime('%H:%M')}"
84
  ]
85
  for tz_name in timezones:
86
  tz = pytz.timezone(tz_name)
87
- loc_start = chosen_start.astimezone(tz).strftime('%Y-%m-%d %H:%M')
88
- loc_end = chosen_end.astimezone(tz).strftime('%H:%M')
89
- summary_lines.append(f" • {tz_name}: {loc_start} – {loc_end}")
90
 
91
- return "\n".join(summary_lines)
92
 
93
- # ────────────────────────────────────────────────────────────────────────────
94
- # 3️⃣ Example of an empty placeholder tool you can extend later
95
- # ────────────────────────────────────────────────────────────────────────────
96
- @tool
97
- def my_custom_tool(arg1: str, arg2: int) -> str:
98
- """A template tool you can repurpose for anything."""
99
- return f"Received arg1={arg1}, arg2={arg2}. What magic will you build?"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py ─────────────────────────────────────────────────────────────────────
2
+ # 1. Bootstrap: import-or-install helper
3
+ import importlib, subprocess, sys, datetime, os
4
+ from typing import List, Tuple
5
 
6
+ def ensure(pkg: str, version: str | None = None):
7
+ """Import a package, or pip-install it if missing, then import again."""
8
+ try:
9
+ return importlib.import_module(pkg)
10
+ except ModuleNotFoundError:
11
+ target = f"{pkg}=={version}" if version else pkg
12
+ print(f"[bootstrap] Installing {target} …", flush=True)
13
+ subprocess.check_call([sys.executable, "-m", "pip", "install", target])
14
+ return importlib.import_module(pkg)
15
+
16
+ # 2. Ensure dependencies
17
+ pytz = ensure("pytz")
18
+ langchain = ensure("langchain", "0.1.16") # pinned, stable API
19
+ langchain_openai = ensure("langchain-openai") # latest OK
20
+ from langchain.tools import tool
21
+ from langchain_openai import ChatOpenAI
22
+ from langchain.agents import Tool, AgentExecutor, create_openai_functions_agent
23
 
24
+ # 3. Tool: current time in timezone
 
 
25
  @tool
26
  def get_current_time_in_timezone(timezone: str) -> str:
27
+ """Return the current local time (YYYY-MM-DD HH:MM:SS) in a given timezone."""
28
  try:
29
  tz = pytz.timezone(timezone)
30
+ return datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
 
31
  except Exception as e:
32
  return f"Error: {e}"
33
 
34
+ # 4. Tool: find overlapping stand-up slot
 
 
35
  @tool
36
  def find_overlap_slot(
37
  timezones: List[str],
 
40
  slot_minutes: int = 30
41
  ) -> str:
42
  """
43
+ Find the next common slot across multiple time-zones.
 
44
  Args:
45
+ timezones: list of IANA tz strings
46
+ workday_start / workday_end: local work hours (inclusive start, exclusive end)
47
+ slot_minutes: length of slot
 
 
 
48
  """
 
49
  now_utc = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
50
+ intervals: List[Tuple[datetime.datetime, datetime.datetime]] = []
51
 
52
  for tz_name in timezones:
53
  try:
 
56
  return f"Unknown timezone: {tz_name}"
57
 
58
  local_now = now_utc.astimezone(tz)
 
59
  start_local = local_now.replace(hour=workday_start, minute=0, second=0, microsecond=0)
60
  end_local = local_now.replace(hour=workday_end, minute=0, second=0, microsecond=0)
61
+
62
+ # move window to tomorrow if current time past work hours
63
+ if local_now >= end_local:
64
  start_local += datetime.timedelta(days=1)
65
  end_local += datetime.timedelta(days=1)
66
  elif local_now > start_local:
67
+ start_local = local_now # cannot schedule in the past
 
68
 
69
+ intervals.append((start_local.astimezone(pytz.utc), end_local.astimezone(pytz.utc)))
 
 
 
 
70
 
71
+ slot_start = max(iv[0] for iv in intervals)
72
+ slot_end = min(iv[1] for iv in intervals)
 
73
 
74
  if slot_end - slot_start < datetime.timedelta(minutes=slot_minutes):
75
  return "No overlapping work-hour slot found in the next day."
76
 
77
+ chosen_end = slot_start + datetime.timedelta(minutes=slot_minutes)
 
 
78
 
79
+ lines = [
80
+ f"Proposed {slot_minutes}-minute stand-up:",
81
+ f" UTC: {slot_start.strftime('%Y-%m-%d %H:%M')} – {chosen_end.strftime('%H:%M')}"
 
82
  ]
83
  for tz_name in timezones:
84
  tz = pytz.timezone(tz_name)
85
+ local_start = slot_start.astimezone(tz).strftime('%Y-%m-%d %H:%M')
86
+ local_end = chosen_end.astimezone(tz).strftime('%H:%M')
87
+ lines.append(f"• {tz_name}: {local_start} – {local_end}")
88
 
89
+ return "\n".join(lines)
90
 
91
+ # 5. Build LangChain tools list
92
+ tools = [
93
+ Tool.from_function(
94
+ func=find_overlap_slot,
95
+ name="find_overlap_slot",
96
+ description=(
97
+ "Find a meeting slot. Args: timezones (List[str]), "
98
+ "workday_start, workday_end, slot_minutes."
99
+ ),
100
+ ),
101
+ Tool.from_function(
102
+ func=get_current_time_in_timezone,
103
+ name="get_current_time_in_timezone",
104
+ description="Return current local time for a timezone.",
105
+ ),
106
+ ]
107
+
108
+ # 6. Create the agent
109
+ openai_api_key = os.getenv("OPENAI_API_KEY") or "sk-..." # replace or set as HF secret
110
+ llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0, api_key=openai_api_key)
111
+ agent = create_openai_functions_agent(llm, tools)
112
+ agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=False)
113
+
114
+ # 7. Minimal Gradio interface
115
+ import gradio as gr
116
+
117
+ def chat_agent(user_input, history):
118
+ """Wrapper to make the agent compatible with Gradio ChatInterface."""
119
+ result = agent_executor.invoke({"input": user_input})
120
+ return result["output"]
121
+
122
+ with gr.Blocks() as demo:
123
+ gr.Markdown("# 🕒 Time-zone Helper Agent")
124
+ gr.ChatInterface(chat_agent)
125
+
126
+ # 8. Launch if running locally; HF Spaces ignores this in production
127
+ if __name__ == "__main__":
128
+ demo.launch(server_name="0.0.0.0", server_port=7860)