Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/electro.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: uvicorn --host=0.0.0.0 --port=${PORT:-8000} electro.app:app
19 changes: 19 additions & 0 deletions electro/app.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""The API server that works as an endpoint for all the Electro Interfaces."""

from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from tortoise.contrib.fastapi import register_tortoise

from . import types_ as types
from .chat_router import chat__router
from .flow_manager import global_flow_manager
from .settings import settings
from .toolkit.tortoise_orm import get_tortoise_config

app = FastAPI(
Expand All @@ -14,6 +17,20 @@
# docs_url="/",
# redoc_url=None,
)
app.add_middleware(
CORSMiddleware,
allow_origin_regex=(
# Allow http or https, with or without port, with or without subdomain
rf"(http|https)://.*{settings.DOMAIN.split(':', maxsplit=1)[0]}(:\d+)*"
if settings.DEBUG
# Allow all subdomains of the main domain, but only https
# pylint: disable=anomalous-backslash-in-string
else rf"https:\/\/.*\.?{'\.'.join(settings.DOMAIN.split('.')[-2:])}"
),
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


@app.post("/message")
Expand All @@ -23,6 +40,8 @@ async def process_message(message: types.Message) -> list[types.MessageToSend] |
return await global_flow_manager.on_message(message)


app.include_router(chat__router, prefix="/chat")

# region Register Tortoise
register_tortoise(
app,
Expand Down
92 changes: 92 additions & 0 deletions electro/assistant_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Functions for OpenAI Assistant."""

import json
from contextvars import ContextVar

from .enums import FrontendAction
from .toolkit.loguru_logging import logger

# Create a ContextVar to store frontend actions
action_context: ContextVar[list[str]] = ContextVar("actions", default=[])


async def get_frontend_actions(*_, **__) -> str:
"""
Asynchronously fetches the list of frontend actions from the database.
This function is designed to work directly with OpenAI Assistant's tool calling pattern.

Returns:
str: JSON string containing the list of frontend actions or error information

Schema:
{
"name": "get_frontend_actions",
"description": "Fetch the list of the actions that can be performed by the frontend",
"parameters": {
"type": "object",
"required": [],
"properties": {}
}
}
"""
try:
# Fetch the list of frontend actions
frontend_actions = [action.value for action in FrontendAction]

# Return the list of frontend actions
return json.dumps(frontend_actions, ensure_ascii=False, indent=2)

except Exception as e:
logger.error(f"Unexpected error while fetching frontend actions: {str(e)}")
error_response = {"error": str(e), "status": "failed"}
return json.dumps(error_response, ensure_ascii=False, indent=2)


async def submit_frontend_action(action: str, *_, **__) -> str:
"""
Asynchronously submits a frontend action to the database.
This function is designed to work directly with OpenAI Assistant's tool calling pattern.

Args:
action (str): The frontend action to submit

Returns:
str: JSON string containing the result or error information

Schema:
{
"name": "submit_frontend_action",
"description": "Submit an action to be performed by the frontend",
"parameters": {
"type": "object",
"required": ["action"],
"properties": {
"action": {
"type": "string",
"description": "The frontend action to submit"
}
}
}
}
"""
try:
# Input validation
if not action or not isinstance(action, str):
error_response = {"error": "action must be a non-empty string", "status": "failed"}
return json.dumps(error_response, ensure_ascii=False, indent=2)

# Check if the action is valid
if action not in [frontend_action.value for frontend_action in FrontendAction]:
error_response = {"error": "Invalid frontend action", "status": "failed"}
return json.dumps(error_response, ensure_ascii=False, indent=2)

# Save the frontend action
# TODO: [2025-01-31 by Mykola] Check if we can `.append()` it in-place, or we need to `.get()` and `.set()` it
action_context.get().append(action)

return json.dumps({"status": "success"}, ensure_ascii=False, indent=2)

except Exception as exception:
logger.error(f"Error submitting frontend action: {exception}")
error_response = {"error": str(exception), "status": "failed"}
return json.dumps(error_response, ensure_ascii=False, indent=2)
13 changes: 13 additions & 0 deletions electro/auth_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Authentication dependencies, using JWT."""

# TODO: [2025-07-14 by Mykola] Actually use JWT (or some other authentication method) for User authentication.

from fastapi.requests import Request

from .models import User


async def get_current_user(_request: Request) -> User:
"""Get the current user from the request."""

return await User.get(id=1)
Loading