Skip to content

Instantly share code, notes, and snippets.

@lynnaloo
Created February 12, 2026 05:34
Show Gist options
  • Select an option

  • Save lynnaloo/5ffb9e7620d6db95dab4d3787a5703fd to your computer and use it in GitHub Desktop.

Select an option

Save lynnaloo/5ffb9e7620d6db95dab4d3787a5703fd to your computer and use it in GitHub Desktop.
/**
* ChatterActionItemParser
*
* Invocable Apex class designed to be called from a Salesforce Flow.
* Accepts a chatter summary (e.g., from an AI prompt summarization) and
* parses out numbered action items, returning them as a List<String>
* that can be iterated in a Flow Loop to create Tasks.
*
* Supported formats:
* 1. Action item text
* 2. Action item text
* - Action item text
* * Action item text
*
* Multi-line action items are concatenated until the next item or end of text.
*/
public with sharing class ChatterActionItemParser {
// ── Flow Input ──────────────────────────────────────────────────────
public class ActionItemRequest {
@InvocableVariable(label='Chatter Summary Text' description='The full AI-generated chatter summary containing action items' required=true)
public String summaryText;
}
// ── Flow Output ─────────────────────────────────────────────────────
public class ActionItemResult {
@InvocableVariable(label='Action Items' description='Ordered list of parsed action items')
public List<String> actionItems;
@InvocableVariable(label='Item Count' description='Number of action items found')
public Integer itemCount;
@InvocableVariable(label='Success' description='Whether parsing completed without error')
public Boolean success;
@InvocableVariable(label='Error Message' description='Error details if parsing failed')
public String errorMessage;
}
// ── Invocable Method ────────────────────────────────────────────────
@InvocableMethod(
label='Parse Chatter Action Items'
description='Extracts numbered/bulleted action items from an AI chatter summary and returns them as a string collection for Flow iteration.'
category='Chatter Utilities'
)
public static List<ActionItemResult> parseActionItems(List<ActionItemRequest> requests) {
List<ActionItemResult> results = new List<ActionItemResult>();
for (ActionItemRequest req : requests) {
ActionItemResult result = new ActionItemResult();
result.actionItems = new List<String>();
result.success = true;
try {
if (String.isBlank(req.summaryText)) {
result.success = false;
result.errorMessage = 'Summary text is blank or null.';
result.itemCount = 0;
results.add(result);
continue;
}
result.actionItems = extractActionItems(req.summaryText);
result.itemCount = result.actionItems.size();
} catch (Exception ex) {
result.success = false;
result.errorMessage = ex.getMessage();
result.itemCount = 0;
}
results.add(result);
}
return results;
}
// ── Core Parsing Logic ──────────────────────────────────────────────
/**
* Extracts action items from the summary text.
* Looks for a header line (e.g., "Action items" / "action items include:")
* then parses numbered (1. 2. 3.) or bulleted (- / *) list entries.
* Multi-line items are joined with a space.
*/
@TestVisible
private static List<String> extractActionItems(String summaryText) {
List<String> items = new List<String>();
// Normalize line breaks
String normalized = summaryText.replace('\r\n', '\n').replace('\r', '\n');
List<String> lines = normalized.split('\n');
// Regex patterns
// Numbered item: "1. Some text" or "1) Some text"
// Bulleted item: "- Some text" or "* Some text"
String numberedPattern = '^\\s*\\d+[.\\)]\\s+(.+)';
String bulletPattern = '^\\s*[-*]\\s+(.+)';
// Determine where the action items section begins.
// We look for a line containing "action item" (case-insensitive).
Integer startIndex = 0;
for (Integer i = 0; i < lines.size(); i++) {
if (lines[i].toLowerCase().contains('action item')) {
startIndex = i + 1; // start parsing from the line after the header
break;
}
}
// If no header found, scan the entire text for list items
if (startIndex == 0) {
startIndex = 0;
}
String currentItem = null;
for (Integer i = startIndex; i < lines.size(); i++) {
String line = lines[i];
String trimmed = line.trim();
// Skip empty lines between items
if (String.isBlank(trimmed)) {
// If we have a pending item, finalize it
if (currentItem != null) {
items.add(currentItem.trim());
currentItem = null;
}
continue;
}
// Check for numbered item
Boolean isNumbered = Pattern.matches(numberedPattern, trimmed);
// Check for bulleted item
Boolean isBulleted = Pattern.matches(bulletPattern, trimmed);
if (isNumbered || isBulleted) {
// Save any previous item
if (currentItem != null) {
items.add(currentItem.trim());
}
// Extract the text after the marker
if (isNumbered) {
currentItem = trimmed.replaceFirst('^\\s*\\d+[.\\)]\\s+', '');
} else {
currentItem = trimmed.replaceFirst('^\\s*[-*]\\s+', '');
}
} else if (currentItem != null) {
// Continuation line for a multi-line action item
currentItem += ' ' + trimmed;
}
// Lines before any list item starts (and after the header) are ignored
}
// Don't forget the last item
if (currentItem != null) {
items.add(currentItem.trim());
}
return items;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment