File size: 9,826 Bytes
deb711d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
from smolagents import tool
import streamlit as st
import requests
import pandas as pd
# from duckduckgo_search import DDGS
# def duckduckgo_search(query: str, max_results: int = 3) -> list:
# """Search the web using DuckDuckGo"""
# with DDGS() as ddgs:
# return [r for r in ddgs.text(query, max_results=max_results)]
#MAL Tools
@tool
def add_anime(anime_id: int, status:str = "Watching", episodes_watched: int = 0, score: int = 0) -> str:
"""
Add an anime to your MyAnimeList.
Args:
anime_id (int): The ID of the anime to add.
status (str): The status of the anime (e.g., "Watching", "Completed", "On-Hold", "Dropped", "Plan to Watch").
episodes_watched (int): The number of episodes watched.
score (int): Your score for the anime (0-10).
Returns:
str: A message indicating the success or failure of the operation.
"""
try:
response = requests.patch( # Changed to PATCH
f"https://api.myanimelist.net/v2/anime/{anime_id}/my_list_status",
headers={
"Authorization": f"Bearer {st.session_state.access_token}",
"Content-Type": "application/x-www-form-urlencoded" # Required
},
data={ # Use data instead of json
"status": status,
"num_watched_episodes": episodes_watched,
"score": score
}
)
response.raise_for_status()
return "Anime added/updated successfully!"
except Exception as e:
return f"Failed to add anime: {str(e)}"
@tool
def remove_anime(anime_id: int) -> str:
"""
Remove an anime from your MyAnimeList.
Args:
anime_id (int): The ID of the anime to remove.
Returns:
str: A message indicating the success or failure of the operation.
"""
try:
response = requests.delete(
f"https://api.myanimelist.net/v2/anime/{anime_id}/my_list_status",
headers={
"Authorization": f"Bearer {st.session_state.access_token}",
}
)
response.raise_for_status()
return "Anime removed successfully!"
except Exception as e:
return f"Failed to remove anime: {e}"
@tool
def get_anime_list(status_filter: str = None) -> str:
"""
Get the list of anime in your MyAnimeList.
Args:
status_filter (str): Filter by status (e.g., "Watching", "Completed", "On-Hold", "Dropped", "Plan to Watch").
Returns:
str: A markdown table representation of the anime list with columns: Name, Status, Episodes Watched, Total Episodes, and Score.
"""
try:
params = {
"fields": "list_status,media_type,num_episodes",
"limit": 100
}
if status_filter:
params["status"] = status_filter.lower().replace(" ", "_") # Convert to MAL format
response = requests.get(
"https://api.myanimelist.net/v2/users/@me/animelist",
headers={"Authorization": f"Bearer {st.session_state.access_token}"},
params=params
)
response.raise_for_status() # Check for HTTP errors
return format_anime_list(response.json().get("data", []))
except Exception as e:
return f"Error fetching list: {str(e)}"
@tool
def search_anime(query: str) -> str:
"""
Search for anime by title.
Args:
query (str): The title of the anime to search for.
Returns:
str: A string representation of the search results.
"""
try:
response = requests.get(
"https://api.myanimelist.net/v2/anime",
headers={
"Authorization": f"Bearer {st.session_state.access_token}",
},
params={
"q": query,
"limit": 10
}
)
response.raise_for_status()
return response.json()
except Exception as e:
return f"Failed to search anime: {e}"
@tool
def format_anime_list(anime_list: list) -> str:
"""
Format the anime list as a markdown table.
Args:
anime_list (list): The list of anime entries.
Returns:
str: A markdown table representation of the anime list with columns: Name, Status, Episodes Watched, Total Episodes, and Score.
"""
if not anime_list:
return "No anime found."
# Create markdown table header
table = "| Name | Status | Episodes Watched | Total Episodes | Score |\n"
table += "|------|--------|------------------|----------------|-------|\n"
# Add rows for each anime
for entry in anime_list:
anime = entry["node"]
list_status = entry["list_status"]
title = anime["title"]
status = list_status["status"].replace("_", " ").title()
episodes_watched = list_status["num_episodes_watched"]
total_episodes = anime.get("num_episodes", "Unknown")
score = list_status.get("score", "Not Rated")
# Handle score display
score_display = str(score) if score and score > 0 else "Not Rated"
# Escape pipe characters in title if any
title_escaped = title.replace("|", "\\|")
table += f"| {title_escaped} | {status} | {episodes_watched} | {total_episodes} | {score_display} |\n"
return table
@tool
def display_anime_cards(search_results: list[dict]) -> str:
"""
Render a list of anime search results as visually formatted cards using Streamlit.
This function displays multiple anime entries in a structured, scrollable layout,
where each anime is presented as a card with its cover image, title (linked to detail page),
rating, episode count, and a truncated synopsis. Designed for use in interactive applications
like SmolAgents, this enhances user experience by presenting search data in an intuitive format.
Args:
search_results (list[dict]): A list of dictionaries, where each dictionary contains metadata
for a single anime. Expected keys in each dictionary:
- title (str): Title of the anime.
- cover_image (str): URL of the anime’s cover/poster image.
- rating (float): Average rating (0.0 to 10.0 scale).
- episodes (int): Total number of episodes.
- synopsis (str): Brief plot summary.
- url (str): Link to the anime’s full detail page.
Returns:
None: The function does not return any value.
It directly renders UI components using the Streamlit.
Example:
search_results = [
{
"title": "Naruto",
"cover_image": "https://cdn.example.com/naruto.jpg",
"rating": 7.9,
"episodes": 220,
"synopsis": "A young ninja strives to become Hokage...",
"url": "https://myanimelist.net/anime/20/Naruto"
},
...
]
display_anime_cards(search_results)
"""
pass
# for anime in search_results:
# # Extract cover image URL (prefer large, then medium, then fallback)
# cover_url = None
# if "main_picture" in anime:
# cover_url = anime["main_picture"].get("large") or anime["main_picture"].get("medium")
# elif "cover_url" in anime:
# cover_url = anime["cover_url"]
# else:
# cover_url = "https://via.placeholder.com/150x220.png?text=No+Image"
# with st.container(border=True):
# col1, col2 = st.columns([1, 3])
# with col1:
# st.image(cover_url, width=150)
# with col2:
# st.markdown(f"### {anime.get('title', 'Untitled')}")
# mal_score = anime.get('score', 0)
# normalized_rating = mal_score / 2 # Convert to 0-5 scale
# full_stars = int(normalized_rating)
# half_star = 1 if normalized_rating - full_stars >= 0.5 else 0
# empty_stars = 5 - full_stars - half_star
# stars = "⭐" * full_stars + "⯨" * half_star + "✰" * empty_stars
# st.markdown(f"**MAL Score:** {stars} ({mal_score}/10)")
# if 'episodes' in anime:
# st.caption(f"Episodes: {anime['episodes']}")
# if 'status' in anime:
# st.caption(f"Airing Status: {anime['status'].capitalize()}")
# return "Displayed anime results successfully"
@tool
def hianime_watchlink(query: str) -> str:
"""
Search for a HiAnime watch link for the anime in query make Sure its hianime.sx link.
Args:
query (str): The title of the anime to search for.
Returns:
str: A string representation of the hianime watch link or an error message.
"""
pass
# try:
# results = duckduckgo_search(f"{query} site:hianime.tv", max_results=1)
# return results[0]['href'] if results else "No HiAnime link found"
# except Exception as e:
# return f"Search failed: {str(e)}"
@tool
def anime_suggestion(query: str) -> str:
"""
Suggest an anime based on a query.
Args:
query (str): The title or description of the anime to suggest.
Returns:
str: A tabular representation of the suggested anime or an error message, this suggestion than searched through search_anime tool to get more information.
"""
# try:
# results = duckduckgo_search(query + " anime suggestion")
# if not results:
# return "No anime suggestion found."
# return results[0].get("href", "No valid suggestion found.")
# except Exception as e:
# return f"Failed to suggest anime: {e}"
pass
|