Alessio-Chiovelli commited on
Commit
a191da0
·
verified ·
1 Parent(s): cfd8b92

Upload 46 files

Browse files

test application

Files changed (41) hide show
  1. APIModules/AIAgents/AlertAgent.py +28 -0
  2. APIModules/AIAgents/MeetingSchedulerAgent.py +119 -0
  3. APIModules/AIAgents/SQLAgent.py +167 -0
  4. APIModules/AIAgents/TranscriptQAAgent.py +97 -0
  5. APIModules/AIAgents/__init__.py +12 -0
  6. APIModules/AIAgents/__pycache__/AlertAgent.cpython-312.pyc +0 -0
  7. APIModules/AIAgents/__pycache__/MeetingSchedulerAgent.cpython-312.pyc +0 -0
  8. APIModules/AIAgents/__pycache__/SQLAgent.cpython-312.pyc +0 -0
  9. APIModules/AIAgents/__pycache__/TranscriptQAAgent.cpython-312.pyc +0 -0
  10. APIModules/AIAgents/__pycache__/__init__.cpython-312.pyc +0 -0
  11. APIModules/APIs/__pycache__/agents_router.cpython-312.pyc +0 -0
  12. APIModules/APIs/__pycache__/base_router.cpython-312.pyc +0 -0
  13. APIModules/APIs/__pycache__/router.cpython-312.pyc +0 -0
  14. APIModules/APIs/agents_router.py +113 -0
  15. APIModules/ObjectModels/Calendar.py +9 -0
  16. APIModules/ObjectModels/Meeting.py +16 -0
  17. APIModules/ObjectModels/Project.py +12 -0
  18. APIModules/ObjectModels/Resource.py +12 -0
  19. APIModules/ObjectModels/Task.py +10 -0
  20. APIModules/ObjectModels/__init__.py +5 -0
  21. APIModules/ObjectModels/__pycache__/Task.cpython-312.pyc +0 -0
  22. APIModules/ObjectModels/__pycache__/__init__.cpython-312.pyc +0 -0
  23. APIModules/ObjectModels/functions.py +104 -0
  24. LICENSE +21 -0
  25. README.md +1 -1
  26. app.py +0 -2
  27. desktop-client_secret_690889704678-5lml5g9dfva4hk1eugmhkbpn5atpnb8c.apps.googleusercontent.com.json +13 -0
  28. frontend_pages/BasePage.py +7 -1
  29. frontend_pages/ChatModelPage.py +198 -44
  30. frontend_pages/ConfigPage.py +44 -2
  31. frontend_pages/Dashboard.py +3 -3
  32. frontend_pages/__init__.py +10 -1
  33. frontend_pages/__pycache__/BasePage.cpython-312.pyc +0 -0
  34. frontend_pages/__pycache__/ChatModelPage.cpython-312.pyc +0 -0
  35. frontend_pages/__pycache__/ConfigPage.cpython-312.pyc +0 -0
  36. frontend_pages/__pycache__/Dashboard.cpython-312.pyc +0 -0
  37. frontend_pages/__pycache__/__init__.cpython-312.pyc +0 -0
  38. frontend_pages/__pycache__/modals.cpython-312.pyc +0 -0
  39. frontend_pages/modals.py +25 -7
  40. google_calendar.py +164 -0
  41. 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
- with st.sidebar:
 
 
 
 
 
 
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 get_base64_image, create_call_modal, upload_call_modal_and_actions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- response = requests.post(os.getenv("BACKEND_URL", "http://127.0.0.1:5000") + f'{uri}', json = {"prompt" : prompt})
13
- if response.status_code != 200:
14
- st.error(f"Error: {response.text}")
15
- return
16
- response_json = response.json()
17
- print(f'{response_json = }')
18
- return response_json["message"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- return self.router_agent(**kwargs)
 
 
 
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 upfront_page(self):
64
- tabs_and_dialogs_cols = st.columns(2)
65
- with tabs_and_dialogs_cols[0]:
 
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
- key = "upload-call-button",
84
- css_styles = f"""
85
- button {{
86
- background-image: url("data:image/png;base64,{get_base64_image("static/UploadCallButton.png")}");
87
- background-size: cover;
88
- background-repeat: no-repeat;
89
- background-position: center;
90
- background-color: transparent;
91
- border: none;
92
- width: 298px;
93
- height: 164px;
94
- }}"""
95
- ):
96
- clicked_create_call_button = st.button('', key='upload-call-button')
97
- if clicked_create_call_button:upload_call_modal_and_actions()
98
- with tabs_and_dialogs_cols[1]:
99
- tabs = st.tabs(["Tasks"])
100
- func_of_tabs = [ self.tasks ]
101
- for tab, func in zip(tabs, func_of_tabs):
102
- with tab:func()
 
 
 
 
103
 
104
  def sidebar_elements(self):
105
  actions = {"Project" : self.projects, "Team" : self.team }
106
  with st.sidebar:
107
- for label, action in actions.items():
108
- st.header(label)
109
- action()
110
- st.divider()
 
111
 
112
  def team(self):
113
- teams_per_project = {"Projects": "Team1", "Projects2": "Team2", "Projects3": "Team3"}
114
- st.write(teams_per_project[st.session_state.selected_project])
115
  # st.image("static/2.png")
116
 
117
  def tasks(self):
118
- st.image("static/3.png")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  def projects(self):
121
- projects_available = ["Projects", "Projects2", "Projects3"]
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
- from . import os
 
 
 
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.image("static/3.png")
69
 
70
  def projects(self):
71
- projects_available = ["Projects", "Projects2", "Projects3"]
72
- st.session_state.selected_project = st.selectbox("Projects available", projects_available)
 
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", ["Alice", "Bob", "Charlie"])
18
- st.multiselect("Tasks", ["Task 1", "Task 2", "Task 3"])
19
- st.text_area("Prompt")
 
 
 
 
 
20
  buttons = [
21
- ("submit", lambda _ : st.balloons()),
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
+ }