From 3e5d8b03ec3daccc964b442c8f273b613b496bf9 Mon Sep 17 00:00:00 2001 From: Johan Grande Date: Sun, 18 Jan 2026 14:02:45 +0100 Subject: [PATCH 1/5] Several improvements --- .gitignore | 3 +- tools/giphy/giphy.py | 81 ++++++++++++++++++++++++++------------------ 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index b694934..605d130 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.venv \ No newline at end of file +.venv +*.iml diff --git a/tools/giphy/giphy.py b/tools/giphy/giphy.py index c194894..a3b8fd3 100644 --- a/tools/giphy/giphy.py +++ b/tools/giphy/giphy.py @@ -1,18 +1,19 @@ """ title: Giphy Search and Embed Tool description: Search Giphy for GIFs and display them in the chat -version: 1.0.1 -author: elfin8er +version: 1.1.0 +author: elfin8er, nahoj author_site: https://github.com/elfin8er/Open_WebUI_Stuff git_url: https://github.com/elfin8er/Open_WebUI_Stuff.git -requires: aiohttp, fastapi, pydantic +requires: aiohttp, pydantic """ -import random +from typing import Optional, Any, Callable, Awaitable, Literal + import aiohttp -from typing import Optional, Any, Callable, Awaitable from pydantic import BaseModel, Field + async def emit_status( event_emitter: Optional[Callable[[Any], Awaitable[None]]], description: str, @@ -24,16 +25,6 @@ async def emit_status( {"type": "status", "data": {"description": description, "done": done}} ) -async def emit_embed( - gif: dict, - __event_emitter__: Callable[[dict], Awaitable[None]], -) -> None: - await __event_emitter__({ - "type": "message", - "data": { - "content": f"![test]({gif['images']['original']['url']})\n[via GIPHY]({gif['url']})" - } - }) class Tools: class Valves(BaseModel): @@ -49,31 +40,49 @@ class Valves(BaseModel): default=6, description="Number of GIFs to retrieve per search", ) - GIF_RATING: str = Field( - default="g", - description="Content rating for GIFs (e.g., g, pg, pg-13, r)", - ) + + class UserValves(BaseModel): GIF_LANG: str = Field( default="en", - description="Language for search results", + description="Language(s) for search results, e.g. 'en,fr'", + ) + GIF_RATING: Literal["g", "pg", "pg-13", "r"] = Field( + default="g", + description="Content rating for GIFs", ) def __init__(self): self.valves = self.Valves() - async def search_gifs(self, query: str, __event_emitter__: Callable[[dict], Awaitable[None]]): + async def search_gifs( + self, + query: str, + __event_emitter__: Callable[[dict], Awaitable[None]], + __user__: Optional[dict] = None, + ): """ - Search for a GIF on Giphy and embed it in the chat. - :param query: The search term to find a GIF (e.g., 'funny cat'). - :return: A status message indicating if the GIF was found. + Search for GIFs on Giphy. To display to the user, use Markdown syntax `![{alt}]({url})`. + :param query: The search terms to find GIFs for. + :return: GIFs to choose from. """ + user_valves = self.UserValves.model_validate(__user__.get("valves", {})) + if not self.valves.GIPHY_API_KEY: return "ERROR: GIPHY_API_KEY is not set in Valves configuration." if not query: return "ERROR: No search query provided." await emit_status(__event_emitter__, f"Searching Giphy for: {query}") - url = f"{self.valves.API_BASE_URL}/gifs/search?api_key={self.valves.GIPHY_API_KEY}&q={query}&limit={self.valves.GIF_LIMIT}&offset=0&rating={self.valves.GIF_RATING}&lang={self.valves.GIF_LANG}" - + url = ( + f"{self.valves.API_BASE_URL}/gifs/search" + f"?api_key={self.valves.GIPHY_API_KEY}" + f"&q={query}" + f"&limit={self.valves.GIF_LIMIT}" + f"&offset=0" + f"&rating={user_valves.GIF_RATING}" + f"&lang={user_valves.GIF_LANG}" + ) + # await emit_status(__event_emitter__, f"Giphy URL: {url}") + try: async with aiohttp.ClientSession() as session: async with session.get(url) as response: @@ -81,13 +90,19 @@ async def search_gifs(self, query: str, __event_emitter__: Callable[[dict], Awai return "Error: Invalid API key or API quota exceeded. Please check your Giphy API key and quota." elif response.status != 200: return f"Error: Giphy API returned status {response.status}" - + search_data = await response.json() - if not search_data['data']: + # await emit_status(__event_emitter__, f"Got response: {search_data}") + if not search_data["data"]: return f"ERROR: No GIFs found for query: {query}" - gif = search_data['data'][random.randint(0, len(search_data['data']) - 1)] - await emit_status(__event_emitter__, f"Displaying gif from query: {query}", done=True) - await emit_embed(gif, __event_emitter__) - return f"Successfully found and displayed a GIF for '{query}'." + + return [ + { + "description": (gif["alt_text"] or f'{gif["title"]} {gif["slug"]}'), + "url": gif["images"]["original"]["webp"], + } + for gif in search_data["data"] + ] + except Exception as e: - return f"Error occurred while searching Giphy: {str(e)}" \ No newline at end of file + return f"Error occurred while searching Giphy: {str(e)}" From 6af48c02251ec59919a7a5016c4f69f56d619d80 Mon Sep 17 00:00:00 2001 From: Johan Grande Date: Sun, 18 Jan 2026 14:32:51 +0100 Subject: [PATCH 2/5] No multiple languages + fallback to .gif if no webp --- tools/giphy/giphy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/giphy/giphy.py b/tools/giphy/giphy.py index a3b8fd3..c2479c3 100644 --- a/tools/giphy/giphy.py +++ b/tools/giphy/giphy.py @@ -44,7 +44,7 @@ class Valves(BaseModel): class UserValves(BaseModel): GIF_LANG: str = Field( default="en", - description="Language(s) for search results, e.g. 'en,fr'", + description="Language for search results", ) GIF_RATING: Literal["g", "pg", "pg-13", "r"] = Field( default="g", @@ -99,7 +99,7 @@ async def search_gifs( return [ { "description": (gif["alt_text"] or f'{gif["title"]} {gif["slug"]}'), - "url": gif["images"]["original"]["webp"], + "url": (gif["images"]["original"].get("webp", None) or gif["images"]["original"]["url"]), } for gif in search_data["data"] ] From 61da09a7a5527006a8dee2a8b1996ca2cdd69f86 Mon Sep 17 00:00:00 2001 From: elfin8er Date: Sun, 18 Jan 2026 14:52:06 -0500 Subject: [PATCH 3/5] Multiple languages + multiple gifs --- tools/giphy/giphy.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/tools/giphy/giphy.py b/tools/giphy/giphy.py index 2f8fc81..a189803 100644 --- a/tools/giphy/giphy.py +++ b/tools/giphy/giphy.py @@ -10,7 +10,7 @@ from typing import Optional, Any, Callable, Awaitable, Literal -import random +import json import aiohttp from pydantic import BaseModel, Field @@ -45,7 +45,7 @@ class Valves(BaseModel): class UserValves(BaseModel): GIF_LANG: str = Field( default="en", - description="Language for search results", + description="Language(s) for search results (eg. 'en,fr')", ) GIF_RATING: Literal["g", "pg", "pg-13", "r"] = Field( default="g", @@ -62,9 +62,11 @@ async def search_gifs( __user__: Optional[dict] = None, ): """ - Search for a GIF on Giphy. - :param query: The search term to find a GIF (e.g., 'funny cat'). - :return: A markdown embed string to embed the gif in the chat. + Search for GIFs on Giphy. When displaying a GIF, you MUST ALWAYS include the full markdown image and attribution. + :param query: The search term to find a GIF. + :return: A dictionary of GIFs to choose from. Each dict contains: + * title: The title of the GIF + * markdown: The markdown to display the GIF in the chat. """ user_valves = self.UserValves.model_validate(__user__.get("valves", {})) @@ -92,13 +94,16 @@ async def search_gifs( elif response.status != 200: return f"Error: Giphy API returned status {response.status}" - search_data = await response.json() - # await emit_status(__event_emitter__, f"Got response: {search_data}") - if not search_data["data"]: + search_data = (await response.json())['data'] + await emit_status(__event_emitter__, f"Found {len(search_data)} GIFs from query: {query}", done=True) + if not search_data: return f"ERROR: No GIFs found for query: {query}" - gif = search_data['data'][random.randint(0, len(search_data['data']) - 1)] - await emit_status(__event_emitter__, f"Displaying gif from query: {query}") - await emit_status(__event_emitter__, gif['url'], done=True) - return f"![from giphy]({gif['images']['original']['url']}) [via GIPHY]({gif['url']})" + output = [] + for gif in search_data: + elem = {} + elem['title'] = gif['title'] or "No title" + elem['markdown'] = f"![giphy]({gif['images']['original']['url']}) [via GIPHY]({gif['url']})" + output.append(elem) + return json.dumps(output, indent=2) except Exception as e: - return f"Error occurred while searching Giphy: {str(e)}" + return f"Error occurred while searching Giphy: {str(e)}" \ No newline at end of file From 7d48c59d5309d8aa8c740f4d9e60d05f43ab2bd2 Mon Sep 17 00:00:00 2001 From: elfin8er Date: Sun, 18 Jan 2026 18:06:46 -0500 Subject: [PATCH 4/5] Add alt-text --- tools/giphy/giphy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/giphy/giphy.py b/tools/giphy/giphy.py index a189803..d069355 100644 --- a/tools/giphy/giphy.py +++ b/tools/giphy/giphy.py @@ -102,6 +102,7 @@ async def search_gifs( for gif in search_data: elem = {} elem['title'] = gif['title'] or "No title" + elem['description'] = gif['alt_text'] or "No description" elem['markdown'] = f"![giphy]({gif['images']['original']['url']}) [via GIPHY]({gif['url']})" output.append(elem) return json.dumps(output, indent=2) From 8dadeccb29fc08e4ed661def0655a1401e03346a Mon Sep 17 00:00:00 2001 From: elfin8er Date: Sun, 18 Jan 2026 18:13:49 -0500 Subject: [PATCH 5/5] Update docstring to include description --- tools/giphy/giphy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/giphy/giphy.py b/tools/giphy/giphy.py index d069355..9dfeb89 100644 --- a/tools/giphy/giphy.py +++ b/tools/giphy/giphy.py @@ -66,6 +66,7 @@ async def search_gifs( :param query: The search term to find a GIF. :return: A dictionary of GIFs to choose from. Each dict contains: * title: The title of the GIF + * description: The alt text/description of the GIF * markdown: The markdown to display the GIF in the chat. """ user_valves = self.UserValves.model_validate(__user__.get("valves", {}))