feat(gb-llm): Implement email sending and meeting scheduling functionalities; add customer sentiment analysis and opportunity updates

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-04-20 01:40:40 -03:00
parent a86a965cd0
commit bf382e623e
17 changed files with 704 additions and 11 deletions

View file

@ -11,6 +11,9 @@ use flate2::read::GzDecoder;
use tar::Archive;
use zip::ZipArchive;
// TODO: https://helpcenter.onlyoffice.com/docs/installation/docs-community-install-ubuntu.aspx
const INSTALL_DIR: &str = "/opt/gbo";
const TEMP_DIR: &str = "/tmp/gbotemp";

View file

@ -0,0 +1,45 @@
PARAM customer_id AS STRING
PARAM time_period AS INTEGER DEFAULT 30
# Gather customer communications
emails = CALL "/storage/json", ".gbdata/communication_logs",
"to = '${customer_id}' OR from = '${customer_id}' AND timestamp > NOW() - DAYS(${time_period})"
support_tickets = CALL "/crm/tickets/list", {
"customer_id": customer_id,
"created_after": NOW() - DAYS(time_period)
}
meeting_notes = CALL "/crm/meetings/list", {
"customer_id": customer_id,
"date_after": NOW() - DAYS(time_period)
}
# Combine all text for analysis
all_text = ""
FOR EACH email IN emails
all_text = all_text + email.subject + " " + email.body + " "
NEXT
FOR EACH ticket IN support_tickets
all_text = all_text + ticket.description + " " + ticket.resolution + " "
NEXT
FOR EACH meeting IN meeting_notes
all_text = all_text + meeting.notes + " "
NEXT
# Analyze sentiment
sentiment = CALL "/ai/analyze/text", all_text, "sentiment"
# Generate insights
insights = CALL "/ai/analyze/text", all_text, "key_topics"
RETURN {
"customer_id": customer_id,
"time_period": time_period + " days",
"sentiment_score": sentiment.score,
"sentiment_label": sentiment.label,
"key_topics": insights.topics,
"recommendations": insights.recommendations
}

View file

@ -0,0 +1,83 @@
PARAM period AS STRING DEFAULT "month"
PARAM team_id AS STRING OPTIONAL
# Determine date range
IF period = "week" THEN
start_date = NOW() - DAYS(7)
ELSEIF period = "month" THEN
start_date = NOW() - DAYS(30)
ELSEIF period = "quarter" THEN
start_date = NOW() - DAYS(90)
ELSEIF period = "year" THEN
start_date = NOW() - DAYS(365)
ELSE
RETURN "Invalid period specified. Use 'week', 'month', 'quarter', or 'year'."
END IF
# Construct team filter
team_filter = ""
IF team_id IS NOT NULL THEN
team_filter = " AND team_id = '" + team_id + "'"
END IF
# Get sales data
opportunities = QUERY "SELECT * FROM Opportunities WHERE close_date >= '${start_date}'" + team_filter
closed_won = QUERY "SELECT * FROM Opportunities WHERE status = 'Won' AND close_date >= '${start_date}'" + team_filter
closed_lost = QUERY "SELECT * FROM Opportunities WHERE status = 'Lost' AND close_date >= '${start_date}'" + team_filter
# Calculate metrics
total_value = 0
FOR EACH opp IN closed_won
total_value = total_value + opp.value
NEXT
win_rate = LEN(closed_won) / (LEN(closed_won) + LEN(closed_lost)) * 100
# Get performance by rep
sales_reps = QUERY "SELECT owner_id, COUNT(*) as deals, SUM(value) as total_value FROM Opportunities WHERE status = 'Won' AND close_date >= '${start_date}'" + team_filter + " GROUP BY owner_id"
# Generate report
report = CALL "/analytics/reports/generate", {
"title": "Sales Performance Report - " + UPPER(period),
"date_range": "From " + FORMAT_DATE(start_date) + " to " + FORMAT_DATE(NOW()),
"metrics": {
"total_opportunities": LEN(opportunities),
"won_opportunities": LEN(closed_won),
"lost_opportunities": LEN(closed_lost),
"win_rate": win_rate,
"total_value": total_value
},
"rep_performance": sales_reps,
"charts": [
{
"type": "bar",
"title": "Won vs Lost Opportunities",
"data": {"Won": LEN(closed_won), "Lost": LEN(closed_lost)}
},
{
"type": "line",
"title": "Sales Trend",
"data": QUERY "SELECT DATE_FORMAT(close_date, '%Y-%m-%d') as date, COUNT(*) as count, SUM(value) as value FROM Opportunities WHERE status = 'Won' AND close_date >= '${start_date}'" + team_filter + " GROUP BY DATE_FORMAT(close_date, '%Y-%m-%d')"
}
]
}
# Save report
report_file = ".gbdrive/Reports/Sales/sales_performance_" + period + "_" + FORMAT_DATE(NOW(), "Ymd") + ".pdf"
CALL "/files/save", report_file, report
# Share report
IF team_id IS NOT NULL THEN
CALL "/files/shareFolder", report_file, team_id
# Notify team manager
manager = QUERY "SELECT manager_id FROM Teams WHERE id = '${team_id}'"
IF LEN(manager) > 0 THEN
CALL "/comm/email/send", manager[0],
"Sales Performance Report - " + UPPER(period),
"The latest sales performance report for your team is now available.",
[report_file]
END IF
END IF
RETURN "Sales performance report generated: " + report_file

