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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ venv/
*.pyc

#Google Calendar key
config/google_calendar_sa.json
config/google_calendar_sa.jsonbackend/config/google_calendar_sa.json
File renamed without changes.
34 changes: 34 additions & 0 deletions application/orchestrator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from backend.graph.graph import build_graph
from backend.graph.state import EventState


class Orchestrator:
def __init__(self):
self.graph = build_graph()
self.sessions = {}

def _load_state(self , user_id :str) -> EventState | None:
return self.sessions.get(user_id)

def _save_state(self, user_id:str, state:EventState):
self.sessions[user_id] = state

async def handle(self, user_id: str, message: str):
state = self._load_state(user_id)
if state is None:
state = EventState()

if state.awaiting_input:
state.user_response = message

result = await self.graph.ainvoke(state)

# Convert dict → EventState
updated_state = EventState(**result)

self._save_state(user_id, updated_state)

if updated_state.awaiting_input:
return updated_state.prompt

return "Workflow completed successfully."
Empty file added backend/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion config/llm.py → backend/config/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
load_dotenv()

from langchain_groq import ChatGroq
from config.settings import (
from backend.config.settings import (
GROQ_MODEL,
TEMPERATURE,
MAX_TOKENS,
Expand Down
3 changes: 0 additions & 3 deletions config/settings.py → backend/config/settings.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# config/settings.py

# -----------------------------
# LLM configuration (Groq only)
# -----------------------------

# Groq model name
GROQ_MODEL = "llama-3.1-8b-instant"

Expand Down
Empty file added backend/db/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion db/init_db.py → backend/db/init_db.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from db.models import Base, Coordinator
from backend.db.models import Base, Coordinator

DATABASE_URL = "sqlite:///coordinators.db"

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion db/repository.py → backend/db/repository.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from sqlalchemy import create_engine, asc
from sqlalchemy.orm import sessionmaker
from db.models import Coordinator
from backend.db.models import Coordinator

DATABASE_URL = "sqlite:///coordinators.db"

Expand Down
7 changes: 4 additions & 3 deletions graph/graph.py → backend/graph/graph.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from langgraph.graph import StateGraph, END

from graph.state import EventState
from graph.nodes import (
from backend.graph.state import EventState
from backend.graph.nodes import (
suggest_event_idea,
human_approve_idea,
fetch_dates,
Expand All @@ -14,7 +14,7 @@
publish_event_to_calendar,
persist_coordinator_updates
)
from graph.routing import (
from backend.graph.routing import (
route_after_idea_selection,
route_after_date_approval,
route_after_coordinator_approval,
Expand Down Expand Up @@ -53,6 +53,7 @@ def build_graph():
{
"fetch_dates": "fetch_dates",
"suggest_event_idea": "suggest_event_idea",
END: END

}
)
Expand Down
58 changes: 48 additions & 10 deletions graph/nodes.py → backend/graph/nodes.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from datetime import datetime

from graph.state import EventState
from tools.calendar import get_available_dates
from config.llm import get_llm
from config.settings import EVENT_DAY_PREFERENCE
from tools.coordinators import fetch_candidate_coordinators,reconstruct_names_from_tokens,increment_events_conducted
from tools.calendar import create_calendar_event
from tools.calendar import create_calendar_event
from backend.graph.state import EventState
from backend.tools.calendar import get_available_dates
from backend.config.llm import get_llm
from backend.config.settings import EVENT_DAY_PREFERENCE
from backend.tools.coordinators import fetch_candidate_coordinators,reconstruct_names_from_tokens,increment_events_conducted
from backend.tools.calendar import create_calendar_event
from backend.tools.calendar import create_calendar_event

from db.repository import get_coordinators_by_names
from backend.db.repository import get_coordinators_by_names


import re # for smarter parsing
Expand Down Expand Up @@ -45,7 +45,7 @@ def suggest_event_idea(state: EventState):
#HUMAN APPROVE IDEA NODE

def human_approve_idea(state: EventState):
print("\nProposed Event Ideas:")
'''print("\nProposed Event Ideas:")
for idx, idea in enumerate(state.ideas, start=1):
print(f"{idx}. {idea}")

Expand Down Expand Up @@ -74,8 +74,46 @@ def human_approve_idea(state: EventState):

print("Invalid input. Regenerating ideas.")
state.idea_approved = False
return state'''
# Asking for input phase
if state.user_response is None:
state.awaiting_input = True
state.prompt={
"type":"approve_idea",
"ideas":state.ideas,
"instructions": (
"Select a number (1-5), "
"'r' to regenerate ideas, "
"or type a custom event name."
),
}
return state

# Once user input is given processing the input
choice = state.user_response.strip()
state.user_response = None
if choice.lower() == 'r':
state.idea_approved = False

elif choice.isdigit():
index = int(choice) - 1
if 0 <= index < len(state.ideas):
state.selected_idea = state.ideas[index]
state.idea_approved = True
else:
state.idea_approved = False

elif choice:
state.selected_idea = choice
state.idea_approved = True

else:
state.idea_approved = False

state.awaiting_input = False
state.prompt = None
return state


#FETCH DATE NODE

Expand Down
9 changes: 8 additions & 1 deletion graph/routing.py → backend/graph/routing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from graph.state import EventState
from backend.graph.state import EventState
from langgraph.graph import END

#ROUTING AFTER IDEA APPROVAL
Expand All @@ -7,11 +7,18 @@
ELSE MOVE BACK TO SUGGEST EVENT IDEA NODE
'''
def route_after_idea_selection(state: EventState) -> str:

# If waiting for human input → stop graph here
if state.awaiting_input:
return END

if state.idea_approved:
return "fetch_dates"

return "suggest_event_idea"



#ROUTING AFTER DATE APPROVAL
'''
IF DATE IS APPROVED MOVE TO PROPOSE COORDINATOR NODE
Expand Down
7 changes: 7 additions & 0 deletions graph/state.py → backend/graph/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ class EventState:
writeup_approved: bool = False
coordinator_updates_persisted: bool=False

# Additional state handlers for seperating backend from other layers
awaiting_input : bool = False
prompt: dict | None = None
user_response : str | None = None



File renamed without changes.
6 changes: 3 additions & 3 deletions tools/coordinators.py → backend/tools/coordinators.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import List,Dict
from db.models import Coordinator
from db.init_db import Session
from backend.db.models import Coordinator
from backend.db.init_db import Session

from db.repository import (
from backend.db.repository import (
get_least_used_coordinators,
get_least_used_coordinators_excluding
)
Expand Down
Binary file modified coordinators.db
Binary file not shown.
Empty file added interfaces/__init__.py
Empty file.
24 changes: 24 additions & 0 deletions interfaces/cli_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from application.orchestrator import Orchestrator
async def run_cli():
orchestrator = Orchestrator()
user_id = "cli_user"

print("Type anything to start. Type 'exit' to quit.\n")

while True:
message = input(">> ")

if message.lower() == "exit":
break

result = await orchestrator.handle(user_id, message)

if isinstance(result, dict):
# This means graph paused
print("\n--- PROMPT ---")
print(result["instructions"])
for i, idea in enumerate(result["ideas"], start=1):
print(f"{i}. {idea}")
print("---------------\n")
else:
print(result)
Empty file added interfaces/discord_bot.py
Empty file.
41 changes: 29 additions & 12 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
from graph.state import EventState
from graph.graph import build_graph
from tools.calendar import set_calendar_provider,GoogleCalendarProvider
# main.py

if __name__ == "__main__":
# main.py
import sys
import asyncio

from backend.tools.calendar import set_calendar_provider, GoogleCalendarProvider


def setup_calendar():
set_calendar_provider(
GoogleCalendarProvider(
calendar_id="sbrsetcsi@gmail.com",
credentials_path="config/google_calendar_sa.json",
calendar_id="sbrsetcsi@gmail.com",
credentials_path="backend/config/google_calendar_sa.json",
)
)

app = build_graph()

initial_state = EventState()
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage:")
print(" python main.py cli")
print(" python main.py discord")
exit()

setup_calendar()

mode = sys.argv[1]

if mode == "cli":
from interfaces.cli_app import run_cli
asyncio.run(run_cli())

final_state = app.invoke(initial_state)
elif mode == "discord":
from interfaces.discord_bot import run_discord
run_discord()

print("\nFinal State:")
print(final_state)
else:
print("Invalid mode.")
4 changes: 2 additions & 2 deletions tests/check_coordinators_events_persisted.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from db.init_db import Session
from db.models import Coordinator
from backend.db.init_db import Session
from backend.db.models import Coordinator

db = Session()

Expand Down