Skip to content

Commit ae07c7c

Browse files
committed
Bump version to 3.0.32
1 parent 8a7614a commit ae07c7c

5 files changed

Lines changed: 117 additions & 10 deletions

File tree

llms/extensions/app/__init__.py

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import io
23
import json
34
import os
45
import time
@@ -471,17 +472,12 @@ async def get_user_avatar(req):
471472
ctx.add_get("/avatar/user", get_user_avatar)
472473

473474
async def get_agent_avatar(req):
474-
role = req.match_info["role"]
475475
mode = req.query.get("mode", "light")
476476

477477
# Cache for 1 hour # "Cache-Control": "public, max-age=3600",
478478
headers = {"Content-Type": "image/svg+xml"}
479479

480480
candidate_paths = [
481-
os.path.join(ctx.get_user_path(), role + "." + mode + ".png"),
482-
os.path.join(ctx.get_user_path(), role + "." + mode + ".svg"),
483-
os.path.join(ctx.get_user_path(), role + ".png"),
484-
os.path.join(ctx.get_user_path(), role + ".svg"),
485481
os.path.join(ctx.get_user_path(), "agent." + mode + ".png"),
486482
os.path.join(ctx.get_user_path(), "agent." + mode + ".svg"),
487483
os.path.join(ctx.get_user_path(), "agent.png"),
@@ -505,7 +501,118 @@ async def get_agent_avatar(req):
505501
"""
506502
return web.Response(text=default_avatar, headers=headers)
507503

508-
ctx.add_get("/agents/avatar/{role}", get_agent_avatar)
504+
ctx.add_get("/agents/avatar", get_agent_avatar)
505+
506+
async def upload_user_avatar(request):
507+
user = ctx.get_username(request)
508+
user_path = ctx.get_user_path(user=user)
509+
510+
# Ensure the user directory exists
511+
os.makedirs(user_path, exist_ok=True)
512+
513+
# Parse multipart form data
514+
reader = await request.multipart()
515+
field = await reader.next()
516+
517+
if field is None or field.name != "file":
518+
raise Exception("No file provided")
519+
520+
filename = field.filename or ""
521+
content_type = field.headers.get("Content-Type", "").lower()
522+
523+
# Read file data
524+
file_data = await field.read()
525+
526+
# Determine file type from extension or content type
527+
ext = os.path.splitext(filename)[1].lower() if filename else ""
528+
529+
if ext == ".svg" or content_type == "image/svg+xml":
530+
# Save SVG directly
531+
avatar_path = os.path.join(user_path, "avatar.svg")
532+
with open(avatar_path, "wb") as f:
533+
f.write(file_data)
534+
elif ext == ".png" or content_type == "image/png":
535+
# Save PNG directly
536+
avatar_path = os.path.join(user_path, "avatar.png")
537+
with open(avatar_path, "wb") as f:
538+
f.write(file_data)
539+
else:
540+
# Try to convert to PNG using Pillow
541+
try:
542+
from PIL import Image
543+
544+
img = Image.open(io.BytesIO(file_data))
545+
# Convert to RGB if necessary (for formats like JPEG)
546+
if img.mode in ("RGBA", "P"):
547+
img = img.convert("RGBA")
548+
elif img.mode != "RGB":
549+
img = img.convert("RGB")
550+
551+
avatar_path = os.path.join(user_path, "avatar.png")
552+
img.save(avatar_path, "PNG")
553+
except ImportError:
554+
raise Exception(
555+
"Only SVG and PNG formats are supported. Install Pillow to convert other image formats."
556+
) from None
557+
558+
return web.json_response({"success": True, "path": avatar_path})
559+
560+
ctx.add_post("/user/avatar", upload_user_avatar)
561+
562+
async def upload_agent_avatar(request):
563+
user_path = ctx.get_user_path()
564+
565+
# Ensure the user directory exists
566+
os.makedirs(user_path, exist_ok=True)
567+
568+
# Parse multipart form data
569+
reader = await request.multipart()
570+
field = await reader.next()
571+
572+
if field is None or field.name != "file":
573+
raise Exception("No file provided")
574+
575+
filename = field.filename or ""
576+
content_type = field.headers.get("Content-Type", "").lower()
577+
578+
# Read file data
579+
file_data = await field.read()
580+
581+
# Determine file type from extension or content type
582+
ext = os.path.splitext(filename)[1].lower() if filename else ""
583+
584+
if ext == ".svg" or content_type == "image/svg+xml":
585+
# Save SVG directly
586+
avatar_path = os.path.join(user_path, "agent.svg")
587+
with open(avatar_path, "wb") as f:
588+
f.write(file_data)
589+
elif ext == ".png" or content_type == "image/png":
590+
# Save PNG directly
591+
avatar_path = os.path.join(user_path, "agent.png")
592+
with open(avatar_path, "wb") as f:
593+
f.write(file_data)
594+
else:
595+
# Try to convert to PNG using Pillow
596+
try:
597+
from PIL import Image
598+
599+
img = Image.open(io.BytesIO(file_data))
600+
# Convert to RGB if necessary (for formats like JPEG)
601+
if img.mode in ("RGBA", "P"):
602+
img = img.convert("RGBA")
603+
elif img.mode != "RGB":
604+
img = img.convert("RGB")
605+
606+
avatar_path = os.path.join(user_path, "agent.png")
607+
img.save(avatar_path, "PNG")
608+
except ImportError:
609+
raise Exception(
610+
"Only SVG and PNG formats are supported. Install Pillow to convert other image formats."
611+
) from None
612+
613+
return web.json_response({"success": True, "path": avatar_path})
614+
615+
ctx.add_post("/agents/avatar", upload_agent_avatar)
509616

510617
async def chat_request(openai_request, context):
511618
chat = openai_request

llms/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
HAS_PIL = False
5858

5959
_ROOT = None
60-
VERSION = "3.0.31"
60+
VERSION = "3.0.32"
6161
DEBUG = os.getenv("DEBUG") == "1"
6262
MOCK = os.getenv("MOCK") == "1"
6363
MOCK_DIR = os.getenv("MOCK_DIR")

llms/ui/ai.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const headers = { 'Accept': 'application/json' }
66
const prefsKey = 'llms.prefs'
77

88
export const o = {
9-
version: '3.0.31',
9+
version: '3.0.32',
1010
base,
1111
prefsKey,
1212
welcome: 'Welcome to llms.py',

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "llms-py"
7-
version = "3.0.31"
7+
version = "3.0.32"
88
description = "A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers"
99
readme = "README.md"
1010
license = "BSD-3-Clause"

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
setup(
1717
name="llms-py",
18-
version="3.0.31",
18+
version="3.0.32",
1919
author="ServiceStack",
2020
author_email="team@servicestack.net",
2121
description="A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers",

0 commit comments

Comments
 (0)