View file

@ -0,0 +1,25 @@
PARAM to AS STRING
PARAM template AS STRING
PARAM opportunity AS STRING
company = QUERY "SELECT Company FROM Opportunities WHERE Id = ${opportunity}"
doc = FILL template
# Generate email subject and content based on conversation history
subject = REWRITE "Based on this ${history}, generate a subject for a proposal email to ${company}"
contents = REWRITE "Based on this ${history}, and ${subject}, generate the e-mail body for ${to}, signed by ${user}, including key points from our proposal"
# Add proposal to CRM
CALL "/files/upload", ".gbdrive/Proposals/${company}-proposal.docx", doc
CALL "/files/permissions", ".gbdrive/Proposals/${company}-proposal.docx", "sales-team", "edit"
# Record activity in CRM
CALL "/crm/activities/create", opportunity, "email_sent", {
"subject": subject,
"description": "Proposal sent to " + company,
"date": NOW()
}
# Send the email
CALL "/comm/email/send", to, subject, contents, doc

View file

@ -0,0 +1,36 @@
PARAM attendees AS ARRAY
PARAM topic AS STRING
PARAM duration AS INTEGER
PARAM preferred_date AS DATE OPTIONAL
# Find available time for all attendees
IF preferred_date IS NULL THEN
available_slots = CALL "/calendar/availability/check", attendees, NOW(), NOW() + DAYS(7), duration
ELSE
available_slots = CALL "/calendar/availability/check", attendees, preferred_date, preferred_date + DAYS(1), duration
END IF
IF LEN(available_slots) = 0 THEN
RETURN "No available time slots found for all attendees."
END IF
# Create meeting description
description = REWRITE "Generate a concise meeting description for topic: ${topic}"
# Schedule the meeting
event_id = CALL "/calendar/events/create", {
"subject": topic,
"description": description,
"start_time": available_slots[0].start,
"end_time": available_slots[0].end,
"attendees": attendees,
"location": "Virtual Meeting"
}
# Notify attendees
FOR EACH person IN attendees
CALL "/comm/notifications/send", person, "Meeting Scheduled: " + topic,
"You have been invited to a meeting on " + FORMAT_DATE(available_slots[0].start)
NEXT
RETURN "Meeting scheduled for " + FORMAT_DATE(available_slots[0].start)

View file

