yamilsteven commited on
Commit
e30aaa7
·
1 Parent(s): befb7f7

[UI COMPONENTS] :: Player card, Game card, and Team card added

Browse files
api/event_handlers/gradio_handler.py CHANGED
@@ -5,12 +5,61 @@ from langchain_core.outputs.llm_result import LLMResult
5
  from typing import List
6
  from langchain_core.messages import BaseMessage
7
 
 
 
8
  image_base = """
9
- <img
10
- src="https://huggingface.co/spaces/ryanbalch/IFX-huge-league/resolve/main/assets/profiles/players_pics/{filename}"
11
- style="max-width: 100%; max-height: 100%; object-fit: contain; display: block; margin: 0 auto;"
12
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  """
 
14
  team_image_map = {
15
  'everglade-fc': 'Everglade_FC',
16
  'fraser-valley-united': 'Fraser_Valley_United',
@@ -68,12 +117,129 @@ class GradioEventHandler(AsyncCallbackHandler):
68
  print(f"\n{Fore.CYAN}[TOOL END] {output}{Style.RESET_ALL}")
69
  for doc in output:
70
  if doc.metadata.get("show_profile_card"):
71
- img = image_base.format(filename=self.get_image_filename(doc))
72
- print(f"\n{Fore.YELLOW}[TOOL END] {img}{Style.RESET_ALL}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  self.ots_box(img)
74
- break
75
- # else:
76
- # self.info_box(doc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  async def on_tool_start(self, input: any, *args, **kwargs):
79
  self.info_box(input.get("name", "[TOOL START]"))
@@ -95,3 +261,12 @@ class GradioEventHandler(AsyncCallbackHandler):
95
  @staticmethod
96
  def get_image_filename(doc):
97
  return f'{team_image_map.get(doc.metadata.get("team"))}_{doc.metadata.get("number")}.png'
 
 
 
 
 
 
 
 
 
 
5
  from typing import List
6
  from langchain_core.messages import BaseMessage
7
 
8
+ TEAM_LOGO_BASE_URL = "https://huggingface.co/spaces/yamilsteven/ifx-assets/resolve/main/assets/team_logos/"
9
+
10
  image_base = """
11
+ <div style="background-color: #2C2C2C; border-radius: 10px; padding: 25px; display: flex; align-items: center; width: 450px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);">
12
+ <div style="margin-right: 25px;">
13
+ <img src="https://huggingface.co/spaces/ryanbalch/IFX-huge-league/resolve/main/assets/profiles/players_pics/{filename}" alt="{filename} Pic" style="width: 100px; height: 100px; border-radius: 50%; object-fit: cover; border: 3px solid #444;">
14
+ </div>
15
+ <div style="color: #E0E0E0;">
16
+ <h2 style="margin-top: 0; margin-bottom: 8px; font-size: 22px; color: #FFFFFF;">{player_name} - #{player_number} (DL)</h2>
17
+ <p style="margin: 6px 0; font-size: 15px; color: #B0B0B0;">Ht: {height} | Wt: {weight} lbs</p>
18
+ <p style="margin: 6px 0; font-size: 15px; color: #B0B0B0;">Team: {team_name}</p>
19
+ <p style="margin: 6px 0; font-size: 15px; color: #B0B0B0;">Experience: 6 Years</p>
20
+ <a href="https://www.instagram.com/{instagram_url}" style="background-color: #C00000; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block; margin-top: 15px; font-size: 14px; text-align: center;">Instagram Profile</a>
21
+ </div>
22
+ </div>
23
+ """
24
+
25
+ game_card_html = """
26
+ <div style="text-align: center; padding: 20px; margin: 10px 0; border-radius: 8px; background-color: #3A3B3C; color: #FFFFFF; font-family: Arial, sans-serif; max-width: 500px; margin-left: auto; margin-right: auto;">
27
+ <h3 style="margin-top: 0; margin-bottom: 10px; font-size: 20px; color: #E0E0E0;">{game_title}</h3>
28
+ <div style="display: flex; justify-content: space-around; align-items: center; margin-bottom: 15px;">
29
+ <div style="text-align: center; width: 40%;">
30
+ <img src="{team1_logo_url}"
31
+ alt="{team_home} Logo"
32
+ style="max-width: 70px; max-height: 70px; margin-bottom: 5px; vertical-align: middle; display: block; margin-left: auto; margin-right: auto;"
33
+ onerror="this.onerror=null; this.src='{default_logo_url}';">
34
+ <p style="font-size: 16px; font-weight: bold; margin: 5px 0;">{team_home}</p>
35
+ <p style="font-size: 24px; font-weight: bold; margin: 0;">{team1_score}</p>
36
+ </div>
37
+ <p style="font-size: 20px; margin: 0 10px;">vs</p>
38
+ <div style="text-align: center; width: 40%;">
39
+ <img src="{team2_logo_url}"
40
+ alt="{team_away} Logo"
41
+ style="max-width: 70px; max-height: 70px; margin-bottom: 5px; vertical-align: middle; display: block; margin-left: auto; margin-right: auto;"
42
+ onerror="this.onerror=null; this.src='{default_logo_url}';">
43
+ <p style="font-size: 16px; font-weight: bold; margin: 5px 0;">{team_away}</p>
44
+ <p style="font-size: 24px; font-weight: bold; margin: 0;">{team2_score}</p>
45
+ </div>
46
+ </div>
47
+ <div style="margin-top: 20px; border-top: 1px solid #555; padding-top: 15px;">
48
+ <h4 style="margin-top: 0; margin-bottom: 10px; font-size: 16px; color: #D0D0D0; text-align: left;">Goal Highlights:</h4>
49
+ {highlights}
50
+ </div>
51
+ </div>
52
+ """
53
+
54
+ team_info_card_html = """
55
+ <div style="text-align: center; padding: 50px; margin:10px 0; border-radius: 8px; background-color: #FFFFFF; color: #333333; font-family: Arial, sans-serif; max-width: 450px; margin-left: auto; margin-right: auto; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">
56
+ <img src="{team_logo_url}" alt="{team_display_name} Logo" style="max-width: 80px; max-height: 80px; margin-bottom: 10px; display: block; margin-left: auto; margin-right: auto; border-radius: 5px;" onerror="this.onerror=null; this.src='{default_logo_url}';">
57
+ <h3 style="margin-top: 0; margin-bottom: 8px; font-size: 22px; color: #000000;">{team_display_name}</h3>
58
+ <p style="font-size: 15px; color: #333333; margin-bottom: 15px;">{city_display}</p>
59
+ {team_page_cta_html}
60
+ </div>
61
  """
62
+
63
  team_image_map = {
64
  'everglade-fc': 'Everglade_FC',
65
  'fraser-valley-united': 'Fraser_Valley_United',
 
117
  print(f"\n{Fore.CYAN}[TOOL END] {output}{Style.RESET_ALL}")
118
  for doc in output:
119
  if doc.metadata.get("show_profile_card"):
120
+ raw_player_name = doc.metadata.get("name", "unknown-player")
121
+ player_name = "Unknown Player"
122
+ if raw_player_name != "unknown-player":
123
+ parts = raw_player_name.split('-')
124
+ if len(parts) == 2:
125
+ first_name = parts[0].capitalize()
126
+ last_name = parts[1].capitalize()
127
+ player_name = f"{last_name} {first_name}"
128
+ else:
129
+ player_name = raw_player_name.replace('-', ' ').title()
130
+ else:
131
+ player_name = "Unknown Player"
132
+
133
+ team_slug = doc.metadata.get("team", "unknown-team")
134
+ formatted_team_name = team_slug.replace('-', ' ').title()
135
+ if team_slug == "unknown-team":
136
+ formatted_team_name = "Unknown Team"
137
+
138
+ player_number = doc.metadata.get("number", "N/A")
139
+ height = doc.metadata.get("height", "175")
140
+ weight = doc.metadata.get("weight", "150")
141
+ instagram_url = doc.metadata.get("instagram_url", raw_player_name)
142
+
143
+ img = image_base.format(
144
+ filename=self.get_image_filename(doc),
145
+ player_name=player_name,
146
+ team_name=formatted_team_name,
147
+ player_number=player_number,
148
+ height=height,
149
+ weight=weight,
150
+ instagram_url=instagram_url
151
+ )
152
  self.ots_box(img)
153
+ return
154
+
155
+ if output and isinstance(output[0], object) and hasattr(output[0], 'metadata') and output[0].metadata.get('type') == 'event':
156
+ game_teams_set = set()
157
+ game_events_details = []
158
+ raw_game_name = "Unknown Game"
159
+
160
+ for i, doc in enumerate(output):
161
+ if hasattr(doc, 'metadata') and doc.metadata.get('team'):
162
+ game_teams_set.add(doc.metadata.get('team'))
163
+ if i == 0:
164
+ raw_game_name = doc.metadata.get('game_name', raw_game_name)
165
+
166
+ if hasattr(doc, 'metadata') and doc.metadata.get('event') == 'Goal':
167
+ event_desc = doc.metadata.get('description', 'Goal scored')
168
+ event_minute = doc.metadata.get('minute', '')
169
+ event_team = doc.metadata.get('team', '')
170
+ game_events_details.append(f"({event_minute}') {event_team}: {event_desc}")
171
+
172
+ if len(game_teams_set) >= 1:
173
+ team_list = list(game_teams_set)
174
+ team1_name_str = team_list[0]
175
+ team2_name_str = team_list[1] if len(team_list) > 1 else "(opponent not specified)"
176
+
177
+ score_team1 = 0
178
+ score_team2 = 0
179
+ for doc in output:
180
+ if hasattr(doc, 'metadata') and doc.metadata.get('event') == 'Goal':
181
+ scoring_team = doc.metadata.get('team')
182
+ if scoring_team == team1_name_str:
183
+ score_team1 += 1
184
+ elif scoring_team == team2_name_str:
185
+ score_team2 += 1
186
+
187
+ game_title_str = raw_game_name.replace('_', ' ').title()
188
+ highlights_html_str = "<ul style='list-style-type: none; padding-left: 0; text-align: left;'>"
189
+ for highlight in game_events_details[:3]:
190
+ highlights_html_str += f"<li style='margin-bottom: 5px; font-size: 14px;'>{highlight}</li>"
191
+ highlights_html_str += "</ul>"
192
+ if not game_events_details:
193
+ highlights_html_str = "<p style='font-size: 14px;'>No goal highlights available.</p>"
194
+
195
+ log_msg = f"Game: {game_title_str} | {team1_name_str} {score_team1} - {score_team2} {team2_name_str}"
196
+ team1_logo_filename = GradioEventHandler.get_team_logo_slug(team1_name_str)
197
+ team2_logo_filename = GradioEventHandler.get_team_logo_slug(team2_name_str)
198
+
199
+ team1_logo_url = f"{TEAM_LOGO_BASE_URL}{team1_logo_filename}"
200
+ team2_logo_url = f"{TEAM_LOGO_BASE_URL}{team2_logo_filename}"
201
+ default_logo_url = f"{TEAM_LOGO_BASE_URL}default.png"
202
+
203
+ formatted_game_html = game_card_html.format(
204
+ game_title=game_title_str,
205
+ team_home=team1_name_str,
206
+ team_away=team2_name_str,
207
+ team1_score=score_team1,
208
+ team2_score=score_team2,
209
+ highlights=highlights_html_str,
210
+ team1_logo_url=team1_logo_url,
211
+ team2_logo_url=team2_logo_url,
212
+ default_logo_url=default_logo_url
213
+ )
214
+ self.ots_box(formatted_game_html)
215
+ return
216
+ else:
217
+ print(f"\n{Fore.RED}[TOOL END - GAME CARD] Not enough team data found in events.{Style.RESET_ALL}")
218
+
219
+ elif isinstance(output, list) and output:
220
+ doc_for_team_card = output[0]
221
+ if hasattr(doc_for_team_card, 'metadata') and doc_for_team_card.metadata.get("show_team_card"):
222
+ team_name_from_meta = doc_for_team_card.metadata.get("team_name", "Unknown Team")
223
+ city_raw = doc_for_team_card.metadata.get("city", "N/A")
224
+ display_team_name = str(team_name_from_meta).replace('-', ' ').replace('_', ' ').title()
225
+ city_display = city_raw.title() if city_raw != "N/A" else "Location N/A"
226
+ logo_filename = GradioEventHandler.get_team_logo_slug(team_name_from_meta)
227
+ team_specific_logo_url = f"{TEAM_LOGO_BASE_URL}{logo_filename}"
228
+ current_default_logo_url = f"{TEAM_LOGO_BASE_URL}default.png"
229
+ team_id_slug = doc_for_team_card.metadata.get("team_id", "")
230
+ team_page_url = f"https://www.team.com/{team_id_slug}" if team_id_slug else "#"
231
+ team_page_cta_html = f'''<a href="{team_page_url}" target="_blank" style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block; font-size: 14px; margin-top: 10px;">Visit Team Page</a>'''
232
+
233
+ formatted_team_card_html = team_info_card_html.format(
234
+ team_logo_url=team_specific_logo_url,
235
+ team_display_name=display_team_name,
236
+ city_display=city_display,
237
+ default_logo_url=current_default_logo_url,
238
+ team_page_cta_html=team_page_cta_html
239
+ )
240
+
241
+ self.ots_box(formatted_team_card_html)
242
+ return
243
 
244
  async def on_tool_start(self, input: any, *args, **kwargs):
245
  self.info_box(input.get("name", "[TOOL START]"))
 
261
  @staticmethod
262
  def get_image_filename(doc):
263
  return f'{team_image_map.get(doc.metadata.get("team"))}_{doc.metadata.get("number")}.png'
264
+
265
+ @staticmethod
266
+ def get_team_logo_slug(team_name: str) -> str:
267
+ if not team_name or team_name == "(opponent not specified)":
268
+ return "default.png"
269
+ # Normalize accented for the Yucatán logo
270
+ normalized_team_name = team_name.lower().replace('á', 'a')
271
+ slug = normalized_team_name.replace(' ', '-')
272
+ return slug + ".png"
api/tools/__init__.py CHANGED
@@ -1,9 +1,11 @@
1
  from langchain_core.documents import Document
2
  from .player_search import PlayerSearchTool
3
  from .game_search import GameSearchTool
 
4
 
5
  __all__ = [
6
  "PlayerSearchTool",
7
  "GameSearchTool",
 
8
  "Document",
9
  ]
 
1
  from langchain_core.documents import Document
2
  from .player_search import PlayerSearchTool
3
  from .game_search import GameSearchTool
4
+ from .team_search import TeamSearchTool
5
 
6
  __all__ = [
7
  "PlayerSearchTool",
8
  "GameSearchTool",
9
+ "TeamSearchTool",
10
  "Document",
11
  ]
api/tools/team_search.py CHANGED
@@ -1 +1,92 @@
1
- # TODO: returns news; in this case recaps of the semi-final games
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Team Search Tool
3
+
4
+ This module defines the TeamSearchTool, a LangChain-compatible tool for searching soccer teams
5
+ in the fictional Huge League using the project's vector store.
6
+ """
7
+
8
+ from pydantic import BaseModel, Field
9
+ from langchain.tools import BaseTool
10
+ from langchain_core.documents import Document
11
+ from typing import Type, List, Optional
12
+ from langchain.callbacks.manager import (
13
+ AsyncCallbackManagerForToolRun,
14
+ CallbackManagerForToolRun,
15
+ )
16
+ from data.vectorstore_singleton import get_vector_store
17
+
18
+ vector_store = get_vector_store()
19
+
20
+
21
+ class TeamSearchInputSchema(BaseModel):
22
+ team_query: str = Field(description=(
23
+ "The search query to identify a soccer team in the fictional league. "
24
+ ))
25
+
26
+ class TeamSearchTool(BaseTool):
27
+ name: str = "team_search"
28
+ description: str = (
29
+ "Searches for a specific soccer team in the fictional league based on its name. "
30
+ "Returns information about the team, which can be used to display a team card."
31
+ )
32
+ args_schema: Type[BaseModel] = TeamSearchInputSchema
33
+
34
+ def _run(
35
+ self,
36
+ team_query: str,
37
+ run_manager: Optional[CallbackManagerForToolRun] = None,
38
+ ) -> List[Document]:
39
+ """Search for a team using the vector store."""
40
+ results = vector_store.similarity_search(
41
+ query=team_query,
42
+ k=1,
43
+ filter=lambda doc: doc.metadata.get("type") == "team",
44
+ )
45
+
46
+ processed_results = []
47
+ for doc in results:
48
+ team_name_found = doc.metadata.get("name", team_query)
49
+
50
+ doc.metadata["show_team_card"] = True
51
+ doc.metadata["team_name"] = team_name_found
52
+ doc.metadata.pop("country", None)
53
+ doc.metadata.pop("description", None)
54
+ if "city" not in doc.metadata:
55
+ doc.metadata["city"] = "N/A"
56
+
57
+ processed_results.append(doc)
58
+
59
+ return processed_results
60
+
61
+ async def _arun(
62
+ self,
63
+ team_query: str,
64
+ run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
65
+ ) -> List[Document]:
66
+ """Asynchronously searches for a team using the vector store."""
67
+ found_docs = await vector_store.asimilarity_search(
68
+ query=team_query,
69
+ k=3,
70
+ metadata={"type": "team"} # Use metadata filter instead of filter function
71
+ )
72
+
73
+ processed_results = []
74
+ if found_docs:
75
+ doc = found_docs[0]
76
+ if doc.metadata.get("type") == "team" and doc.metadata.get("name"):
77
+ metadata = {
78
+ "show_team_card": True,
79
+ "team_name": doc.metadata.get("name", "Unknown Team"),
80
+ "team_id": doc.metadata.get("id", doc.metadata.get("name", "unknown-id").lower().replace(" ", "-")),
81
+ "city": doc.metadata.get("city", "N/A"),
82
+ }
83
+ page_content = f"Found: {metadata['team_name']}. Location: {metadata.get('city')}."
84
+ processed_doc = Document(page_content=page_content, metadata=metadata)
85
+ processed_results.append(processed_doc)
86
+ else:
87
+ print(f"Found document for query '{team_query}' but it's not a valid team entry or lacks name.")
88
+
89
+ if not processed_results:
90
+ print(f"No team found for query: {team_query} after vector search and filtering.")
91
+
92
+ return processed_results
api/workflows/base.py CHANGED
@@ -28,12 +28,14 @@ from tools import (
28
  Document,
29
  PlayerSearchTool,
30
  GameSearchTool,
 
31
  )
32
 
33
 
34
  available_tools = [
35
  GameSearchTool(),
36
  PlayerSearchTool(),
 
37
  ]
38
  tool_node = ToolNode(available_tools)
39
 
 
28
  Document,
29
  PlayerSearchTool,
30
  GameSearchTool,
31
+ TeamSearchTool,
32
  )
33
 
34
 
35
  available_tools = [
36
  GameSearchTool(),
37
  PlayerSearchTool(),
38
+ TeamSearchTool(),
39
  ]
40
  tool_node = ToolNode(available_tools)
41