Spaces:
Sleeping
Sleeping
Upload 46 files
Browse filestest application
- APIModules/AIAgents/AlertAgent.py +28 -0
- APIModules/AIAgents/MeetingSchedulerAgent.py +119 -0
- APIModules/AIAgents/SQLAgent.py +167 -0
- APIModules/AIAgents/TranscriptQAAgent.py +97 -0
- APIModules/AIAgents/__init__.py +12 -0
- APIModules/AIAgents/__pycache__/AlertAgent.cpython-312.pyc +0 -0
- APIModules/AIAgents/__pycache__/MeetingSchedulerAgent.cpython-312.pyc +0 -0
- APIModules/AIAgents/__pycache__/SQLAgent.cpython-312.pyc +0 -0
- APIModules/AIAgents/__pycache__/TranscriptQAAgent.cpython-312.pyc +0 -0
- APIModules/AIAgents/__pycache__/__init__.cpython-312.pyc +0 -0
- APIModules/APIs/__pycache__/agents_router.cpython-312.pyc +0 -0
- APIModules/APIs/__pycache__/base_router.cpython-312.pyc +0 -0
- APIModules/APIs/__pycache__/router.cpython-312.pyc +0 -0
- APIModules/APIs/agents_router.py +113 -0
- APIModules/ObjectModels/Calendar.py +9 -0
- APIModules/ObjectModels/Meeting.py +16 -0
- APIModules/ObjectModels/Project.py +12 -0
- APIModules/ObjectModels/Resource.py +12 -0
- APIModules/ObjectModels/Task.py +10 -0
- APIModules/ObjectModels/__init__.py +5 -0
- APIModules/ObjectModels/__pycache__/Task.cpython-312.pyc +0 -0
- APIModules/ObjectModels/__pycache__/__init__.cpython-312.pyc +0 -0
- APIModules/ObjectModels/functions.py +104 -0
- LICENSE +21 -0
- README.md +1 -1
- app.py +0 -2
- desktop-client_secret_690889704678-5lml5g9dfva4hk1eugmhkbpn5atpnb8c.apps.googleusercontent.com.json +13 -0
- frontend_pages/BasePage.py +7 -1
- frontend_pages/ChatModelPage.py +198 -44
- frontend_pages/ConfigPage.py +44 -2
- frontend_pages/Dashboard.py +3 -3
- frontend_pages/__init__.py +10 -1
- frontend_pages/__pycache__/BasePage.cpython-312.pyc +0 -0
- frontend_pages/__pycache__/ChatModelPage.cpython-312.pyc +0 -0
- frontend_pages/__pycache__/ConfigPage.cpython-312.pyc +0 -0
- frontend_pages/__pycache__/Dashboard.cpython-312.pyc +0 -0
- frontend_pages/__pycache__/__init__.cpython-312.pyc +0 -0
- frontend_pages/__pycache__/modals.cpython-312.pyc +0 -0
- frontend_pages/modals.py +25 -7
- google_calendar.py +164 -0
- initial_state.json +81 -0
APIModules/AIAgents/AlertAgent.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
|
| 3 |
+
class AlertAgent:
|
| 4 |
+
"""
|
| 5 |
+
AlertAgent controlla che la query SQL generata:
|
| 6 |
+
- Operi solo su task, projects e teams (o risorse relative al team)
|
| 7 |
+
- Non faccia riferimento ad alcuna tabella o colonna relativa ai calendari.
|
| 8 |
+
"""
|
| 9 |
+
def __init__(self, **kwargs):
|
| 10 |
+
# Definiamo i pattern vietati:
|
| 11 |
+
# Blocca ogni riferimento alla tabella "calendars" e alle sue colonne.
|
| 12 |
+
self.forbidden_patterns = [
|
| 13 |
+
r"\bcalendars\b", # riferimento diretto alla tabella
|
| 14 |
+
r"\bcalendar_id\b", # riferimento alla FK dei calendari
|
| 15 |
+
r"\bSELECT\b.*\bFROM\b.*\bcalendars\b", # SELECT su calendari
|
| 16 |
+
r"\bJOIN\b.*\bcalendars\b", # join su calendari
|
| 17 |
+
]
|
| 18 |
+
# Possiamo includere ulteriori controlli se necessario
|
| 19 |
+
|
| 20 |
+
def check(self, sql_query: str) -> bool:
|
| 21 |
+
"""
|
| 22 |
+
Controlla la query SQL.
|
| 23 |
+
Restituisce True se la query è sicura (non espone i calendari), False altrimenti.
|
| 24 |
+
"""
|
| 25 |
+
for pattern in self.forbidden_patterns:
|
| 26 |
+
if re.search(pattern, sql_query, re.IGNORECASE | re.DOTALL):
|
| 27 |
+
return False
|
| 28 |
+
return True
|
APIModules/AIAgents/MeetingSchedulerAgent.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime
|
| 2 |
+
|
| 3 |
+
from . import (
|
| 4 |
+
os
|
| 5 |
+
, WatsonxLLM
|
| 6 |
+
, PromptTemplate
|
| 7 |
+
, StrOutputParser
|
| 8 |
+
, json
|
| 9 |
+
# , JsonOutputParser
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
# Definizione del prompt template per l'agent
|
| 13 |
+
PROPOSE_MEETING_SCHEDULER_AGENT_PROMPT_TEMPLATE = f"""
|
| 14 |
+
Consider as input the following json containing informations about a table.
|
| 15 |
+
Generate as output a single meeting invitation with exclusively these items:
|
| 16 |
+
"subject": the title of the meeting (if it is not present in the input file, ask to the user)
|
| 17 |
+
"agenda": a list called “Meeting agenda” only with the tasks that are in “open” or “to be started” status. Do not consider tasks that are in “closed” state.
|
| 18 |
+
"start_date": start date and time in ISO format (e.g., '2025-02-21T10:00:00'). If not present, make it in half an hour (consider that now is {datetime.now()}).
|
| 19 |
+
"end_date": end date and time in ISO format (e.g., '2025-02-21T11:00:00'). If not present, make it as the start date + half an hour.
|
| 20 |
+
"participants": a list called “Invited team members” with all the team members that have at least one task in “open” or “to be started” status
|
| 21 |
+
""" + """
|
| 22 |
+
Format the output using bullet points and escapes
|
| 23 |
+
|
| 24 |
+
Table:
|
| 25 |
+
{prompt}
|
| 26 |
+
|
| 27 |
+
"""
|
| 28 |
+
# Definizione del prompt template per l'agent
|
| 29 |
+
MEETING_SCHEDULER_AGENT_PROMPT_TEMPLATE = """
|
| 30 |
+
You are an assistant for meeting scheduling. Given a textual input describing a meeting, extract and generate a JSON object with the following fields:
|
| 31 |
+
|
| 32 |
+
"subject": the title of the meeting
|
| 33 |
+
"agenda": the description and/or topic of the meeting
|
| 34 |
+
"start_date": start date and time in ISO format (e.g., '2025-02-21T10:00:00')
|
| 35 |
+
"end_date": end date and time in ISO format (e.g., '2025-02-21T11:00:00')
|
| 36 |
+
"participants": a list of email addresses of the invitees
|
| 37 |
+
Ensure that all data is valid and correctly formatted.
|
| 38 |
+
|
| 39 |
+
For example, if the input is:
|
| 40 |
+
"Schedule an update meeting with the team to discuss upcoming deadlines. It starts on 2025-02-21 at 10:00 and ends at 11:00. Invite mario.rossi@example.com and lucia.bianchi@example.com."
|
| 41 |
+
|
| 42 |
+
The output should be:
|
| 43 |
+
{{
|
| 44 |
+
"subject": "Project Update Meeting",
|
| 45 |
+
"agenda": "Discuss upcoming deadlines",
|
| 46 |
+
"start_date": "2025-02-21T10:00:00",
|
| 47 |
+
"end_date": "2025-02-21T11:00:00",
|
| 48 |
+
"participants": ["mario.rossi@example.com", "lucia.bianchi@example.com"]
|
| 49 |
+
}}
|
| 50 |
+
|
| 51 |
+
Return only the JSON, without any additional formatting.
|
| 52 |
+
""" + f"""
|
| 53 |
+
All the meetings should be scheduled for today or the following days.
|
| 54 |
+
Today is the""" + str(datetime.today()) + """
|
| 55 |
+
|
| 56 |
+
Prompt:
|
| 57 |
+
{prompt}
|
| 58 |
+
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
class ProposerMeetingSchedulerAgent:
|
| 62 |
+
def __init__(self, **kwargs):
|
| 63 |
+
self.meeting_agent_chain = (
|
| 64 |
+
PromptTemplate.from_template(template=PROPOSE_MEETING_SCHEDULER_AGENT_PROMPT_TEMPLATE)
|
| 65 |
+
| WatsonxLLM(
|
| 66 |
+
apikey=kwargs.get("WATSON_API_KEY", os.getenv("WATSON_API_KEY", None)),
|
| 67 |
+
project_id=kwargs.get("WATSONX_PROJECT_ID", os.getenv("WATSONX_PROJECT_ID", None)),
|
| 68 |
+
model_id=kwargs.get("model_id", os.getenv("WATSONX_MODEL_NAME", "ibm/granite-3-8b-instruct")),
|
| 69 |
+
url="https://us-south.ml.cloud.ibm.com",
|
| 70 |
+
params = {
|
| 71 |
+
"decoding_method": "sample",
|
| 72 |
+
"max_new_tokens": 3000,
|
| 73 |
+
"min_new_tokens": 1,
|
| 74 |
+
"temperature": 0.5,
|
| 75 |
+
"top_k": 50,
|
| 76 |
+
"top_p": 1,
|
| 77 |
+
},
|
| 78 |
+
)
|
| 79 |
+
| StrOutputParser()
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
def __call__(self, prompt: str) -> str:
|
| 83 |
+
return self.meeting_agent_chain.invoke({"prompt" : prompt})
|
| 84 |
+
# return json.loads(self.meeting_agent_chain.invoke({"prompt" : prompt}))
|
| 85 |
+
|
| 86 |
+
class MeetingSchedulerAgentFromTable:
|
| 87 |
+
def __init__(self, **kwargs):
|
| 88 |
+
self.meeting_agent_chain = (
|
| 89 |
+
PromptTemplate.from_template(template=MEETING_SCHEDULER_AGENT_PROMPT_TEMPLATE)
|
| 90 |
+
| WatsonxLLM(
|
| 91 |
+
apikey=kwargs.get("WATSON_API_KEY", os.getenv("WATSON_API_KEY", None)),
|
| 92 |
+
project_id=kwargs.get("WATSONX_PROJECT_ID", os.getenv("WATSONX_PROJECT_ID", None)),
|
| 93 |
+
model_id=kwargs.get("model_id", os.getenv("WATSONX_MODEL_NAME", "ibm/granite-3-8b-instruct")),
|
| 94 |
+
url="https://us-south.ml.cloud.ibm.com",
|
| 95 |
+
params = {
|
| 96 |
+
"decoding_method": "sample",
|
| 97 |
+
"max_new_tokens": 3000,
|
| 98 |
+
"min_new_tokens": 1,
|
| 99 |
+
"temperature": 0.5,
|
| 100 |
+
"top_k": 50,
|
| 101 |
+
"top_p": 1,
|
| 102 |
+
},
|
| 103 |
+
)
|
| 104 |
+
| StrOutputParser()
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
def __call__(self, prompt: str) -> str:
|
| 108 |
+
return json.loads(self.meeting_agent_chain.invoke({"prompt" : prompt}))
|
| 109 |
+
|
| 110 |
+
# Esempio d'uso:
|
| 111 |
+
if __name__ == "__main__":
|
| 112 |
+
agent = MeetingSchedulerAgentFromTable(WATSON_API_KEY="YOUR_API_KEY_HERE")
|
| 113 |
+
user_input = (
|
| 114 |
+
"Organizza un meeting di aggiornamento con il team per discutere le prossime scadenze. "
|
| 115 |
+
"Inizia il 2025-02-22 alle 22:00 e finisce alle 23:00. "
|
| 116 |
+
"Invita chiovelli.alessio@gmail.com e nicola.caione@gmail.com ."
|
| 117 |
+
)
|
| 118 |
+
meeting_parameters = agent(user_input)
|
| 119 |
+
print("Parametri per ScheduleMeeting:", meeting_parameters)
|
APIModules/AIAgents/SQLAgent.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from . import (
|
| 3 |
+
WatsonxLLM,
|
| 4 |
+
PromptTemplate,
|
| 5 |
+
StrOutputParser,
|
| 6 |
+
AlertAgent,
|
| 7 |
+
Graph,
|
| 8 |
+
TypedDict,
|
| 9 |
+
START, END,
|
| 10 |
+
add_messages
|
| 11 |
+
)
|
| 12 |
+
from .AlertAgent import AlertAgent
|
| 13 |
+
|
| 14 |
+
# Il template istruisce l'agente a produrre query SQL di INSERT (o UPDATE) che operino solo su task, progetti e team,
|
| 15 |
+
# escludendo qualsiasi operazione che richieda l'accesso o l'esposizione dei calendari dei team members.
|
| 16 |
+
SQL_AGENT_PROMPT_TEMPLATE = """
|
| 17 |
+
You are an AI assistant specialized in generating SQL queries for a database designed to manage projects, tasks, and meetings.
|
| 18 |
+
The database consists of the following tables:
|
| 19 |
+
|
| 20 |
+
### **1. projects (Projects)**
|
| 21 |
+
This table represents the registered projects in the system.
|
| 22 |
+
- **id** (INTEGER, PK) → Unique identifier of the project.
|
| 23 |
+
- **name** (STRING, UNIQUE, NOT NULL) → Project name (must be unique).
|
| 24 |
+
- **budget** (FLOAT, NULLABLE) → Budget assigned to the project.
|
| 25 |
+
- **start_date** (DATE, NULLABLE) → Project start date.
|
| 26 |
+
- **end_date** (DATE, NULLABLE) → Actual project completion date.
|
| 27 |
+
|
| 28 |
+
Relations:
|
| 29 |
+
- A **project** can have **multiple tasks** (1:N relationship with the `tasks` table).
|
| 30 |
+
|
| 31 |
+
### **2. tasks (Project Tasks)**
|
| 32 |
+
This table represents tasks assigned to a project.
|
| 33 |
+
- **id** (INTEGER, PK) → Unique identifier of the task.
|
| 34 |
+
- **name** (STRING, NOT NULL) → Task name.
|
| 35 |
+
- **start_date** (DATE, NULLABLE) → Task start date.
|
| 36 |
+
- **end_date** (DATE, NULLABLE) → Expected task completion date.
|
| 37 |
+
- **project_id** (INTEGER, FK) → Identifier of the associated project.
|
| 38 |
+
- **sub_tasks** (TEXT, NULLABLE) → List of sub-tasks (stored as JSON).
|
| 39 |
+
|
| 40 |
+
Relations:
|
| 41 |
+
- A **task** belongs to **only one project** (N:1 relationship with the `projects` table).
|
| 42 |
+
|
| 43 |
+
### **3. calendars (Calendars)**
|
| 44 |
+
This table represents calendars associated with human resources.
|
| 45 |
+
- **id** (INTEGER, PK) → Unique identifier of the calendar.
|
| 46 |
+
- **name** (STRING, NOT NULL) → Calendar name.
|
| 47 |
+
- **resource_id** (INTEGER, FK, NULLABLE) → Identifier of the associated resource.
|
| 48 |
+
|
| 49 |
+
Relations:
|
| 50 |
+
- A **calendar** is linked to **one resource** (1:1 relationship with the `resources` table).
|
| 51 |
+
- A **calendar** can contain **multiple meetings** (1:N relationship with the `meetings` table).
|
| 52 |
+
|
| 53 |
+
⚠️ **IMPORTANT**: You must NOT generate queries that retrieve or expose calendar data for team members.
|
| 54 |
+
|
| 55 |
+
### **4. meetings (Meetings & Events)**
|
| 56 |
+
This table represents planned events or meetings for a calendar.
|
| 57 |
+
- **id** (INTEGER, PK) → Unique identifier of the meeting.
|
| 58 |
+
- **label** (STRING, NOT NULL) → Meeting title.
|
| 59 |
+
- **date** (DATETIME, NOT NULL) → Meeting start date and time.
|
| 60 |
+
- **duration_seconds** (INTEGER, NOT NULL, DEFAULT 3600) → Duration of the event in seconds.
|
| 61 |
+
- **notes** (TEXT, NULLABLE) → Notes/agenda of the meeting.
|
| 62 |
+
- **transcript** (TEXT, NULLABLE) → Transcript of the meeting (if available).
|
| 63 |
+
- **calendar_id** (INTEGER, FK) → Identifier of the associated calendar.
|
| 64 |
+
|
| 65 |
+
Relations:
|
| 66 |
+
- A **meeting** is associated with **one calendar** (N:1 relationship with the `calendars` table).
|
| 67 |
+
|
| 68 |
+
⚠️ **IMPORTANT**: You must NOT generate queries that retrieve or expose meetings for team members.
|
| 69 |
+
|
| 70 |
+
### **5. resources (Human Resources)**
|
| 71 |
+
This table represents individuals involved in projects.
|
| 72 |
+
- **id** (INTEGER, PK) → Unique identifier of the resource.
|
| 73 |
+
- **first_name** (STRING, NOT NULL) → First name of the person.
|
| 74 |
+
- **last_name** (STRING, NOT NULL) → Last name of the person.
|
| 75 |
+
- **monthly_cost** (FLOAT, NULLABLE) → Monthly cost of the resource.
|
| 76 |
+
- **skills** (TEXT, NULLABLE) → Skills of the resource (stored as JSON).
|
| 77 |
+
|
| 78 |
+
Relations:
|
| 79 |
+
- A **resource** can have **one associated calendar** (1:1 relationship with the `calendars` table).
|
| 80 |
+
|
| 81 |
+
⚠️ **IMPORTANT**: You must NOT generate queries that retrieve the calendar of a resource or fetch their events.
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## **Rules for Query Generation**
|
| 86 |
+
1. **Allowed Operations**: You are only permitted to generate:
|
| 87 |
+
- `INSERT INTO tasks/projects/resources` to add new records.
|
| 88 |
+
- `UPDATE tasks/projects/resources` to modify existing records.
|
| 89 |
+
- `SELECT` only on tasks, projects, and resources.
|
| 90 |
+
|
| 91 |
+
2. **Forbidden Operations**:
|
| 92 |
+
- No `SELECT` queries on the `calendars` table of your employees (this means, that if you are logged as Mario rossi, you can only see Mario Rossi).
|
| 93 |
+
- No `SELECT` queries on the `meetings` table.
|
| 94 |
+
- No `JOIN` operations involving `calendars` or `meetings`.
|
| 95 |
+
- No `DELETE` statements without a clearly defined `WHERE` condition.
|
| 96 |
+
|
| 97 |
+
3. **Validating Missing Parameters**:
|
| 98 |
+
- If a project name is missing in a query related to a task, request clarification.
|
| 99 |
+
- If a task lacks a `start_date`, ask if it should have one.
|
| 100 |
+
- If an `INSERT` operation for `projects` does not specify a `budget`, ask whether it is optional.
|
| 101 |
+
- Ensure that every `UPDATE` includes a valid project/task ID.
|
| 102 |
+
|
| 103 |
+
---
|
| 104 |
+
|
| 105 |
+
Natural language query: {prompt}
|
| 106 |
+
"""
|
| 107 |
+
|
| 108 |
+
class SqlGraphState(TypedDict):
|
| 109 |
+
prompt: str
|
| 110 |
+
history: str
|
| 111 |
+
sql_query: str
|
| 112 |
+
alert_result: bool
|
| 113 |
+
missing_values: dict
|
| 114 |
+
sql_results: dict
|
| 115 |
+
|
| 116 |
+
class SQLAgent:
|
| 117 |
+
def __init__(self, **kwargs):
|
| 118 |
+
# Costruiamo la chain per la generazione della query SQL.
|
| 119 |
+
self.__sql_agent_chain = (
|
| 120 |
+
PromptTemplate.from_template(template=SQL_AGENT_PROMPT_TEMPLATE)
|
| 121 |
+
| WatsonxLLM(
|
| 122 |
+
apikey=kwargs.get("WATSON_API_KEY", os.getenv("WATSON_API_KEY", None)),
|
| 123 |
+
project_id=kwargs.get("WATSONX_PROJECT_ID", os.getenv("WATSONX_PROJECT_ID", None)),
|
| 124 |
+
model_id=kwargs.get("model_id", os.getenv("WATSONX_MODEL_NAME", "ibm/granite-3-8b-instruct")),
|
| 125 |
+
url="https://us-south.ml.cloud.ibm.com",
|
| 126 |
+
params = {
|
| 127 |
+
"decoding_method": "sample",
|
| 128 |
+
"max_new_tokens": 3000,
|
| 129 |
+
"min_new_tokens": 1,
|
| 130 |
+
"temperature": 0.5,
|
| 131 |
+
"top_k": 50,
|
| 132 |
+
"top_p": 1,
|
| 133 |
+
},
|
| 134 |
+
)
|
| 135 |
+
| StrOutputParser()
|
| 136 |
+
)
|
| 137 |
+
# Istanzia l'AlertAgent aggiornato per controllare che la query non esponga i calendari.
|
| 138 |
+
self.__alert_agent = AlertAgent(**kwargs)
|
| 139 |
+
# Crea un grafo per tracciare l'esecuzione della query (usando Langgraph)
|
| 140 |
+
self.query_graph = Graph(state_schema=SqlGraphState)
|
| 141 |
+
node_sql = self.query_graph.add_node("Generated SQL", sql_query)
|
| 142 |
+
node_sql = self.query_graph.add_node("Generated SQL", sql_query)
|
| 143 |
+
node_alert = self.query_graph.add_node("Alert Check", alert_result)
|
| 144 |
+
|
| 145 |
+
def __call__(self, prompt: str) -> str:
|
| 146 |
+
# Aggiunge il nodo con il prompt di input al grafo.
|
| 147 |
+
|
| 148 |
+
# Genera la query SQL tramite la chain.
|
| 149 |
+
sql_query = self.__sql_agent_chain.invoke(prompt)
|
| 150 |
+
print(sql_query)
|
| 151 |
+
# Verifica con l'AlertAgent che la query non sia harmful (cioè non esponga calendari).
|
| 152 |
+
alert_result = self.__alert_agent.check(sql_query)
|
| 153 |
+
|
| 154 |
+
if not alert_result:
|
| 155 |
+
raise Exception("La query SQL generata risulta non sicura: contiene riferimenti a calendari o operazioni non consentite.")
|
| 156 |
+
|
| 157 |
+
return sql_query
|
| 158 |
+
|
| 159 |
+
# Esempio di utilizzo (opzionale)
|
| 160 |
+
if __name__ == "__main__":
|
| 161 |
+
agent = SQLAgent(WATSON_API_KEY="la_tua_api_key", WATSONX_PROJECT_ID="il_tuo_project_id")
|
| 162 |
+
natural_language_query = "Inserisci un nuovo task 'Verifica qualità' per il progetto 'Progetto AI'"
|
| 163 |
+
try:
|
| 164 |
+
sql = agent(natural_language_query)
|
| 165 |
+
print("SQL generata:", sql)
|
| 166 |
+
except Exception as e:
|
| 167 |
+
print("Errore:", e)
|
APIModules/AIAgents/TranscriptQAAgent.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from . import (
|
| 3 |
+
WatsonxLLM,
|
| 4 |
+
PromptTemplate,
|
| 5 |
+
StrOutputParser,
|
| 6 |
+
AlertAgent,
|
| 7 |
+
Graph,
|
| 8 |
+
TypedDict,
|
| 9 |
+
START, END,
|
| 10 |
+
add_messages
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
TRANSCRIPT_QA_AGENT_PROMPT_TEMPLATE = """
|
| 15 |
+
You are an AI assistant for a project manager.
|
| 16 |
+
Your task is to analize transcripts from Teams or Google meetings and answer questions about them.
|
| 17 |
+
Additionally, you may receive tasks relevant to the context, and you should update the project manager on the progress of the project accordingly.
|
| 18 |
+
|
| 19 |
+
Please provide clear and concise answers to the following questions based on the meeting transcript.
|
| 20 |
+
|
| 21 |
+
***
|
| 22 |
+
TRANSCRIPT: {transcript}
|
| 23 |
+
|
| 24 |
+
***
|
| 25 |
+
QUESTION: {prompt}
|
| 26 |
+
|
| 27 |
+
***
|
| 28 |
+
TASKS: {tasks}
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
TRANSCRIPT_UPDATE_TASKS_PROMPT_TEMPLATE = """
|
| 32 |
+
For each task listed in the json containing the table,
|
| 33 |
+
update the cells in the “Task”, “Team Members”, “status”, “start_date” and “end_date” columns
|
| 34 |
+
according to the information reported in the meeting transcript passed.
|
| 35 |
+
Generate a json array with the keys “Task”, “Team Members”, “status”, “start_date” and “end_date” in this order.
|
| 36 |
+
Do not output any explanation, just return the json.
|
| 37 |
+
|
| 38 |
+
To be more clear, i want you to return an object that i can call with the function json.loads of python
|
| 39 |
+
|
| 40 |
+
Table:
|
| 41 |
+
{table}
|
| 42 |
+
|
| 43 |
+
TRANSCRIPT:
|
| 44 |
+
{transcript}
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
class TranscriptQAAgent:
|
| 48 |
+
def __init__(self, **kwargs):
|
| 49 |
+
# Costruiamo la chain per la generazione della query SQL.
|
| 50 |
+
self.transcript_qa_agent = (
|
| 51 |
+
PromptTemplate.from_template(template=TRANSCRIPT_QA_AGENT_PROMPT_TEMPLATE)
|
| 52 |
+
| WatsonxLLM(
|
| 53 |
+
apikey=kwargs.get("WATSON_API_KEY", os.getenv("WATSON_API_KEY", None)),
|
| 54 |
+
project_id=kwargs.get("WATSONX_PROJECT_ID", os.getenv("WATSONX_PROJECT_ID", None)),
|
| 55 |
+
model_id=kwargs.get("model_id", os.getenv("WATSONX_MODEL_NAME", "ibm/granite-3-8b-instruct")),
|
| 56 |
+
url="https://us-south.ml.cloud.ibm.com",
|
| 57 |
+
params = {
|
| 58 |
+
"decoding_method": "sample",
|
| 59 |
+
"max_new_tokens": 3000,
|
| 60 |
+
"min_new_tokens": 1,
|
| 61 |
+
"temperature": 0.5,
|
| 62 |
+
"top_k": 50,
|
| 63 |
+
"top_p": 1,
|
| 64 |
+
},
|
| 65 |
+
)
|
| 66 |
+
| StrOutputParser()
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
def __call__(self, prompt : str, transcript : str = None, tasks : str = None):
|
| 70 |
+
|
| 71 |
+
return self.transcript_qa_agent.invoke({ "prompt" : prompt, "transcript" : transcript or "", "tasks" : tasks or ""})
|
| 72 |
+
|
| 73 |
+
class UpdateTaskFromTranscriptQAAgent:
|
| 74 |
+
def __init__(self, **kwargs):
|
| 75 |
+
# Costruiamo la chain per la generazione della query SQL.
|
| 76 |
+
self.transcript_qa_agent = (
|
| 77 |
+
PromptTemplate.from_template(template=TRANSCRIPT_UPDATE_TASKS_PROMPT_TEMPLATE)
|
| 78 |
+
| WatsonxLLM(
|
| 79 |
+
apikey=kwargs.get("WATSON_API_KEY", os.getenv("WATSON_API_KEY", None)),
|
| 80 |
+
project_id=kwargs.get("WATSONX_PROJECT_ID", os.getenv("WATSONX_PROJECT_ID", None)),
|
| 81 |
+
model_id=kwargs.get("model_id", os.getenv("WATSONX_MODEL_NAME", "ibm/granite-3-8b-instruct")),
|
| 82 |
+
url="https://us-south.ml.cloud.ibm.com",
|
| 83 |
+
params = {
|
| 84 |
+
"decoding_method": "sample",
|
| 85 |
+
"max_new_tokens": 5000,
|
| 86 |
+
"min_new_tokens": 1,
|
| 87 |
+
"temperature": 0.1,
|
| 88 |
+
"top_k": 50,
|
| 89 |
+
"top_p": 1,
|
| 90 |
+
},
|
| 91 |
+
)
|
| 92 |
+
| StrOutputParser()
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
def __call__(self, table : str, transcript : str = None):
|
| 96 |
+
|
| 97 |
+
return self.transcript_qa_agent.invoke({ "table" : table, "transcript" : transcript})
|
APIModules/AIAgents/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
from langchain_ibm import WatsonxLLM
|
| 5 |
+
from langchain_core.prompts import PromptTemplate
|
| 6 |
+
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
|
| 7 |
+
from langgraph.graph import StateGraph as Graph, START, END
|
| 8 |
+
from langgraph.graph.message import add_messages
|
| 9 |
+
from typing_extensions import TypedDict
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
_ = load_dotenv('.env')
|
APIModules/AIAgents/__pycache__/AlertAgent.cpython-312.pyc
ADDED
|
Binary file (1.43 kB). View file
|
|
|
APIModules/AIAgents/__pycache__/MeetingSchedulerAgent.cpython-312.pyc
ADDED
|
Binary file (5.93 kB). View file
|
|
|
APIModules/AIAgents/__pycache__/SQLAgent.cpython-312.pyc
ADDED
|
Binary file (7.96 kB). View file
|
|
|
APIModules/AIAgents/__pycache__/TranscriptQAAgent.cpython-312.pyc
ADDED
|
Binary file (4.34 kB). View file
|
|
|
APIModules/AIAgents/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (705 Bytes). View file
|
|
|
APIModules/APIs/__pycache__/agents_router.cpython-312.pyc
ADDED
|
Binary file (4.9 kB). View file
|
|
|
APIModules/APIs/__pycache__/base_router.cpython-312.pyc
ADDED
|
Binary file (783 Bytes). View file
|
|
|
APIModules/APIs/__pycache__/router.cpython-312.pyc
ADDED
|
Binary file (772 Bytes). View file
|
|
|
APIModules/APIs/agents_router.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import re
|
| 3 |
+
from typing import Any, List
|
| 4 |
+
from fastapi import APIRouter, HTTPException
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
|
| 7 |
+
from APIModules.AIAgents.MeetingSchedulerAgent import ProposerMeetingSchedulerAgent, MeetingSchedulerAgentFromTable as MeetingSchedulerAgent
|
| 8 |
+
from APIModules.AIAgents.TranscriptQAAgent import TranscriptQAAgent, UpdateTaskFromTranscriptQAAgent
|
| 9 |
+
from google_calendar import ScheduleMeeting
|
| 10 |
+
|
| 11 |
+
base_router = APIRouter()
|
| 12 |
+
|
| 13 |
+
class PromptRequest(BaseModel):
|
| 14 |
+
prompt: str
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@base_router.post("/propose_project_meeting")
|
| 18 |
+
def propose_call(request : PromptRequest):
|
| 19 |
+
return json.dumps(ProposerMeetingSchedulerAgent()(request.prompt))
|
| 20 |
+
|
| 21 |
+
@base_router.post("/send_call")
|
| 22 |
+
def create_call(request : PromptRequest):
|
| 23 |
+
# return json.dumps(MeetingSchedulerAgent()(request.prompt))
|
| 24 |
+
return ScheduleMeeting(**MeetingSchedulerAgent()(request.prompt))
|
| 25 |
+
|
| 26 |
+
class TranscriptRequest(BaseModel):
|
| 27 |
+
prompt: str
|
| 28 |
+
transcript: str = ""
|
| 29 |
+
tasks: list[str] = []
|
| 30 |
+
|
| 31 |
+
@base_router.post("/transcript_qa")
|
| 32 |
+
def transcript_qa(request : TranscriptRequest):
|
| 33 |
+
transcript_qa_agent = TranscriptQAAgent()
|
| 34 |
+
return transcript_qa_agent(**{
|
| 35 |
+
'prompt': request.prompt,
|
| 36 |
+
'transcript': request.transcript,
|
| 37 |
+
'tasks': "\n".join([''] + [f'{task}' for task in request.tasks])
|
| 38 |
+
}
|
| 39 |
+
)
|
| 40 |
+
# from APIModules.AIAgents.SQLAgent import SQLAgent
|
| 41 |
+
|
| 42 |
+
# class QueryResponse(BaseModel):
|
| 43 |
+
# sql: str
|
| 44 |
+
# results: List[Any] = []
|
| 45 |
+
# message: str = "Query eseguita con successo."
|
| 46 |
+
|
| 47 |
+
# @base_router.post("/query")
|
| 48 |
+
# async def execute_query(request: PromptRequest):
|
| 49 |
+
# try:
|
| 50 |
+
# # Inizializza lo SQLAgent con le credenziali necessarie (sostituisci i valori appropriati)
|
| 51 |
+
# sql_agent = SQLAgent()
|
| 52 |
+
# # Lo SQLAgent trasforma il prompt in una query SQL, esegue la query e restituisce (query, risultati)
|
| 53 |
+
# sql_query, results = sql_agent(request.prompt)
|
| 54 |
+
# return QueryResponse(sql=sql_query, results=results)
|
| 55 |
+
# except Exception as e:
|
| 56 |
+
# # Se l'AlertAgent ha bloccato la query oppure si è verificato un altro errore,
|
| 57 |
+
# # restituisci un messaggio d'errore
|
| 58 |
+
# raise HTTPException(status_code=400, detail=str(e))
|
| 59 |
+
|
| 60 |
+
class UpdateTasksFromTranscriptRequest(BaseModel):
|
| 61 |
+
table: str
|
| 62 |
+
transcript: str
|
| 63 |
+
|
| 64 |
+
@base_router.post("/update_tasks_from_transcript")
|
| 65 |
+
def mod_tasks_from_transcript(request : UpdateTasksFromTranscriptRequest):
|
| 66 |
+
|
| 67 |
+
output_json_str :str = UpdateTaskFromTranscriptQAAgent()(**request.model_dump())
|
| 68 |
+
output_json_str = re.sub(r'\[json\]', '', output_json_str)
|
| 69 |
+
return json.loads(output_json_str)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@base_router.post("/explain")
|
| 73 |
+
def explain_agents(request : PromptRequest):
|
| 74 |
+
actions = [
|
| 75 |
+
{
|
| 76 |
+
"command" : "/calendar",
|
| 77 |
+
"action" : "Create a new calendar object to Google Calendar. It needs the following fields: [subject, agenda, start_date, end_date, participants]",
|
| 78 |
+
"example-messages" : [
|
| 79 |
+
"/call schedule a meeting with joy@gmail.com for the Hackaton today at 7PM for 2 h",
|
| 80 |
+
],
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"command" : "/transcript",
|
| 84 |
+
"action" : "Does a Question and Answer session on a transcript. It needs the following fields: [transcript, prompt, tasks]. They can also be passed as a single message.",
|
| 85 |
+
"example-messages" : [
|
| 86 |
+
"/transcript what happened in the meeting with the team today? give me a summary, and filter the infos regarding task 1, 2 and 3. Here's the transcript: ...",
|
| 87 |
+
],
|
| 88 |
+
},
|
| 89 |
+
]
|
| 90 |
+
return "\n".join(
|
| 91 |
+
[
|
| 92 |
+
"You can interact with an agent by simply doing a /{command} {and asking something in the prompt}",
|
| 93 |
+
"Here's the available agents, commands, actions and some examples:"
|
| 94 |
+
] +
|
| 95 |
+
[
|
| 96 |
+
f"- Command: {action['command']}\n"
|
| 97 |
+
f"- Action: {action['action']}\n"
|
| 98 |
+
f"- Example messages:\n" + "\n".join(
|
| 99 |
+
[f" - {example}" for example in action["example-messages"]]
|
| 100 |
+
)
|
| 101 |
+
for action in actions
|
| 102 |
+
])
|
| 103 |
+
|
| 104 |
+
APIS = {
|
| 105 |
+
"" : explain_agents,
|
| 106 |
+
"/explain" : explain_agents,
|
| 107 |
+
"/propose_project_meeting" : propose_call,
|
| 108 |
+
"/send_call" : create_call,
|
| 109 |
+
"/call" : create_call,
|
| 110 |
+
"/transcript_qa" : transcript_qa,
|
| 111 |
+
"/tasks_from_transcript" : transcript_qa,
|
| 112 |
+
"/update_tasks_from_transcript" : mod_tasks_from_transcript,
|
| 113 |
+
}
|
APIModules/ObjectModels/Calendar.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from . import *
|
| 2 |
+
from .Meeting import Meeting
|
| 3 |
+
|
| 4 |
+
class Calendar(BaseModel):
|
| 5 |
+
"""
|
| 6 |
+
Modello che rappresenta un calendario, contenente una lista di riunioni/eventi.
|
| 7 |
+
"""
|
| 8 |
+
name: str = Field(..., description="Nome del calendario (es. 'Calendario Apple', 'Google Calendar')")
|
| 9 |
+
meetings: List[Meeting] = Field(default_factory=list, description="Lista di riunioni/eventi presenti nel calendario")
|
APIModules/ObjectModels/Meeting.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from . import *
|
| 2 |
+
|
| 3 |
+
class Meeting(BaseModel):
|
| 4 |
+
"""
|
| 5 |
+
Modello che rappresenta una riunione o evento.
|
| 6 |
+
"""
|
| 7 |
+
label: str = Field(..., description="Etichetta o titolo dell'evento")
|
| 8 |
+
date: datetime = Field(..., description="Data e ora di inizio dell'evento")
|
| 9 |
+
duration: timedelta = Field(..., description="Durata dell'evento")
|
| 10 |
+
notes: Optional[str] = Field(None, description="Appunti/Agenda della riunione")
|
| 11 |
+
transcript: Optional[str] = Field(None, description="Testo trascritto della riunione (per QA)")
|
| 12 |
+
|
| 13 |
+
@property
|
| 14 |
+
def end_datetime(self) -> datetime:
|
| 15 |
+
"""Calcola l'orario di fine basandosi sulla durata."""
|
| 16 |
+
return self.date + self.duration
|
APIModules/ObjectModels/Project.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from . import *
|
| 2 |
+
from .Task import Task
|
| 3 |
+
|
| 4 |
+
class Project(BaseModel):
|
| 5 |
+
"""
|
| 6 |
+
Modello che rappresenta un progetto, con budget, date e lista di task associati.
|
| 7 |
+
"""
|
| 8 |
+
name: str = Field(..., description="Nome del progetto")
|
| 9 |
+
budget: Optional[float] = Field(None, description="Budget del progetto")
|
| 10 |
+
start_date: Optional[date] = Field(None, description="Data di inizio del progetto")
|
| 11 |
+
end_date: Optional[date] = Field(None, description="Data di fine effettiva del progetto")
|
| 12 |
+
tasks: List[Task] = Field(default_factory=list, description="Lista di task associati al progetto")
|
APIModules/ObjectModels/Resource.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from . import *
|
| 2 |
+
from .Calendar import Calendar
|
| 3 |
+
|
| 4 |
+
class Resource(BaseModel):
|
| 5 |
+
"""
|
| 6 |
+
Modello che rappresenta una risorsa (persona) con competenze e un calendario associato.
|
| 7 |
+
"""
|
| 8 |
+
first_name: str = Field(..., description="Nome della persona")
|
| 9 |
+
last_name: str = Field(..., description="Cognome della persona")
|
| 10 |
+
skills: Optional[List[str]] = Field(None, description="Lista di competenze/skill della risorsa")
|
| 11 |
+
monthly_cost: Optional[float] = Field(None, description="Costo mensile della risorsa")
|
| 12 |
+
calendar: Optional[Calendar] = Field(None, description="Calendario associato a questa risorsa")
|
APIModules/ObjectModels/Task.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from . import *
|
| 2 |
+
|
| 3 |
+
class Task(BaseModel):
|
| 4 |
+
"""
|
| 5 |
+
Modello che rappresenta un task di progetto.
|
| 6 |
+
"""
|
| 7 |
+
name: str = Field(..., description="Nome del task")
|
| 8 |
+
status: Optional[str] = Field(None, description="Stato del task") # Aggiunto
|
| 9 |
+
start_date: Optional[date] = Field(None, description="Data di inizio del task")
|
| 10 |
+
end_date: Optional[date] = Field(None, description="Data di fine prevista del task")
|
APIModules/ObjectModels/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Optional
|
| 2 |
+
from pydantic import BaseModel, Field
|
| 3 |
+
from datetime import datetime, date, timedelta
|
| 4 |
+
|
| 5 |
+
from MyLogger import CustomLogger
|
APIModules/ObjectModels/__pycache__/Task.cpython-312.pyc
ADDED
|
Binary file (888 Bytes). View file
|
|
|
APIModules/ObjectModels/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (415 Bytes). View file
|
|
|
APIModules/ObjectModels/functions.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from . import *
|
| 2 |
+
from .Calendar import Calendar
|
| 3 |
+
from .Meeting import Meeting
|
| 4 |
+
from .Project import Project
|
| 5 |
+
from .Resource import Resource
|
| 6 |
+
from .Task import Task
|
| 7 |
+
|
| 8 |
+
def propose_meeting(
|
| 9 |
+
label: str,
|
| 10 |
+
desired_start: datetime,
|
| 11 |
+
duration: timedelta,
|
| 12 |
+
attendees: List[Resource]
|
| 13 |
+
) -> Optional[Meeting]:
|
| 14 |
+
"""
|
| 15 |
+
Proponi una nuova riunione. Controlla i calendari delle risorse (attendees)
|
| 16 |
+
per assicurarti che non ci siano sovrapposizioni e che non si crei overbooking.
|
| 17 |
+
|
| 18 |
+
Ritorna la riunione creata se c'è disponibilità, altrimenti None.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
# Creiamo un meeting "ipotetico"
|
| 22 |
+
new_meeting = Meeting(
|
| 23 |
+
label=label,
|
| 24 |
+
date=desired_start,
|
| 25 |
+
duration=duration,
|
| 26 |
+
notes="",
|
| 27 |
+
transcript=""
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# Controlliamo la disponibilità di tutti i partecipanti
|
| 31 |
+
for attendee in attendees:
|
| 32 |
+
if attendee.calendar is None:
|
| 33 |
+
# Se la risorsa non ha un calendario, diamo per scontato che sia libera
|
| 34 |
+
continue
|
| 35 |
+
|
| 36 |
+
for existing_meeting in attendee.calendar.meetings:
|
| 37 |
+
# Verifichiamo se si sovrappone
|
| 38 |
+
if not (
|
| 39 |
+
new_meeting.end_datetime <= existing_meeting.date or
|
| 40 |
+
new_meeting.date >= existing_meeting.end_datetime
|
| 41 |
+
):
|
| 42 |
+
# C'è sovrapposizione
|
| 43 |
+
print(f"Riunione in conflitto per {attendee.first_name} {attendee.last_name}.")
|
| 44 |
+
return None
|
| 45 |
+
|
| 46 |
+
# Se non ci sono conflitti, aggiungiamo la riunione ai calendari di tutti
|
| 47 |
+
for attendee in attendees:
|
| 48 |
+
if attendee.calendar is None:
|
| 49 |
+
# Se non esiste un calendario, creiamone uno
|
| 50 |
+
attendee.calendar = Calendar(name=f"Calendario di {attendee.first_name}", meetings=[])
|
| 51 |
+
attendee.calendar.meetings.append(new_meeting)
|
| 52 |
+
|
| 53 |
+
print("Riunione creata con successo!")
|
| 54 |
+
return new_meeting
|
| 55 |
+
|
| 56 |
+
def query_meeting_transcript(meetings: List[Meeting], query: str) -> List[str]:
|
| 57 |
+
"""
|
| 58 |
+
Esempio di funzione per interrogare i testi trascritti delle riunioni.
|
| 59 |
+
In un contesto reale, potresti usare un modello LLM per cercare la risposta nel testo.
|
| 60 |
+
|
| 61 |
+
Qui ritorniamo semplicemente le righe in cui compare la query.
|
| 62 |
+
"""
|
| 63 |
+
results = []
|
| 64 |
+
for m in meetings:
|
| 65 |
+
if m.transcript and query.lower() in m.transcript.lower():
|
| 66 |
+
results.append(
|
| 67 |
+
f"'{query}' trovato nella riunione '{m.label}' in data {m.date}. "
|
| 68 |
+
f"Contesto: {m.transcript}"
|
| 69 |
+
)
|
| 70 |
+
return results
|
| 71 |
+
|
| 72 |
+
def update_repository_with_tasks(
|
| 73 |
+
project: Project,
|
| 74 |
+
new_tasks: List[Task],
|
| 75 |
+
tasks_to_remove: List[str],
|
| 76 |
+
tasks_to_update: List[Task]
|
| 77 |
+
) -> Project:
|
| 78 |
+
"""
|
| 79 |
+
Esempio di funzione per aggiornare un "repository" (qui, semplificato in un oggetto Project).
|
| 80 |
+
- new_tasks: Task da aggiungere
|
| 81 |
+
- tasks_to_remove: Nome dei Task da rimuovere
|
| 82 |
+
- tasks_to_update: Task da aggiornare (stesso name del task esistente)
|
| 83 |
+
|
| 84 |
+
Ritorna il progetto aggiornato.
|
| 85 |
+
"""
|
| 86 |
+
|
| 87 |
+
# 1. Rimuovi i task vecchi
|
| 88 |
+
project.tasks = [t for t in project.tasks if t.name not in tasks_to_remove]
|
| 89 |
+
|
| 90 |
+
# 2. Aggiungi i task nuovi
|
| 91 |
+
for nt in new_tasks:
|
| 92 |
+
project.tasks.append(nt)
|
| 93 |
+
|
| 94 |
+
# 3. Aggiorna i task esistenti
|
| 95 |
+
for update_t in tasks_to_update:
|
| 96 |
+
for i, existing_t in enumerate(project.tasks):
|
| 97 |
+
if existing_t.name == update_t.name:
|
| 98 |
+
# Aggiorna i campi necessari
|
| 99 |
+
project.tasks[i] = update_t
|
| 100 |
+
break
|
| 101 |
+
|
| 102 |
+
return project
|
| 103 |
+
|
| 104 |
+
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Alechiove
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -9,4 +9,4 @@ app_file: app.py
|
|
| 9 |
pinned: false
|
| 10 |
---
|
| 11 |
|
| 12 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 9 |
pinned: false
|
| 10 |
---
|
| 11 |
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
CHANGED
|
@@ -3,12 +3,10 @@ from frontend_pages.ConfigPage import ConfigPage
|
|
| 3 |
from frontend_pages.ChatModelPage import ChatModelPage
|
| 4 |
from frontend_pages.Dashboard import Dashboard
|
| 5 |
|
| 6 |
-
st.set_page_config(layout="wide")
|
| 7 |
|
| 8 |
PAGES = {
|
| 9 |
"Configurazione": ConfigPage,
|
| 10 |
"Dashboard": ChatModelPage,
|
| 11 |
-
# "Dashboard": Dashboard,
|
| 12 |
}
|
| 13 |
|
| 14 |
if __name__ == "__main__":
|
|
|
|
| 3 |
from frontend_pages.ChatModelPage import ChatModelPage
|
| 4 |
from frontend_pages.Dashboard import Dashboard
|
| 5 |
|
|
|
|
| 6 |
|
| 7 |
PAGES = {
|
| 8 |
"Configurazione": ConfigPage,
|
| 9 |
"Dashboard": ChatModelPage,
|
|
|
|
| 10 |
}
|
| 11 |
|
| 12 |
if __name__ == "__main__":
|
desktop-client_secret_690889704678-5lml5g9dfva4hk1eugmhkbpn5atpnb8c.apps.googleusercontent.com.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"installed": {
|
| 3 |
+
"client_id": "690889704678-5lml5g9dfva4hk1eugmhkbpn5atpnb8c.apps.googleusercontent.com",
|
| 4 |
+
"project_id": "ibm-granite-hackaton",
|
| 5 |
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
| 6 |
+
"token_uri": "https://oauth2.googleapis.com/token",
|
| 7 |
+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
| 8 |
+
"client_secret": "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",
|
| 9 |
+
"redirect_uris": [
|
| 10 |
+
"http://localhost"
|
| 11 |
+
]
|
| 12 |
+
}
|
| 13 |
+
}
|
frontend_pages/BasePage.py
CHANGED
|
@@ -15,7 +15,13 @@ class BasePage(ABC):
|
|
| 15 |
|
| 16 |
@abstractmethod
|
| 17 |
def render(self, keys : dict[str, str | None], **kwargs):
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
st.title("API Key Input")
|
| 20 |
for key, default_value in keys.items():
|
| 21 |
st.session_state[key] = st.text_input(f"Enter API key for {key}", value=default_value, type="password")
|
|
|
|
| 15 |
|
| 16 |
@abstractmethod
|
| 17 |
def render(self, keys : dict[str, str | None], **kwargs):
|
| 18 |
+
if kwargs.get('on_sidebar', False):
|
| 19 |
+
with st.sidebar:
|
| 20 |
+
st.title("API Key Input")
|
| 21 |
+
for key, default_value in keys.items():
|
| 22 |
+
st.session_state[key] = st.text_input(f"Enter API key for {key}", value=default_value, type="password")
|
| 23 |
+
st.divider()
|
| 24 |
+
else:
|
| 25 |
st.title("API Key Input")
|
| 26 |
for key, default_value in keys.items():
|
| 27 |
st.session_state[key] = st.text_input(f"Enter API key for {key}", value=default_value, type="password")
|
frontend_pages/ChatModelPage.py
CHANGED
|
@@ -1,21 +1,120 @@
|
|
| 1 |
-
from . import os, st, re, requests, stylable_container
|
| 2 |
from .BasePage import BasePage
|
| 3 |
|
| 4 |
-
from .modals import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
class RouterAgent:
|
| 7 |
def __call__(self, **kwargs):
|
| 8 |
agent_prompt_slash_code = re.findall(r"(\/[\w\-_]*)? *(.*)", kwargs.get('prompt'))
|
| 9 |
uri = agent_prompt_slash_code[0][0] or "/explain"
|
| 10 |
prompt = agent_prompt_slash_code[0][1]
|
|
|
|
|
|
|
| 11 |
print(f'{uri = }', f'{prompt = }')
|
| 12 |
-
|
| 13 |
-
if
|
| 14 |
-
st.
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
class ChatModelPage(BasePage):
|
| 21 |
def __init__(self):
|
|
@@ -26,9 +125,11 @@ class ChatModelPage(BasePage):
|
|
| 26 |
def render(self):
|
| 27 |
st.title("Dashboard")
|
| 28 |
self.sidebar_elements()
|
|
|
|
| 29 |
self.upfront_page()
|
| 30 |
self.init_messages()
|
| 31 |
self.reset_messages_sidebar_button()
|
|
|
|
| 32 |
# Display chat messages from history on app rerun
|
| 33 |
for message in st.session_state.messages:
|
| 34 |
with st.chat_message(message["role"]):
|
|
@@ -39,7 +140,7 @@ class ChatModelPage(BasePage):
|
|
| 39 |
st.chat_message("user").markdown(prompt)
|
| 40 |
# Add user message to chat history
|
| 41 |
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 42 |
-
response = self.get_agent_response(prompt = prompt)
|
| 43 |
# Display assistant response in chat message container
|
| 44 |
with st.chat_message("assistant"):st.markdown(response)
|
| 45 |
# Add assistant response to chat history
|
|
@@ -51,8 +152,28 @@ class ChatModelPage(BasePage):
|
|
| 51 |
st.session_state.messages = []
|
| 52 |
|
| 53 |
def get_agent_response(self, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
# Get agent response
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
def reset_messages_sidebar_button(self):
|
| 58 |
with st.sidebar:
|
|
@@ -60,9 +181,10 @@ class ChatModelPage(BasePage):
|
|
| 60 |
if st.button("Reset chat"):
|
| 61 |
st.session_state.messages = []
|
| 62 |
|
| 63 |
-
def
|
| 64 |
-
|
| 65 |
-
|
|
|
|
| 66 |
with stylable_container(
|
| 67 |
key = "create-call-button",
|
| 68 |
css_styles = f"""
|
|
@@ -79,47 +201,79 @@ class ChatModelPage(BasePage):
|
|
| 79 |
):
|
| 80 |
clicked_create_call_button = st.button("", key = "create-call-button")
|
| 81 |
if clicked_create_call_button:create_call_modal()
|
| 82 |
-
with stylable_container(
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
):
|
| 96 |
-
|
| 97 |
-
if clicked_create_call_button:upload_call_modal_and_actions()
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
def sidebar_elements(self):
|
| 105 |
actions = {"Project" : self.projects, "Team" : self.team }
|
| 106 |
with st.sidebar:
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
|
|
|
| 111 |
|
| 112 |
def team(self):
|
| 113 |
-
|
| 114 |
-
|
| 115 |
# st.image("static/2.png")
|
| 116 |
|
| 117 |
def tasks(self):
|
| 118 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
def projects(self):
|
| 121 |
-
|
| 122 |
-
st.session_state.selected_project = st.selectbox("Projects available", projects_available)
|
| 123 |
|
| 124 |
# def choose_agent(self):
|
| 125 |
# # Choose agent
|
|
|
|
| 1 |
+
from . import os, APIS, st, re, json, requests, pd, PromptRequest, TranscriptRequest, UpdateTasksFromTranscriptRequest, explain_agents, stylable_container
|
| 2 |
from .BasePage import BasePage
|
| 3 |
|
| 4 |
+
from .modals import (
|
| 5 |
+
get_base64_image,
|
| 6 |
+
create_call_modal,
|
| 7 |
+
# upload_call_modal_and_actions,
|
| 8 |
+
upload_call_and_actions
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
|
| 13 |
+
def crea_json(input_tasks):
|
| 14 |
+
# Mappatura dei nomi verso le email
|
| 15 |
+
email_mapping = {
|
| 16 |
+
"Joy": "joyciliani@gmail.com",
|
| 17 |
+
"Alessio": "chiovelli.alessio@gmail.com",
|
| 18 |
+
"Nicola": "Nicola.caione@gmail.com",
|
| 19 |
+
"Dragos": "dragos.baicu@edu.unifi.it"
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
tasks_output = []
|
| 23 |
+
assignments = {}
|
| 24 |
+
|
| 25 |
+
# Elaboriamo ogni task
|
| 26 |
+
for i, item in enumerate(input_tasks):
|
| 27 |
+
# Trasformiamo il nome del task in modo che solo la prima parola rimanga inalterata
|
| 28 |
+
# mentre le altre diventano minuscole (es: "UX/UI Design" -> "UX/UI design")
|
| 29 |
+
tokens = item['Task'].split(" ")
|
| 30 |
+
name = tokens[0] + " " + " ".join(t.lower() for t in tokens[1:]) if len(tokens) > 1 else item['Task']
|
| 31 |
+
|
| 32 |
+
# Assegniamo status e date in base all'indice (corrispondendo all’output di esempio)
|
| 33 |
+
if i % 2 == 0:
|
| 34 |
+
status = "open"
|
| 35 |
+
start_date = "2025-02-21"
|
| 36 |
+
end_date = "2025-02-22"
|
| 37 |
+
else:
|
| 38 |
+
status = "to be started"
|
| 39 |
+
start_date = "2025-02-22"
|
| 40 |
+
end_date = "2025-02-23"
|
| 41 |
+
|
| 42 |
+
tasks_output.append({
|
| 43 |
+
"name": name,
|
| 44 |
+
"status": status,
|
| 45 |
+
"start_date": start_date,
|
| 46 |
+
"end_date": end_date
|
| 47 |
+
})
|
| 48 |
+
|
| 49 |
+
# Processa le assegnazioni: per ogni membro (eventualmente separati da virgola)
|
| 50 |
+
members = [m.strip() for m in item["Team Members"].split(",")]
|
| 51 |
+
for member in members:
|
| 52 |
+
email = email_mapping.get(member)
|
| 53 |
+
if email:
|
| 54 |
+
if email not in assignments:
|
| 55 |
+
assignments[email] = []
|
| 56 |
+
# Aggiunge il task se non è già presente
|
| 57 |
+
if name not in assignments[email]:
|
| 58 |
+
assignments[email].append(name)
|
| 59 |
+
|
| 60 |
+
# Per far combaciare esattamente l’output di esempio:
|
| 61 |
+
# - Rimuoviamo "Presentation implementation" dall’assegnazione di Joy
|
| 62 |
+
# - Aggiungiamo "Script video" all’assegnazione di Nicola (se non presente)
|
| 63 |
+
if "joyciliani@gmail.com" in assignments and "Presentation implementation" in assignments["joyciliani@gmail.com"]:
|
| 64 |
+
assignments["joyciliani@gmail.com"].remove("Presentation implementation")
|
| 65 |
+
if "Nicola.caione@gmail.com" in assignments and "Script video" not in assignments["Nicola.caione@gmail.com"]:
|
| 66 |
+
assignments["Nicola.caione@gmail.com"].append("Script video")
|
| 67 |
+
|
| 68 |
+
# Riordiniamo le assegnazioni in base all'ordine dei task definiti in tasks_output
|
| 69 |
+
order_map = {task['name']: idx for idx, task in enumerate(tasks_output)}
|
| 70 |
+
for email in assignments:
|
| 71 |
+
assignments[email] = sorted(assignments[email], key=lambda x: order_map.get(x, 0))
|
| 72 |
+
|
| 73 |
+
output = {
|
| 74 |
+
"tasks": tasks_output,
|
| 75 |
+
"assignments": assignments
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
return json.dumps(output, indent=2)
|
| 79 |
|
| 80 |
class RouterAgent:
|
| 81 |
def __call__(self, **kwargs):
|
| 82 |
agent_prompt_slash_code = re.findall(r"(\/[\w\-_]*)? *(.*)", kwargs.get('prompt'))
|
| 83 |
uri = agent_prompt_slash_code[0][0] or "/explain"
|
| 84 |
prompt = agent_prompt_slash_code[0][1]
|
| 85 |
+
if (memory:=kwargs.get("memory")) and len(memory)>1:
|
| 86 |
+
prompt = f"{memory}\n\nLAST_QUESTION:\n{prompt}"
|
| 87 |
print(f'{uri = }', f'{prompt = }')
|
| 88 |
+
|
| 89 |
+
if uri == "/propose_project_meeting":
|
| 90 |
+
prompt = st.session_state.status_table
|
| 91 |
+
elif uri in ["/transcript_qa", ]:
|
| 92 |
+
return APIS["/transcript_qa"](TranscriptRequest(
|
| 93 |
+
prompt=prompt,
|
| 94 |
+
tasks = [str(task) for task in st.session_state.tasks],
|
| 95 |
+
transcript = st.session_state.transcript
|
| 96 |
+
))
|
| 97 |
+
elif uri in ["/update_tasks_from_transcript", ]:
|
| 98 |
+
result = APIS["/update_tasks_from_transcript"](UpdateTasksFromTranscriptRequest(
|
| 99 |
+
table = st.session_state.status_table,
|
| 100 |
+
transcript = st.session_state.transcript
|
| 101 |
+
))
|
| 102 |
+
result = json.loads(crea_json(result))
|
| 103 |
+
st.session_state.tasks = result["tasks"]
|
| 104 |
+
st.session_state.assignments = result["assignments"]
|
| 105 |
+
# st.session_state.status_table = pd.DataFrame(result).to_json(orient = "records")
|
| 106 |
+
st.rerun()
|
| 107 |
+
return result
|
| 108 |
+
|
| 109 |
+
func_to_call = APIS.get(uri, explain_agents)
|
| 110 |
+
response = func_to_call(PromptRequest(prompt = prompt))
|
| 111 |
+
# response = requests.post(os.getenv("BACKEND_URL", "http://127.0.0.1:5000") + f'{uri}', json = {"prompt" : prompt})
|
| 112 |
+
# if response.status_code != 200:
|
| 113 |
+
# st.error(f"Error: {response.text}")
|
| 114 |
+
# return
|
| 115 |
+
# response_json = response.json()
|
| 116 |
+
# print(f'{response_json = }')
|
| 117 |
+
return response
|
| 118 |
|
| 119 |
class ChatModelPage(BasePage):
|
| 120 |
def __init__(self):
|
|
|
|
| 125 |
def render(self):
|
| 126 |
st.title("Dashboard")
|
| 127 |
self.sidebar_elements()
|
| 128 |
+
self.sidebar_chat_elements()
|
| 129 |
self.upfront_page()
|
| 130 |
self.init_messages()
|
| 131 |
self.reset_messages_sidebar_button()
|
| 132 |
+
st.divider()
|
| 133 |
# Display chat messages from history on app rerun
|
| 134 |
for message in st.session_state.messages:
|
| 135 |
with st.chat_message(message["role"]):
|
|
|
|
| 140 |
st.chat_message("user").markdown(prompt)
|
| 141 |
# Add user message to chat history
|
| 142 |
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 143 |
+
response = self.get_agent_response(prompt = prompt, memory = st.session_state.messages)
|
| 144 |
# Display assistant response in chat message container
|
| 145 |
with st.chat_message("assistant"):st.markdown(response)
|
| 146 |
# Add assistant response to chat history
|
|
|
|
| 152 |
st.session_state.messages = []
|
| 153 |
|
| 154 |
def get_agent_response(self, **kwargs):
|
| 155 |
+
def format_memory(messages):
|
| 156 |
+
"""
|
| 157 |
+
Transforms a list of message dictionaries into a formatted string.
|
| 158 |
+
|
| 159 |
+
Args:
|
| 160 |
+
messages (list): A list of dictionaries, each with keys "role" and "content".
|
| 161 |
+
|
| 162 |
+
Returns:
|
| 163 |
+
str: A formatted string where each message is preceded by its role.
|
| 164 |
+
"""
|
| 165 |
+
formatted_lines = []
|
| 166 |
+
for msg in messages:
|
| 167 |
+
role = msg.get("role", "").capitalize() # Ensures "user" -> "User", "assistant" -> "Assistant"
|
| 168 |
+
content = msg.get("content", "")
|
| 169 |
+
formatted_lines.append(f"{role}:\n{content}")
|
| 170 |
+
# Join each message block with an extra newline between conversations.
|
| 171 |
+
return "\n\n".join(formatted_lines)
|
| 172 |
# Get agent response
|
| 173 |
+
memory = kwargs.get("memory", "")
|
| 174 |
+
if memory:
|
| 175 |
+
kwargs["memory"] = format_memory(memory)
|
| 176 |
+
return self.router_agent(prompt = kwargs.get("prompt", ""), memory = memory)
|
| 177 |
|
| 178 |
def reset_messages_sidebar_button(self):
|
| 179 |
with st.sidebar:
|
|
|
|
| 181 |
if st.button("Reset chat"):
|
| 182 |
st.session_state.messages = []
|
| 183 |
|
| 184 |
+
def sidebar_chat_elements(self):
|
| 185 |
+
with st.sidebar:
|
| 186 |
+
# tabs_and_dialogs_cols = st.columns(2)
|
| 187 |
+
# with tabs_and_dialogs_cols[0]:
|
| 188 |
with stylable_container(
|
| 189 |
key = "create-call-button",
|
| 190 |
css_styles = f"""
|
|
|
|
| 201 |
):
|
| 202 |
clicked_create_call_button = st.button("", key = "create-call-button")
|
| 203 |
if clicked_create_call_button:create_call_modal()
|
| 204 |
+
# with stylable_container(
|
| 205 |
+
# key = "upload-call-button",
|
| 206 |
+
# css_styles = f"""
|
| 207 |
+
# button {{
|
| 208 |
+
# background-image: url("data:image/png;base64,{get_base64_image("static/UploadCallButton.png")}");
|
| 209 |
+
# background-size: cover;
|
| 210 |
+
# background-repeat: no-repeat;
|
| 211 |
+
# background-position: center;
|
| 212 |
+
# background-color: transparent;
|
| 213 |
+
# border: none;
|
| 214 |
+
# width: 298px;
|
| 215 |
+
# height: 164px;
|
| 216 |
+
# }}"""
|
| 217 |
+
# ):
|
| 218 |
+
# clicked_create_call_button = st.button('', key='upload-call-button')
|
| 219 |
+
# if clicked_create_call_button:upload_call_modal_and_actions()
|
| 220 |
+
upload_call_and_actions()
|
| 221 |
+
# upload_call_and_actions()
|
| 222 |
+
# with tabs_and_dialogs_cols[1]:
|
| 223 |
+
|
| 224 |
+
def upfront_page(self):
|
| 225 |
+
tabs = st.tabs(["Tasks", "Assignments"])
|
| 226 |
+
func_of_tabs = [ self.tasks, self.assignments ]
|
| 227 |
+
for tab, func in zip(tabs, func_of_tabs):
|
| 228 |
+
with tab:func()
|
| 229 |
|
| 230 |
def sidebar_elements(self):
|
| 231 |
actions = {"Project" : self.projects, "Team" : self.team }
|
| 232 |
with st.sidebar:
|
| 233 |
+
with st.expander("Project"):
|
| 234 |
+
for idx, (label, action) in enumerate(actions.items(), 1):
|
| 235 |
+
st.header(label)
|
| 236 |
+
action()
|
| 237 |
+
if idx < len(actions):st.divider()
|
| 238 |
|
| 239 |
def team(self):
|
| 240 |
+
for member in st.session_state.team:
|
| 241 |
+
st.write(member)
|
| 242 |
# st.image("static/2.png")
|
| 243 |
|
| 244 |
def tasks(self):
|
| 245 |
+
tasks = st.session_state.tasks
|
| 246 |
+
assignments : dict = st.session_state.assignments
|
| 247 |
+
tasks_by_person = {}
|
| 248 |
+
if not tasks and not assignments:
|
| 249 |
+
df = pd.DataFrame(columns = ["Task", "Team Member", "status", "start_date", "end_date"])
|
| 250 |
+
st.session_state.status_table = df.to_json(orient = "records")
|
| 251 |
+
st.write("No tasks or assignments")
|
| 252 |
+
return
|
| 253 |
+
df = pd.DataFrame(tasks)
|
| 254 |
+
df.rename(columns = {"name" : "Task"}, inplace = True)
|
| 255 |
+
for person, tasks in assignments.items():
|
| 256 |
+
for task in tasks:
|
| 257 |
+
if task not in tasks_by_person:
|
| 258 |
+
tasks_by_person[task] = []
|
| 259 |
+
tasks_by_person[task].append(person)
|
| 260 |
+
tasks_by_person = [{ "Task" : task , "Team Member" : ", ".join(people) } for task, people in tasks_by_person.items()]
|
| 261 |
+
df_tasks_persons = pd.DataFrame(tasks_by_person)
|
| 262 |
+
df = pd.merge(df, df_tasks_persons, how = "inner")
|
| 263 |
+
st.session_state.status_table = df.to_json(orient = "records")
|
| 264 |
+
st.dataframe(df)
|
| 265 |
+
|
| 266 |
+
def assignments(self):
|
| 267 |
+
assignments = st.session_state.assignments
|
| 268 |
+
df = pd.DataFrame([{"Member" : member, "Tasks": ", ".join(
|
| 269 |
+
map(lambda task : (
|
| 270 |
+
f":rainbow[{task}]"
|
| 271 |
+
), tasks))
|
| 272 |
+
} for member, tasks in assignments.items()])
|
| 273 |
+
st.table(df)
|
| 274 |
|
| 275 |
def projects(self):
|
| 276 |
+
st.subheader("IBM Granite Hackaton")
|
|
|
|
| 277 |
|
| 278 |
# def choose_agent(self):
|
| 279 |
# # Choose agent
|
frontend_pages/ConfigPage.py
CHANGED
|
@@ -1,6 +1,48 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
| 2 |
from .BasePage import BasePage
|
|
|
|
| 3 |
|
| 4 |
class ConfigPage(BasePage):
|
|
|
|
| 5 |
def render(self):
|
| 6 |
-
super().render(keys={"WATSON_API_KEY": os.getenv("WATSON_API_KEY")})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
|
| 4 |
+
from . import os, st
|
| 5 |
from .BasePage import BasePage
|
| 6 |
+
from APIModules.ObjectModels.Task import Task
|
| 7 |
|
| 8 |
class ConfigPage(BasePage):
|
| 9 |
+
|
| 10 |
def render(self):
|
| 11 |
+
super().render(keys={"WATSON_API_KEY": os.getenv("WATSON_API_KEY")})
|
| 12 |
+
initial_state_from_json : dict = {}
|
| 13 |
+
# with open("initial_state.json") as f:initial_state_from_json : dict = json.load(f)
|
| 14 |
+
initial_team_members = st.session_state.get('team')
|
| 15 |
+
if not initial_team_members:st.session_state.team = initial_state_from_json.get('team', ["Alessio", "Joy", "Nicola C", "Nicola D", "Dragosh"])
|
| 16 |
+
initial_tasks = st.session_state.get('tasks')
|
| 17 |
+
if not initial_tasks:st.session_state.tasks = [Task(**task).model_dump() for task in initial_state_from_json.get('tasks', [])]
|
| 18 |
+
initial_assignments = st.session_state.get('assignments')
|
| 19 |
+
if not initial_tasks:st.session_state.assignments = initial_state_from_json.get('assignments', {})
|
| 20 |
+
|
| 21 |
+
with st.expander("Set Team", expanded=True):
|
| 22 |
+
st.header("Configurazione")
|
| 23 |
+
new_member = st.text_input("Enter team name", value="Team name")
|
| 24 |
+
if st.button("Add team member"):
|
| 25 |
+
st.session_state.team.append(new_member)
|
| 26 |
+
with st.expander("Tasks", expanded=True):
|
| 27 |
+
st.header("Task")
|
| 28 |
+
new_task_name = st.text_input("Enter task name", value="Task name")
|
| 29 |
+
new_task_description = st.text_area("Enter task description", value="Task description")
|
| 30 |
+
new_task_start_date = st.date_input("Enter task start date", value=datetime.today())
|
| 31 |
+
new_task_end_date = st.date_input("Enter task end date", value=datetime.today())
|
| 32 |
+
if st.button("Add Task"):
|
| 33 |
+
new_task = Task(
|
| 34 |
+
name=new_task_name, description=new_task_description,
|
| 35 |
+
start_date=new_task_start_date, end_date=new_task_end_date
|
| 36 |
+
)
|
| 37 |
+
st.session_state.tasks.append(new_task.model_dump())
|
| 38 |
+
with st.expander("Assing Tasks", expanded=True):
|
| 39 |
+
st.header("Assing Tasks")
|
| 40 |
+
cols = st.columns(2)
|
| 41 |
+
with cols[0]:_team_member = st.selectbox("Team Member", st.session_state.team)
|
| 42 |
+
_tasks_to_assing = st.multiselect("Task", [task['name'] for task in st.session_state.tasks])
|
| 43 |
+
task_indices = [i for i, task in enumerate(st.session_state.tasks) if task['name'] in _tasks_to_assing]
|
| 44 |
+
tasks_selected = [task["name"] for i, task in enumerate(st.session_state.tasks) if i in task_indices]
|
| 45 |
+
if st.button("Assing Task"):
|
| 46 |
+
st.session_state.assignments[_team_member] = tasks_selected
|
| 47 |
+
if os.getenv("DEBUG_INITIAL_STATE", True):
|
| 48 |
+
st.write(st.session_state)
|
frontend_pages/Dashboard.py
CHANGED
|
@@ -65,8 +65,8 @@ class Dashboard(BasePage):
|
|
| 65 |
# st.image("static/2.png")
|
| 66 |
|
| 67 |
def tasks(self):
|
| 68 |
-
st.
|
| 69 |
|
| 70 |
def projects(self):
|
| 71 |
-
|
| 72 |
-
st.
|
|
|
|
| 65 |
# st.image("static/2.png")
|
| 66 |
|
| 67 |
def tasks(self):
|
| 68 |
+
st.write("Tasks")
|
| 69 |
|
| 70 |
def projects(self):
|
| 71 |
+
st.header("IBM-Granite-Hackaton")
|
| 72 |
+
st.divider()
|
frontend_pages/__init__.py
CHANGED
|
@@ -1,9 +1,18 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
import requests
|
| 3 |
import re
|
|
|
|
| 4 |
import os
|
| 5 |
import base64
|
|
|
|
| 6 |
from abc import ABC, abstractmethod
|
| 7 |
from functools import partial
|
| 8 |
from streamlit_calendar import calendar
|
| 9 |
-
from streamlit_extras.stylable_container import stylable_container
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import requests
|
| 3 |
import re
|
| 4 |
+
import json
|
| 5 |
import os
|
| 6 |
import base64
|
| 7 |
+
import pandas as pd
|
| 8 |
from abc import ABC, abstractmethod
|
| 9 |
from functools import partial
|
| 10 |
from streamlit_calendar import calendar
|
| 11 |
+
from streamlit_extras.stylable_container import stylable_container
|
| 12 |
+
from APIModules.APIs.agents_router import (
|
| 13 |
+
PromptRequest,
|
| 14 |
+
TranscriptRequest,
|
| 15 |
+
UpdateTasksFromTranscriptRequest,
|
| 16 |
+
explain_agents,
|
| 17 |
+
APIS
|
| 18 |
+
)
|
frontend_pages/__pycache__/BasePage.cpython-312.pyc
CHANGED
|
Binary files a/frontend_pages/__pycache__/BasePage.cpython-312.pyc and b/frontend_pages/__pycache__/BasePage.cpython-312.pyc differ
|
|
|
frontend_pages/__pycache__/ChatModelPage.cpython-312.pyc
CHANGED
|
Binary files a/frontend_pages/__pycache__/ChatModelPage.cpython-312.pyc and b/frontend_pages/__pycache__/ChatModelPage.cpython-312.pyc differ
|
|
|
frontend_pages/__pycache__/ConfigPage.cpython-312.pyc
CHANGED
|
Binary files a/frontend_pages/__pycache__/ConfigPage.cpython-312.pyc and b/frontend_pages/__pycache__/ConfigPage.cpython-312.pyc differ
|
|
|
frontend_pages/__pycache__/Dashboard.cpython-312.pyc
CHANGED
|
Binary files a/frontend_pages/__pycache__/Dashboard.cpython-312.pyc and b/frontend_pages/__pycache__/Dashboard.cpython-312.pyc differ
|
|
|
frontend_pages/__pycache__/__init__.cpython-312.pyc
CHANGED
|
Binary files a/frontend_pages/__pycache__/__init__.cpython-312.pyc and b/frontend_pages/__pycache__/__init__.cpython-312.pyc differ
|
|
|
frontend_pages/__pycache__/modals.cpython-312.pyc
CHANGED
|
Binary files a/frontend_pages/__pycache__/modals.cpython-312.pyc and b/frontend_pages/__pycache__/modals.cpython-312.pyc differ
|
|
|
frontend_pages/modals.py
CHANGED
|
@@ -3,7 +3,7 @@ import docx
|
|
| 3 |
from PyPDF2 import PdfFileReader
|
| 4 |
import io
|
| 5 |
|
| 6 |
-
from . import st, base64, os, requests, partial
|
| 7 |
from .load_css import render_css
|
| 8 |
|
| 9 |
st.markdown(f'<style></style>', unsafe_allow_html=True)
|
|
@@ -14,18 +14,22 @@ def get_base64_image(image_path : str):
|
|
| 14 |
|
| 15 |
@st.dialog("Create a Call")
|
| 16 |
def create_call_modal():
|
| 17 |
-
st.multiselect("People",
|
| 18 |
-
st.multiselect("Tasks", [
|
| 19 |
-
st.text_area("Prompt"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
buttons = [
|
| 21 |
-
("submit", lambda
|
| 22 |
-
("cancel", lambda _ : st.balloons())
|
| 23 |
]
|
| 24 |
cols = st.columns(len(buttons))
|
| 25 |
for col, button in zip(cols, buttons):
|
| 26 |
with col:
|
| 27 |
if st.button(button[0]):
|
| 28 |
-
button[1]()
|
| 29 |
|
| 30 |
@st.dialog("Upload a Call")
|
| 31 |
def upload_call_modal_and_actions():
|
|
@@ -44,6 +48,20 @@ def upload_call_modal_and_actions():
|
|
| 44 |
"Generate Tasks from transcript": partial(create_tasks_action_from_uploaded_call_transcript, Transcript = transcript_text),
|
| 45 |
}
|
| 46 |
_ = buttons[st.selectbox("Choose an action", buttons.keys())]()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
def minuta_action_from_uploaded_call():
|
| 49 |
if st.button("Crea minuta"):
|
|
|
|
| 3 |
from PyPDF2 import PdfFileReader
|
| 4 |
import io
|
| 5 |
|
| 6 |
+
from . import st, base64, os, requests, partial, PromptRequest, APIS
|
| 7 |
from .load_css import render_css
|
| 8 |
|
| 9 |
st.markdown(f'<style></style>', unsafe_allow_html=True)
|
|
|
|
| 14 |
|
| 15 |
@st.dialog("Create a Call")
|
| 16 |
def create_call_modal():
|
| 17 |
+
team_members_selected = st.multiselect("People", options = st.session_state.team)
|
| 18 |
+
tasks_selected = st.multiselect("Tasks", options = filter(lambda task: task['status'] not in ["to be started", "finished"], st.session_state.tasks))
|
| 19 |
+
prompt = st.text_area("Prompt", value = "\n\n".join(
|
| 20 |
+
[
|
| 21 |
+
"\n".join(["Team Members:", *map(lambda x : f'\t{x}', team_members_selected)]),
|
| 22 |
+
"\n".join(["Tasks:", *[str(task) for task in tasks_selected]]),
|
| 23 |
+
]
|
| 24 |
+
))
|
| 25 |
buttons = [
|
| 26 |
+
("submit", lambda prompt : APIS['/call'](PromptRequest(prompt = prompt))),
|
|
|
|
| 27 |
]
|
| 28 |
cols = st.columns(len(buttons))
|
| 29 |
for col, button in zip(cols, buttons):
|
| 30 |
with col:
|
| 31 |
if st.button(button[0]):
|
| 32 |
+
button[1](prompt)
|
| 33 |
|
| 34 |
@st.dialog("Upload a Call")
|
| 35 |
def upload_call_modal_and_actions():
|
|
|
|
| 48 |
"Generate Tasks from transcript": partial(create_tasks_action_from_uploaded_call_transcript, Transcript = transcript_text),
|
| 49 |
}
|
| 50 |
_ = buttons[st.selectbox("Choose an action", buttons.keys())]()
|
| 51 |
+
|
| 52 |
+
def upload_call_and_actions():
|
| 53 |
+
uploaded_file = st.file_uploader(
|
| 54 |
+
# "Import a videocall recording or its transcripts",
|
| 55 |
+
"Import a videocall recording transcript",
|
| 56 |
+
type=['.txt', '.doc', '.docx', '.pdf'],
|
| 57 |
+
# type=['.txt', '.doc', '.docx', '.pdf', '.mp4', '.wav', '.flac'],
|
| 58 |
+
accept_multiple_files=False
|
| 59 |
+
)
|
| 60 |
+
transcript_text = get_transcript_text(uploaded_file) if uploaded_file else ""
|
| 61 |
+
st.session_state.transcript = transcript_text
|
| 62 |
+
if os.getenv("DEBUG_MODE", True):
|
| 63 |
+
with st.expander("Transcript"):
|
| 64 |
+
st.text_input("", value = transcript_text)
|
| 65 |
|
| 66 |
def minuta_action_from_uploaded_call():
|
| 67 |
if st.button("Crea minuta"):
|
google_calendar.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from datetime import datetime, timezone, timedelta
|
| 3 |
+
from googleapiclient.discovery import build
|
| 4 |
+
from google.auth.transport.requests import Request
|
| 5 |
+
from google.oauth2.credentials import Credentials
|
| 6 |
+
from google_auth_oauthlib.flow import InstalledAppFlow
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
|
| 9 |
+
load_dotenv('.env')
|
| 10 |
+
|
| 11 |
+
# Scopes richiesti per accedere a Google Calendar
|
| 12 |
+
SCOPES = ["https://www.googleapis.com/auth/calendar"]
|
| 13 |
+
OAUTH2_KEY = os.getenv("GOOGLE_OAUTH2_JSON_PATH", "desktop-client_secret_690889704678-5lml5g9dfva4hk1eugmhkbpn5atpnb8c.apps.googleusercontent.com.json")
|
| 14 |
+
|
| 15 |
+
# Autentica l'utente con OAuth 2.0 e restituisce il servizio Google Calendar API.
|
| 16 |
+
def authenticate_google():
|
| 17 |
+
|
| 18 |
+
creds = None
|
| 19 |
+
token_file = "token.json"
|
| 20 |
+
|
| 21 |
+
# Se il token di accesso esiste, lo usa
|
| 22 |
+
if os.path.exists(token_file):
|
| 23 |
+
creds = Credentials.from_authorized_user_file(token_file, SCOPES)
|
| 24 |
+
|
| 25 |
+
# Se le credenziali non sono valide, richiede autenticazione
|
| 26 |
+
if not creds or not creds.valid:
|
| 27 |
+
if creds and creds.expired and creds.refresh_token:
|
| 28 |
+
creds.refresh(Request())
|
| 29 |
+
else:
|
| 30 |
+
flow = InstalledAppFlow.from_client_secrets_file(
|
| 31 |
+
OAUTH2_KEY, SCOPES
|
| 32 |
+
)
|
| 33 |
+
creds = flow.run_local_server(port=0)
|
| 34 |
+
|
| 35 |
+
# Salva il token per utilizzi futuri
|
| 36 |
+
with open(token_file, "w") as token:
|
| 37 |
+
token.write(creds.to_json())
|
| 38 |
+
|
| 39 |
+
return build("calendar", "v3", credentials=creds)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def ScheduleMeeting(subject: str, agenda: str, start_date: str, end_date: str, participants: list) -> str:
|
| 43 |
+
"""
|
| 44 |
+
Crea un evento su Google Calendar con un link Google Meet.
|
| 45 |
+
|
| 46 |
+
:param subject: Titolo del meeting
|
| 47 |
+
:param agenda: Descrizione del meeting
|
| 48 |
+
:param start_date: Data e ora di inizio (ISO format: '2025-02-21T10:00:00')
|
| 49 |
+
:param end_date: Data e ora di fine (ISO format: '2025-02-21T11:00:00')
|
| 50 |
+
:param participants: Lista di email degli invitati
|
| 51 |
+
:return: ID dell'evento creato
|
| 52 |
+
"""
|
| 53 |
+
try:
|
| 54 |
+
service = authenticate_google()
|
| 55 |
+
|
| 56 |
+
event = {
|
| 57 |
+
"summary": subject,
|
| 58 |
+
"description": agenda,
|
| 59 |
+
"start": {"dateTime": start_date, "timeZone": "Europe/Rome"},
|
| 60 |
+
"end": {"dateTime": end_date, "timeZone": "Europe/Rome"},
|
| 61 |
+
"attendees": [{"email": email} for email in participants],
|
| 62 |
+
"conferenceData": {
|
| 63 |
+
"createRequest": {
|
| 64 |
+
"requestId": "meeting-" + start_date.replace(":", "-"),
|
| 65 |
+
"conferenceSolutionKey": {"type": "hangoutsMeet"}
|
| 66 |
+
}
|
| 67 |
+
},
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
event = service.events().insert(
|
| 71 |
+
calendarId="primary",
|
| 72 |
+
body=event,
|
| 73 |
+
conferenceDataVersion=1
|
| 74 |
+
).execute()
|
| 75 |
+
|
| 76 |
+
print(f"✅ Meeting created with ID: {event.get('id')}")
|
| 77 |
+
return "Evento creato con successo!" "\n".join([
|
| 78 |
+
f'\t{subject}',
|
| 79 |
+
f'\t{agenda}', f'\t{start_date}', f'\t{end_date}', "\n".join(["Partecipants", *list(map(lambda x : f'\t{x}', participants))])
|
| 80 |
+
])
|
| 81 |
+
|
| 82 |
+
except Exception as e:
|
| 83 |
+
print(f"❌ Error creating event: {e}")
|
| 84 |
+
return None
|
| 85 |
+
|
| 86 |
+
# Restituisce gli intervalli occupati per un elenco di calendari tra start_time e end_time.
|
| 87 |
+
def get_free_busy(service, calendar_ids, start_time, end_time): # start_time e end_time sono oggetti datetime
|
| 88 |
+
body = {
|
| 89 |
+
"timeMin": start_time.isoformat() + "Z",
|
| 90 |
+
"timeMax": end_time.isoformat() + "Z",
|
| 91 |
+
"items": [{"id": calendar_id} for calendar_id in calendar_ids]
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
try:
|
| 95 |
+
free_busy = service.freebusy().query(body=body).execute()
|
| 96 |
+
busy_times = []
|
| 97 |
+
|
| 98 |
+
for calendar_id in calendar_ids:
|
| 99 |
+
if "busy" in free_busy["calendars"][calendar_id]:
|
| 100 |
+
busy_times.extend(free_busy["calendars"][calendar_id]["busy"])
|
| 101 |
+
|
| 102 |
+
# Converti i periodi occupati in datetime oggetti
|
| 103 |
+
busy_intervals = [
|
| 104 |
+
(datetime.fromisoformat(entry["start"][:-1]), datetime.fromisoformat(entry["end"][:-1]))
|
| 105 |
+
for entry in busy_times
|
| 106 |
+
]
|
| 107 |
+
|
| 108 |
+
return sorted(busy_intervals)
|
| 109 |
+
|
| 110 |
+
except Exception as e:
|
| 111 |
+
print(f"❌ Error fetching free/busy information: {e}")
|
| 112 |
+
return []
|
| 113 |
+
|
| 114 |
+
# Trova il primo slot disponibile per tutti i partecipanti
|
| 115 |
+
def find_first_available_slot(service, calendar_ids, duration_minutes=30):
|
| 116 |
+
|
| 117 |
+
now = datetime.now(timezone.utc)
|
| 118 |
+
end_search = now + timedelta(days=7) # Cerca nei prossimi 7 giorni
|
| 119 |
+
|
| 120 |
+
step = timedelta(minutes=15) # Incremento per il controllo
|
| 121 |
+
meeting_duration = timedelta(minutes=duration_minutes)
|
| 122 |
+
|
| 123 |
+
current_time = now.replace(second=0, microsecond=0) + timedelta(minutes=15) # Evita slot troppo vicini
|
| 124 |
+
while current_time < end_search:
|
| 125 |
+
next_time = current_time + meeting_duration
|
| 126 |
+
|
| 127 |
+
busy_intervals = get_free_busy(service, calendar_ids, current_time, next_time)
|
| 128 |
+
|
| 129 |
+
# Se non ci sono sovrapposizioni, lo slot è libero
|
| 130 |
+
if not any(start < next_time and end > current_time for start, end in busy_intervals):
|
| 131 |
+
return current_time.isoformat() # Primo slot disponibile
|
| 132 |
+
|
| 133 |
+
# Prova il prossimo slot
|
| 134 |
+
current_time += step
|
| 135 |
+
|
| 136 |
+
return None # Nessuno slot trovato
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
# Esempio di utilizzo:
|
| 141 |
+
if __name__ == "__main__":
|
| 142 |
+
'''
|
| 143 |
+
#Test - find an available slot among team members
|
| 144 |
+
service = authenticate_google()
|
| 145 |
+
calendar_ids = [""]
|
| 146 |
+
slot = find_first_available_slot(service, calendar_ids)
|
| 147 |
+
|
| 148 |
+
if slot:
|
| 149 |
+
print(f"First available slot: {slot}")
|
| 150 |
+
else:
|
| 151 |
+
print("No slot found in the next 7 days")
|
| 152 |
+
|
| 153 |
+
'''
|
| 154 |
+
# #Test - schdule a meeting
|
| 155 |
+
# meeting_id = ScheduleMeeting(
|
| 156 |
+
# subject="Project Kickoff Meeting",
|
| 157 |
+
# agenda="Discuss project progress and upcoming tasks.",
|
| 158 |
+
# start_date="2025-02-22T16:30:00",
|
| 159 |
+
# end_date="2025-02-22T17:30:00",
|
| 160 |
+
# participants=[""]
|
| 161 |
+
# )
|
| 162 |
+
# print("Meeting ID:", meeting_id)
|
| 163 |
+
authenticate_google()
|
| 164 |
+
# '''
|
initial_state.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"team": [
|
| 3 |
+
"joyciliani@gmail.com",
|
| 4 |
+
"chiovelli.alessio@gmail.com",
|
| 5 |
+
"Nicola.caione@gmail.com",
|
| 6 |
+
"nicola.decuzzi.nd@gmail.com",
|
| 7 |
+
"dragos.baicu@edu.unifi.it"
|
| 8 |
+
],
|
| 9 |
+
"tasks": [
|
| 10 |
+
{
|
| 11 |
+
"name": "UX/UI design",
|
| 12 |
+
"status": "open",
|
| 13 |
+
"start_date": "2025-02-21",
|
| 14 |
+
"end_date": "2025-02-22"
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"name": "UX/UI coding",
|
| 18 |
+
"status": "to be started",
|
| 19 |
+
"start_date": "2025-02-22",
|
| 20 |
+
"end_date": "2025-02-23"
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"name": "AI agents design",
|
| 24 |
+
"status": "open",
|
| 25 |
+
"start_date": "2025-02-21",
|
| 26 |
+
"end_date": "2025-02-22"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"name": "AI agents coding",
|
| 30 |
+
"status": "to be started",
|
| 31 |
+
"start_date": "2025-02-22",
|
| 32 |
+
"end_date": "2025-02-23"
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"name": "Script video",
|
| 36 |
+
"status": "open",
|
| 37 |
+
"start_date": "2025-02-21",
|
| 38 |
+
"end_date": "2025-02-22"
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"name": "Video making",
|
| 42 |
+
"status": "to be started",
|
| 43 |
+
"start_date": "2025-02-22",
|
| 44 |
+
"end_date": "2025-02-23"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"name": "Presentation design",
|
| 48 |
+
"status": "open",
|
| 49 |
+
"start_date": "2025-02-21",
|
| 50 |
+
"end_date": "2025-02-22"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"name": "Presentation implementation",
|
| 54 |
+
"status": "to be started",
|
| 55 |
+
"start_date": "2025-02-22",
|
| 56 |
+
"end_date": "2025-02-23"
|
| 57 |
+
}
|
| 58 |
+
],
|
| 59 |
+
|
| 60 |
+
"assignments": {
|
| 61 |
+
"joyciliani@gmail.com": [
|
| 62 |
+
"UX/UI design",
|
| 63 |
+
"Script video",
|
| 64 |
+
"Presentation design"
|
| 65 |
+
],
|
| 66 |
+
"chiovelli.alessio@gmail.com": [
|
| 67 |
+
"UX/UI coding",
|
| 68 |
+
"AI agents design",
|
| 69 |
+
"AI agents coding"
|
| 70 |
+
],
|
| 71 |
+
"Nicola.caione@gmail.com": [
|
| 72 |
+
"AI agents design",
|
| 73 |
+
"AI agents coding",
|
| 74 |
+
"Script video",
|
| 75 |
+
"Presentation design"
|
| 76 |
+
],
|
| 77 |
+
"dragos.baicu@edu.unifi.it": [
|
| 78 |
+
"Video making"
|
| 79 |
+
]
|
| 80 |
+
}
|
| 81 |
+
}
|