@ -0,0 +1,23 @@
PARAM from AS STRING
PARAM to AS STRING
PARAM subject AS STRING
PARAM body AS STRING
PARAM attachments AS ARRAY
# Track in communication history
CALL "/storage/save", ".gbdata/communication_logs", {
"from": from,
"to": to,
"subject": subject,
"timestamp": NOW(),
"type": "email"
}
# Send actual email
CALL "/comm/email/send", from, to, subject, body, attachments
# If WITH HISTORY flag present, include prior communication
IF WITH_HISTORY THEN
prevComms = CALL "/storage/json", ".gbdata/communication_logs", "to = '" + to + "' ORDER BY timestamp DESC LIMIT 5"
APPEND body WITH FORMAT_HISTORY(prevComms)
END IF

View file

@ -0,0 +1,67 @@
PARAM meeting_id AS STRING
PARAM action AS STRING DEFAULT "join"
IF action = "join" THEN
# Get meeting details
meeting = CALL "/calendar/events/get", meeting_id
# Join the meeting
CALL "/conversations/calls/join", meeting.conference_link
# Set up recording
CALL "/conversations/recording/start", meeting_id
# Create meeting notes document
notes_doc = CALL "/files/create",
".gbdrive/Meetings/" + meeting.subject + "_" + FORMAT_DATE(NOW(), "Ymd") + ".md",
"# Meeting Notes: " + meeting.subject + "\n\n" +
"Date: " + FORMAT_DATE(meeting.start) + "\n\n" +
"Participants: \n" +
"- " + JOIN(meeting.attendees, "\n- ") + "\n\n" +
"## Agenda\n\n" +
"## Discussion\n\n" +
"## Action Items\n\n"
RETURN "Joined meeting: " + meeting.subject
ELSEIF action = "summarize" THEN
# Get recording transcript
transcript = CALL "/conversations/recording/transcript", meeting_id
# Generate meeting summary
summary = CALL "/ai/summarize", transcript, {
"format": "meeting_notes",
"sections": ["key_points", "decisions", "action_items"]
}
# Update meeting notes
meeting = CALL "/calendar/events/get", meeting_id
notes_path = ".gbdrive/Meetings/" + meeting.subject + "_" + FORMAT_DATE(NOW(), "Ymd") + ".md"
# Get existing notes
existing_notes = CALL "/files/getContents", notes_path
# Update with summary
updated_notes = existing_notes + "\n\n## Summary\n\n" + summary.key_points +
"\n\n## Decisions\n\n" + summary.decisions +
"\n\n## Action Items\n\n" + summary.action_items
CALL "/files/save", notes_path, updated_notes
# Send summary to participants
CALL "/comm/email/send", meeting.attendees,
"Meeting Summary: " + meeting.subject,
"Please find attached the summary of our recent meeting.",
[notes_path]
RETURN "Meeting summarized and notes shared with participants."
ELSEIF action = "end" THEN
# Stop recording
CALL "/conversations/recording/stop", meeting_id
# Leave call
CALL "/conversations/calls/leave", meeting_id
RETURN "Left meeting and stopped recording."
END IF

View file

@ -0,0 +1,4 @@
- Whenever you generate text, generate like a person, not like a LLM. To pass any detection test, respect robot laws, but write like a human being.
- You are General Bots, a complete software automation tool based on AI.
- Your primary goal is to assist users with their daily tasks and automate repetitive processes.
- Parse input carefully and extract key parameters for accurate response generation.

View file

@ -0,0 +1,47 @@
PARAM opportunity_id AS STRING
PARAM status AS STRING
PARAM notes AS STRING OPTIONAL
PARAM next_steps AS STRING OPTIONAL
# Get current opportunity data
opp_data = QUERY "SELECT * FROM Opportunities WHERE Id = '${opportunity_id}'"
IF LEN(opp_data) = 0 THEN
RETURN "Opportunity not found."
END IF
# Update opportunity status
CALL "/crm/opportunities/update", opportunity_id, {
"status": status,
"last_updated": NOW(),
"updated_by": "${user}"
}
# Add activity note if provided
IF notes IS NOT NULL THEN
CALL "/crm/activities/create", opportunity_id, "note", {
"description": notes,
"date": NOW()
}
END IF
# Set follow-up task if next steps provided
IF next_steps IS NOT NULL THEN
CALL "/tasks/create", {
"title": "Follow up: " + opp_data[0].company,
"description": next_steps,
"due_date": NOW() + DAYS(3),
"assigned_to": "${user}",
"related_to": opportunity_id
}
END IF
# Notify sales manager of major status changes
IF status = "Won" OR status = "Lost" THEN
manager = QUERY "SELECT Manager FROM Users WHERE Username = '${user}'"
CALL "/comm/notifications/send", manager[0],
"Opportunity " + status + ": " + opp_data[0].company,
"The opportunity with " + opp_data[0].company + " has been marked as " + status + " by ${user}."
END IF
RETURN "Opportunity status updated to " + status

