239 lines
9.6 KiB
Python
239 lines
9.6 KiB
Python
import aiohttp
|
|
import json
|
|
import datetime
|
|
from typing import Dict, List, Optional, Any, Union
|
|
from api_service.api_models import Conversation, UserSettings, Message
|
|
|
|
|
|
class ApiClient:
|
|
def __init__(self, api_url: str, token: Optional[str] = None):
|
|
"""
|
|
Initialize the API client
|
|
|
|
Args:
|
|
api_url: The URL of the API server
|
|
token: The Discord token to use for authentication
|
|
"""
|
|
self.api_url = api_url
|
|
self.token = token
|
|
|
|
def set_token(self, token: str):
|
|
"""Set the Discord token for authentication"""
|
|
self.token = token
|
|
|
|
async def _make_request(
|
|
self, method: str, endpoint: str, data: Optional[Dict] = None
|
|
):
|
|
"""
|
|
Make a request to the API
|
|
|
|
Args:
|
|
method: The HTTP method to use
|
|
endpoint: The API endpoint to call
|
|
data: The data to send with the request
|
|
|
|
Returns:
|
|
The response data
|
|
"""
|
|
if not self.token:
|
|
raise ValueError("No token set for API client")
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {self.token}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
url = f"{self.api_url}/{endpoint}"
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
if method == "GET":
|
|
async with session.get(url, headers=headers) as response:
|
|
if response.status != 200:
|
|
error_text = await response.text()
|
|
raise Exception(
|
|
f"API request failed: {response.status} - {error_text}"
|
|
)
|
|
response_text = await response.text()
|
|
return json.loads(response_text)
|
|
elif method == "POST":
|
|
# Convert data to JSON with datetime handling
|
|
json_data = (
|
|
json.dumps(data, default=str, ensure_ascii=False) if data else None
|
|
)
|
|
# Update headers for manually serialized JSON
|
|
if json_data:
|
|
headers["Content-Type"] = "application/json"
|
|
async with session.post(
|
|
url, headers=headers, data=json_data
|
|
) as response:
|
|
if response.status not in (200, 201):
|
|
error_text = await response.text()
|
|
raise Exception(
|
|
f"API request failed: {response.status} - {error_text}"
|
|
)
|
|
response_text = await response.text()
|
|
return json.loads(response_text)
|
|
elif method == "PUT":
|
|
# Convert data to JSON with datetime handling
|
|
json_data = (
|
|
json.dumps(data, default=str, ensure_ascii=False) if data else None
|
|
)
|
|
# Update headers for manually serialized JSON
|
|
if json_data:
|
|
headers["Content-Type"] = "application/json"
|
|
async with session.put(
|
|
url, headers=headers, data=json_data
|
|
) as response:
|
|
if response.status != 200:
|
|
error_text = await response.text()
|
|
raise Exception(
|
|
f"API request failed: {response.status} - {error_text}"
|
|
)
|
|
response_text = await response.text()
|
|
return json.loads(response_text)
|
|
elif method == "DELETE":
|
|
async with session.delete(url, headers=headers) as response:
|
|
if response.status != 200:
|
|
error_text = await response.text()
|
|
raise Exception(
|
|
f"API request failed: {response.status} - {error_text}"
|
|
)
|
|
response_text = await response.text()
|
|
return json.loads(response_text)
|
|
else:
|
|
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
|
|
# ============= Conversation Methods =============
|
|
|
|
async def get_conversations(self) -> List[Conversation]:
|
|
"""Get all conversations for the authenticated user"""
|
|
response = await self._make_request("GET", "conversations")
|
|
return [Conversation.model_validate(conv) for conv in response["conversations"]]
|
|
|
|
async def get_conversation(self, conversation_id: str) -> Conversation:
|
|
"""Get a specific conversation"""
|
|
response = await self._make_request("GET", f"conversations/{conversation_id}")
|
|
return Conversation.model_validate(response)
|
|
|
|
async def create_conversation(self, conversation: Conversation) -> Conversation:
|
|
"""Create a new conversation"""
|
|
response = await self._make_request(
|
|
"POST", "conversations", {"conversation": conversation.model_dump()}
|
|
)
|
|
return Conversation.model_validate(response)
|
|
|
|
async def update_conversation(self, conversation: Conversation) -> Conversation:
|
|
"""Update an existing conversation"""
|
|
response = await self._make_request(
|
|
"PUT",
|
|
f"conversations/{conversation.id}",
|
|
{"conversation": conversation.model_dump()},
|
|
)
|
|
return Conversation.model_validate(response)
|
|
|
|
async def delete_conversation(self, conversation_id: str) -> bool:
|
|
"""Delete a conversation"""
|
|
response = await self._make_request(
|
|
"DELETE", f"conversations/{conversation_id}"
|
|
)
|
|
return response["success"]
|
|
|
|
# ============= Settings Methods =============
|
|
|
|
async def get_settings(self) -> UserSettings:
|
|
"""Get settings for the authenticated user"""
|
|
response = await self._make_request("GET", "settings")
|
|
return UserSettings.model_validate(response["settings"])
|
|
|
|
async def update_settings(self, settings: UserSettings) -> UserSettings:
|
|
"""Update settings for the authenticated user"""
|
|
response = await self._make_request(
|
|
"PUT", "settings", {"settings": settings.model_dump()}
|
|
)
|
|
return UserSettings.model_validate(response)
|
|
|
|
# ============= Helper Methods =============
|
|
|
|
async def save_discord_conversation(
|
|
self,
|
|
messages: List[Dict[str, Any]],
|
|
model_id: str = "openai/gpt-3.5-turbo",
|
|
conversation_id: Optional[str] = None,
|
|
title: str = "Discord Conversation",
|
|
reasoning_enabled: bool = False,
|
|
reasoning_effort: str = "medium",
|
|
temperature: float = 0.7,
|
|
max_tokens: int = 1000,
|
|
web_search_enabled: bool = False,
|
|
system_message: Optional[str] = None,
|
|
) -> Conversation:
|
|
"""
|
|
Save a conversation from Discord to the API
|
|
|
|
Args:
|
|
messages: List of message dictionaries with 'content', 'role', and 'timestamp'
|
|
model_id: The model ID to use for the conversation
|
|
conversation_id: Optional ID for the conversation (will create new if not provided)
|
|
title: The title of the conversation
|
|
reasoning_enabled: Whether reasoning is enabled for the conversation
|
|
reasoning_effort: The reasoning effort level ("low", "medium", "high")
|
|
temperature: The temperature setting for the model
|
|
max_tokens: The maximum tokens setting for the model
|
|
web_search_enabled: Whether web search is enabled for the conversation
|
|
system_message: Optional system message for the conversation
|
|
|
|
Returns:
|
|
The saved Conversation object
|
|
"""
|
|
# Convert messages to the API format
|
|
api_messages = []
|
|
for msg in messages:
|
|
api_messages.append(
|
|
Message(
|
|
content=msg["content"],
|
|
role=msg["role"],
|
|
timestamp=msg.get("timestamp", datetime.datetime.now()),
|
|
reasoning=msg.get("reasoning"),
|
|
usage_data=msg.get("usage_data"),
|
|
)
|
|
)
|
|
|
|
# Create or update the conversation
|
|
if conversation_id:
|
|
# Try to get the existing conversation
|
|
try:
|
|
conversation = await self.get_conversation(conversation_id)
|
|
# Update the conversation
|
|
conversation.messages = api_messages
|
|
conversation.model_id = model_id
|
|
conversation.reasoning_enabled = reasoning_enabled
|
|
conversation.reasoning_effort = reasoning_effort
|
|
conversation.temperature = temperature
|
|
conversation.max_tokens = max_tokens
|
|
conversation.web_search_enabled = web_search_enabled
|
|
conversation.system_message = system_message
|
|
conversation.updated_at = datetime.datetime.now()
|
|
|
|
return await self.update_conversation(conversation)
|
|
except Exception:
|
|
# Conversation doesn't exist, create a new one
|
|
pass
|
|
|
|
# Create a new conversation
|
|
conversation = Conversation(
|
|
id=conversation_id if conversation_id else None,
|
|
title=title,
|
|
messages=api_messages,
|
|
model_id=model_id,
|
|
reasoning_enabled=reasoning_enabled,
|
|
reasoning_effort=reasoning_effort,
|
|
temperature=temperature,
|
|
max_tokens=max_tokens,
|
|
web_search_enabled=web_search_enabled,
|
|
system_message=system_message,
|
|
created_at=datetime.datetime.now(),
|
|
updated_at=datetime.datetime.now(),
|
|
)
|
|
|
|
return await self.create_conversation(conversation)
|