# subflow: Main (global) # Local AI email triage - example call of the local function Triage_Email: the text of each email of a folder goes in, # its category, urgency, one-sentence summary and reply flag come out. The model runs on this PC (Ollama): no email leaves it. # Main shows the reusable way: read each file, call the function, test its flag, use its outputs. The inbox is never # changed: each email is COPIED into a folder named after its category, and a report lists every decision. # The one-block demo is the global subflow Test, autonomous: run it alone with 'Run from here' on its first action. # Regions: cyan = the values to change, red = the guards, green = reading, purple = the processing, gold = the output. @@regionColor: '#57FFE1' **REGION 1. Variables: inbox, output folder, local model # The folder of emails to triage: one .txt file per email (a subject line, an empty line, the body) SET txt_InboxFolder TO $'''C:\\Work\\SharedInbox''' # The folder that receives the report and one copy of each email, in a subfolder per category SET txt_OutputFolder TO $'''C:\\Work\\SharedInbox\\Triaged''' # The Ollama endpoint on this PC and the model it serves (run: ollama pull llama3.2:3b, once) SET txt_Endpoint TO $'''http://localhost:11434/v1''' SET txt_Model TO $'''llama3.2:3b''' **ENDREGION @@regionColor: '#F4B6B6' **REGION 2. Guards: the inbox must exist and hold emails, the output folder is created when missing IF (Folder.IfFolderExists.DoesNotExist Path: txt_InboxFolder) THEN Display.ShowMessageDialog.ShowMessage Title: $'''Local AI email triage''' Message: $'''Inbox folder not found: %txt_InboxFolder%. Put the sample emails there, or fix txt_InboxFolder in the first region.''' Icon: Display.Icon.ErrorIcon Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> _txt_ButtonPressed EXIT Code: 1 END Folder.GetFiles Folder: txt_InboxFolder FileFilter: $'''*.txt''' IncludeSubfolders: False FailOnAccessDenied: True SortBy1: Folder.SortBy.Name SortDescending1: False Files=> lst_Emails IF lst_Emails.Count = 0 THEN Display.ShowMessageDialog.ShowMessage Title: $'''Local AI email triage''' Message: $'''No .txt email in %txt_InboxFolder%.''' Icon: Display.Icon.Warning Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> _txt_ButtonPressed EXIT Code: 0 END IF (Folder.IfFolderExists.DoesNotExist Path: txt_OutputFolder) THEN # Create folder needs an existing parent: split the path into parent + name first File.GetPathPart File: txt_OutputFolder Directory=> fold_OutputParent FileName=> txt_OutputFolderName Folder.Create FolderPath: fold_OutputParent FolderName: txt_OutputFolderName Folder=> _fold_Output END **ENDREGION @@regionColor: '#D9C3E9' **REGION 3. Triage: one call per email, a copy in the folder of its category, one row in the report Variables.CreateNewDatatable InputTable: { ^['File', 'Category', 'Urgency', 'ReplyNeeded', 'Summary', 'Seconds'], ['', '', '', '', '', ''] } DataTable=> tbl_Report SET txt_Lines TO $'''''' SET num_ToReview TO 0 DateTime.GetCurrentDateTime.Local DateTimeFormat: DateTime.DateTimeFormat.DateAndTime CurrentDateTime=> date_Start LOOP FOREACH file_Email IN lst_Emails File.ReadTextFromFile.ReadText File: file_Email Encoding: File.TextFileEncoding.UTF8 Content=> txt_EmailText CALL Triage_Email in_loc_txt_EmailText: txt_EmailText in_loc_txt_Endpoint: txt_Endpoint in_loc_txt_Model: txt_Model out_loc_txt_Category=> txt_Category out_loc_txt_Urgency=> txt_Urgency out_loc_txt_Summary=> txt_Summary out_loc_bool_ReplyNeeded=> bool_ReplyNeeded out_loc_bool_Ok=> bool_Ok out_loc_txt_Error=> txt_Error out_loc_num_ElapsedMs=> num_ElapsedMs out_loc_num_Tokens=> num_Tokens IF bool_Ok = False THEN # The function never throws: an email it could not triage goes to a person, with the reason in the report SET txt_Category TO $'''to_review''' SET txt_Summary TO txt_Error SET num_ToReview TO num_ToReview + 1 END SET txt_CategoryFolder TO $'''%txt_OutputFolder%\\%txt_Category%''' IF (Folder.IfFolderExists.DoesNotExist Path: txt_CategoryFolder) THEN Folder.Create FolderPath: txt_OutputFolder FolderName: txt_Category Folder=> _fold_Category END File.Copy Files: file_Email Destination: txt_CategoryFolder IfFileExists: File.IfExists.Overwrite CopiedFiles=> _lst_Copied SET txt_FileName TO file_Email.Name SET num_SecondsRaw TO num_ElapsedMs / 1000 Variables.TruncateNumber.RoundNumber Number: num_SecondsRaw DecimalPlaces: 1 Result=> num_Seconds Variables.AddRowToDataTable.AppendRowToDataTable DataTable: tbl_Report RowToAdd: [txt_FileName, txt_Category, txt_Urgency, bool_ReplyNeeded, txt_Summary, num_Seconds] Text.AppendLine Text: txt_Lines LineToAppend: $'''%txt_FileName%: %txt_Category% (%txt_Urgency%) - %txt_Summary%''' Result=> txt_Lines END DateTime.GetCurrentDateTime.Local DateTimeFormat: DateTime.DateTimeFormat.DateAndTime CurrentDateTime=> date_End DateTime.Subtract FromDate: date_End SubstractDate: date_Start TimeUnit: DateTime.DifferenceTimeUnit.Seconds TimeDifference=> num_TotalSeconds **ENDREGION @@regionColor: '#FFD966' **REGION 4. Output: the report as a CSV file, the decisions on screen Variables.DeleteEmptyRowsFromDataTable DataTable: tbl_Report SET txt_ReportFile TO $'''%txt_OutputFolder%\\triage-report.csv''' File.WriteToCSVFile.WriteCSV VariableToWrite: tbl_Report CSVFile: txt_ReportFile CsvFileEncoding: File.CSVEncoding.UTF8 IncludeColumnNames: True IfFileExists: File.IfFileExists.Overwrite ColumnsSeparator: File.CSVColumnsSeparator.Semicolon Variables.TruncateNumber.RoundNumber Number: num_TotalSeconds DecimalPlaces: 0 Result=> num_TotalRounded Display.ShowMessageDialog.ShowMessage Title: $'''Local AI email triage''' Message: $'''%lst_Emails.Count% emails triaged in %num_TotalRounded% s by %txt_Model% on this PC, %num_ToReview% to review. %txt_Lines% Report: %txt_ReportFile%''' Icon: Display.Icon.Information Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> _txt_ButtonPressed **ENDREGION