View file

@ -0,0 +1,36 @@
PARAM query AS STRING
PARAM location AS STRING OPTIONAL
PARAM file_type AS STRING OPTIONAL
PARAM date_range AS ARRAY OPTIONAL
search_params = {
"query": query
}
IF location IS NOT NULL THEN
search_params["location"] = location
END IF
IF file_type IS NOT NULL THEN
search_params["file_type"] = file_type
END IF
IF date_range IS NOT NULL THEN
search_params["created_after"] = date_range[0]
search_params["created_before"] = date_range[1]
END IF
results = CALL "/files/search", search_params
IF LEN(results) = 0 THEN
RETURN "No documents found matching your criteria."
END IF
# Format results for display
formatted_results = "Found " + LEN(results) + " documents:\n\n"
FOR EACH doc IN results
formatted_results = formatted_results + "- " + doc.name + " (" + FORMAT_DATE(doc.modified) + ")\n"
formatted_results = formatted_results + " Location: " + doc.path + "\n"
NEXT
RETURN formatted_results

View file

@ -0,0 +1,28 @@
My Work
General
Sales Manager
Project Management
CRM
You should use files in .gbdrive/Proposals to search proposals.
You should use table RoB present in .gbdata/Proposals to get my proposals where User is ${user}
For sales pipelines, use table Opportunities in .gbdata/Sales.
Files
Use API endpoints under /files/* for document management.
CALL "/files/upload" uploads files to the system.
CALL "/files/search" finds relevant documents.
HR
People are in .gbdata/People
You should use files in .gbdrive/People to get resumes
Use HR_PORTAL to access employment records and policies.
ALM
My issues are in .gbservice/forgejo
CALL "/tasks/create" creates new project tasks.
CALL "/tasks/status/update" updates existing task status.
SETTINGS
API_KEYS stored in .gbsecure/keys
PREFERENCES in .gbdata/user-settings

View file

@ -0,0 +1,76 @@
PARAM name AS STRING
PARAM members AS ARRAY
PARAM description AS STRING OPTIONAL
PARAM team_type AS STRING DEFAULT "project"
# Create the group
group_id = CALL "/groups/create", {
"name": name,
"description": description,
"type": team_type
}
# Add members
FOR EACH member IN members
CALL "/groups/members/add", group_id, member
NEXT
# Create standard workspace structure
CALL "/files/createFolder", ".gbdrive/Workspaces/" + name + "/Documents"
CALL "/files/createFolder", ".gbdrive/Workspaces/" + name + "/Meetings"
CALL "/files/createFolder", ".gbdrive/Workspaces/" + name + "/Resources"
# Create default workspace components
IF team_type = "project" THEN
# Create project board
board_id = CALL "/tasks/create", {
"title": name + " Project Board",
"description": "Task board for " + name,
"type": "project_board"
}
# Create standard task lanes
lanes = ["Backlog", "To Do", "In Progress", "Review", "Done"]
FOR EACH lane IN lanes
CALL "/tasks/lanes/create", board_id, lane
NEXT
# Link group to project board
CALL "/groups/settings", group_id, "project_board", board_id
END IF
# Set up communication channel
channel_id = CALL "/conversations/create", {
"name": name,
"description": description,
"type": "group_chat"
}
# Add all members to channel
FOR EACH member IN members
CALL "/conversations/members/add", channel_id, member
NEXT
# Link group to channel
CALL "/groups/settings", group_id, "conversation", channel_id
# Create welcome message
welcome_msg = REWRITE "Create a welcome message for a new workspace called ${name} with purpose: ${description}"
CALL "/conversations/messages/send", channel_id, {
"text": welcome_msg,
"pinned": TRUE
}
# Notify members
FOR EACH member IN members
CALL "/comm/notifications/send", member,
"You've been added to " + name,
"You have been added to the new workspace: " + name
NEXT
RETURN {
"group_id": group_id,
"channel_id": channel_id,
"workspace_location": ".gbdrive/Workspaces/" + name
}

View file

@ -0,0 +1,58 @@
PARAM components AS ARRAY OPTIONAL
PARAM notify AS BOOLEAN DEFAULT TRUE
# Check all components by default
IF components IS NULL THEN
components = ["storage", "api", "database", "integrations", "security"]
END IF
status_report = {}
FOR EACH component IN components
status = CALL "/health/detailed", component
status_report[component] = status
NEXT
# Calculate overall health score
total_score = 0
FOR EACH component IN components
total_score = total_score + status_report[component].health_score
NEXT
overall_health = total_score / LEN(components)
status_report["overall_health"] = overall_health
status_report["timestamp"] = NOW()
# Save status report
CALL "/storage/save", ".gbdata/health/status_" + FORMAT_DATE(NOW(), "Ymd_His") + ".json", status_report
# Check for critical issues
critical_issues = []
FOR EACH component IN components
IF status_report[component].health_score < 0.7 THEN
APPEND critical_issues, {
"component": component,
"score": status_report[component].health_score,
"issues": status_report[component].issues
}
END IF
NEXT
# Notify if critical issues found
IF LEN(critical_issues) > 0 AND notify THEN
issue_summary = "Critical system health issues detected:\n\n"
FOR EACH issue IN critical_issues
issue_summary = issue_summary + "- " + issue.component + " (Score: " + issue.score + ")\n"
FOR EACH detail IN issue.issues
issue_summary = issue_summary + " * " + detail + "\n"
NEXT
issue_summary = issue_summary + "\n"
NEXT
CALL "/comm/notifications/send", "admin-team",
"ALERT: System Health Issues Detected",
issue_summary,
"high"
END IF
RETURN status_report

View file

@ -0,0 +1,51 @@
SET SCHEDULE every 1 hour
# Check emails
unread_emails = CALL "/comm/email/list", {
"status": "unread",
"folder": "inbox",
"max_age": "24h"
}
# Check calendar
upcoming_events = CALL "/calendar/events/list", {
"start": NOW(),
"end": NOW() + HOURS(24)
}
# Check tasks
due_tasks = CALL "/tasks/list", {
"status": "open",
"due_before": NOW() + HOURS(24)
}
# Check important documents
new_documents = CALL "/files/recent", {
"folders": [".gbdrive/papers", ".gbdrive/Proposals"],
"since": NOW() - HOURS(24)
}
# Prepare notification message
notification = "Daily Update:\n"
IF LEN(unread_emails) > 0 THEN
notification = notification + "- You have " + LEN(unread_emails) + " unread emails\n"
END IF
IF LEN(upcoming_events) > 0 THEN
notification = notification + "- You have " + LEN(upcoming_events) + " upcoming meetings in the next 24 hours\n"
notification = notification + " Next: " + upcoming_events[0].subject + " at " + FORMAT_TIME(upcoming_events[0].start) + "\n"
END IF
IF LEN(due_tasks) > 0 THEN
notification = notification + "- You have " + LEN(due_tasks) + " tasks due in the next 24 hours\n"
END IF
IF LEN(new_documents) > 0 THEN
notification = notification + "- " + LEN(new_documents) + " new documents have been added to your monitored folders\n"
END IF
# Send notification
IF LEN(notification) > "Daily Update:\n" THEN
CALL "/comm/notifications/send", "${user}", "Daily Status Update", notification
END IF

View file

@ -0,0 +1,63 @@
PARAM resource_path AS STRING
PARAM review_period AS INTEGER DEFAULT 90
# Get current permissions
current_perms = CALL "/files/permissions", resource_path
# Get access logs
access_logs = CALL "/security/audit/logs", {
"resource": resource_path,
"action": "access",
"timeframe": NOW() - DAYS(review_period)
}
# Identify inactive users with access
inactive_users = []
FOR EACH user IN current_perms
# Check if user has accessed in review period
user_logs = FILTER access_logs WHERE user_id = user.id
IF LEN(user_logs) = 0 THEN
APPEND inactive_users, {
"user_id": user.id,
"access_level": user.access_level,
"last_access": CALL "/security/audit/logs", {
"resource": resource_path,
"action": "access",
"user_id": user.id,
"limit": 1
}
}
END IF
NEXT
# Generate review report
review_report = {
"resource": resource_path,
"review_date": NOW(),
"total_users_with_access": LEN(current_perms),
"inactive_users": inactive_users,
"recommendations": []
}
# Add recommendations
IF LEN(inactive_users) > 0 THEN
review_report.recommendations.APPEND("Remove access for " + LEN(inactive_users) + " inactive users")
END IF
excessive_admins = FILTER current_perms WHERE access_level = "admin"
IF LEN(excessive_admins) > 3 THEN
review_report.recommendations.APPEND("Reduce number of admin users (currently " + LEN(excessive_admins) + ")")
END IF
# Save review report
report_file = ".gbdata/security/access_reviews/" + REPLACE(resource_path, "/", "_") + "_" + FORMAT_DATE(NOW(), "Ymd") + ".json"
CALL "/files/save", report_file, review_report
# Notify security team
CALL "/comm/email/send", "security-team",
"Access Review Report: " + resource_path,
"A new access review report has been generated for " + resource_path + ".",
[report_file]
RETURN review_report

View file

@ -0,0 +1,59 @@
PARAM sender AS STRING
PARAM subject AS STRING
PARAM body AS STRING
# Get history for this sender
history = CALL "/storage/json", ".gbdata/communication_logs", "from = '${sender}' OR to = '${sender}' ORDER BY timestamp DESC LIMIT 10"
# Check if this is a known customer
customer = CALL "/crm/customers/get", sender
# Analyze email content
urgency = CALL "/ai/analyze/text", body, "urgency"
intent = CALL "/ai/analyze/text", body, "intent"
sentiment = CALL "/ai/analyze/text", body, "sentiment"
# Determine if auto-reply needed
should_auto_reply = FALSE
IF urgency.score > 0.8 THEN
should_auto_reply = TRUE
END IF
IF customer IS NOT NULL AND customer.tier = "premium" THEN
should_auto_reply = TRUE
END IF
IF intent.category = "support_request" THEN
# Create support ticket
ticket_id = CALL "/crm/tickets/create", {
"customer": sender,
"subject": subject,
"description": body,
"priority": urgency.score > 0.7 ? "High" : "Normal"
}
should_auto_reply = TRUE
# Notify support team
CALL "/comm/notifications/send", "support-team",
"New Support Ticket: " + subject,
"A new support ticket has been created from an email by " + sender
END IF
IF should_auto_reply THEN
reply_template = intent.category = "support_request" ? "support_acknowledgment" : "general_acknowledgment"
reply_text = REWRITE "Based on this email: ${body}
And this sender history: ${history}
Generate a personalized auto-reply message using the ${reply_template} style.
Include appropriate next steps and expected timeframe for response."
CALL "/comm/email/send", "${user}", sender, "Re: " + subject, reply_text
CALL "/storage/save", ".gbdata/auto_replies", {
"to": sender,
"subject": "Re: " + subject,
"timestamp": NOW()
}
END IF

View file

@ -1,11 +0,0 @@
PARAM to AS STRING
PARAM template AS STRING
company =
doc = FILL template
subject= REWRITE "Based on this ${history}, generate a subject for a proposal email"
contents = REWRITE "Based on this ${history}, and ${subject}, generate the e-mail body for ${to}, signed by ${user}.
SEND MAIL to, subject, contents