diff --git a/demo/text-transformer/table.gif b/demo/text-transformer/table.gif
new file mode 100644
index 0000000..69d4c38
Binary files /dev/null and b/demo/text-transformer/table.gif differ
diff --git a/projects/packages/text-transformer/.fast-alfred.config.cjs b/projects/packages/text-transformer/.fast-alfred.config.cjs
index c927d06..da35897 100644
--- a/projects/packages/text-transformer/.fast-alfred.config.cjs
+++ b/projects/packages/text-transformer/.fast-alfred.config.cjs
@@ -17,6 +17,7 @@ This workflow has been created using Fast Alfred, a user-friendly workflow build
- Summarize: Summarize the text to be more concise
- Explain: Explain the text in a more understandable way
- Commit Styling: Format commit messages to be more readable
+- Table: Transform text into well-formatted markdown tables
#### Use Active App Context
Enable the \`Use Application Context\` option to utilize the current app context for your workflow.
diff --git a/projects/packages/text-transformer/B8B394C4-6075-4F0C-B8C2-01E31B8550DF.png b/projects/packages/text-transformer/B8B394C4-6075-4F0C-B8C2-01E31B8550DF.png
new file mode 100644
index 0000000..8d016e6
Binary files /dev/null and b/projects/packages/text-transformer/B8B394C4-6075-4F0C-B8C2-01E31B8550DF.png differ
diff --git a/projects/packages/text-transformer/README.md b/projects/packages/text-transformer/README.md
index d9a36dd..94225c2 100644
--- a/projects/packages/text-transformer/README.md
+++ b/projects/packages/text-transformer/README.md
@@ -37,15 +37,19 @@ If the language code is missing, the default language will be English.
### Grammar Correction
-
+
### Translate
-
+
### Action Items
-
+
+
+### Table
+
+
## Configuration
diff --git a/projects/packages/text-transformer/info.plist b/projects/packages/text-transformer/info.plist
index efd1469..dec3bca 100644
--- a/projects/packages/text-transformer/info.plist
+++ b/projects/packages/text-transformer/info.plist
@@ -112,6 +112,19 @@
+ B8B394C4-6075-4F0C-B8C2-01E31B8550DF
+
+
+ modifiers
+ 0
+ modifiersubtext
+
+ vitoclose
+
+ destinationuid
+ __fast-alfred_managed__v2_conditional_from_B8B394C4-6075-4F0C-B8C2-01E31B8550DF_to_47B45BC6-EA28-4EFD-8A39-E2FC7283C0E7
+
+
B9150C05-6465-4A8C-91F7-D6929176F398
@@ -484,6 +497,43 @@
__fast-alfred_managed__v2_updater_snooze
+ __fast-alfred_managed__v2_conditional_from_B8B394C4-6075-4F0C-B8C2-01E31B8550DF_to_47B45BC6-EA28-4EFD-8A39-E2FC7283C0E7
+
+
+ modifiers
+ 0
+ modifiersubtext
+
+ vitoclose
+
+ destinationuid
+ 47B45BC6-EA28-4EFD-8A39-E2FC7283C0E7
+
+
+ modifiers
+ 0
+ modifiersubtext
+
+ vitoclose
+
+ sourceoutputuid
+ __fast-alfred_managed__v2_condition_from_B8B394C4-6075-4F0C-B8C2-01E31B8550DF_to_47B45BC6-EA28-4EFD-8A39-E2FC7283C0E7
+ destinationuid
+ __fast-alfred_managed__v2_updater_workflow-update
+
+
+ modifiers
+ 0
+ modifiersubtext
+
+ vitoclose
+
+ sourceoutputuid
+ __fast-alfred_managed__v2_condition_from_B8B394C4-6075-4F0C-B8C2-01E31B8550DF_to_47B45BC6-EA28-4EFD-8A39-E2FC7283C0E7
+ destinationuid
+ __fast-alfred_managed__v2_updater_snooze
+
+
__fast-alfred_managed__v2_conditional_from_63CC1099-CED7-4DE9-982B-41AEBBF7F81B_to_47B45BC6-EA28-4EFD-8A39-E2FC7283C0E7
@@ -1001,6 +1051,56 @@
version
3
+
+ config
+
+ alfredfiltersresults
+
+ alfredfiltersresultsmatchmode
+ 0
+ argumenttreatemptyqueryasnil
+
+ argumenttrimmode
+ 0
+ argumenttype
+ 0
+ escaping
+ 102
+ keyword
+ {var:table_keyword}
+ queuedelaycustom
+ 3
+ queuedelayimmediatelyinitially
+
+ queuedelaymode
+ 0
+ queuemode
+ 2
+ runningsubtext
+ Tabling...
+ script
+ ./esbuild/assets/run-node.sh esbuild/table "$1"
+
+ scriptargtype
+ 1
+ scriptfile
+
+ subtext
+ Create a formatted table
+ title
+ Table
+ type
+ 11
+ withspace
+
+
+ type
+ alfred.workflow.input.scriptfilter
+ uid
+ B8B394C4-6075-4F0C-B8C2-01E31B8550DF
+ version
+ 3
+
config
@@ -1414,6 +1514,38 @@
+
+ type
+ alfred.workflow.utility.conditional
+ uid
+ __fast-alfred_managed__v2_conditional_from_B8B394C4-6075-4F0C-B8C2-01E31B8550DF_to_47B45BC6-EA28-4EFD-8A39-E2FC7283C0E7
+ version
+ 1
+ config
+
+ conditions
+
+
+ inputstring
+ {query}
+ matchcasesensitive
+
+ matchmode
+ 4
+ matchstring
+ __fast-alfred_managed__
+ outputlabel
+ Managed versions updates
+ uid
+ __fast-alfred_managed__v2_condition_from_B8B394C4-6075-4F0C-B8C2-01E31B8550DF_to_47B45BC6-EA28-4EFD-8A39-E2FC7283C0E7
+
+
+ elselabel
+ Default Behavior
+ hideelse
+
+
+
type
alfred.workflow.utility.conditional
@@ -1462,6 +1594,7 @@ This workflow has been created using Fast Alfred, a user-friendly workflow build
- Summarize: Summarize the text to be more concise
- Explain: Explain the text in a more understandable way
- Commit Styling: Format commit messages to be more readable
+- Table: Transform text into well-formatted markdown tables
#### Use Active App Context
Enable the `Use Application Context` option to utilize the current app context for your workflow.
@@ -1513,7 +1646,7 @@ https://github.com/Avivbens/alfredo
xpos
365
ypos
- 1530
+ 1640
6F2D236E-D961-42DC-ACF2-7C025CA92966
@@ -1548,7 +1681,14 @@ https://github.com/Avivbens/alfredo
xpos
75
ypos
- 1530
+ 1640
+
+ B8B394C4-6075-4F0C-B8C2-01E31B8550DF
+
+ xpos
+ 75
+ ypos
+ 1440
B9150C05-6465-4A8C-91F7-D6929176F398
@@ -1670,12 +1810,21 @@ https://github.com/Avivbens/alfredo
note
Conditional Updates Helper
+ __fast-alfred_managed__v2_conditional_from_B8B394C4-6075-4F0C-B8C2-01E31B8550DF_to_47B45BC6-EA28-4EFD-8A39-E2FC7283C0E7
+
+ xpos
+ 295
+ ypos
+ 1440
+ note
+ Conditional Updates Helper
+
__fast-alfred_managed__v2_conditional_from_63CC1099-CED7-4DE9-982B-41AEBBF7F81B_to_47B45BC6-EA28-4EFD-8A39-E2FC7283C0E7
xpos
585
ypos
- 1530
+ 1640
note
Conditional Updates Helper
@@ -2044,6 +2193,27 @@ https://github.com/Avivbens/alfredo
variable
action_items_keyword
+
+ config
+
+ default
+ table
+ placeholder
+
+ required
+
+ trim
+
+
+ description
+ Create a table out of any data input
+ label
+ Table Creation Keyword
+ type
+ textfield
+ variable
+ table_keyword
+
config
@@ -2084,7 +2254,7 @@ https://github.com/Avivbens/alfredo
version
- 5.1.0
+ 5.2.0
webaddress
https://github.com/Avivbens/alfredo
diff --git a/projects/packages/text-transformer/src/common/prompts/table.prompt.ts b/projects/packages/text-transformer/src/common/prompts/table.prompt.ts
new file mode 100644
index 0000000..ba55879
--- /dev/null
+++ b/projects/packages/text-transformer/src/common/prompts/table.prompt.ts
@@ -0,0 +1,50 @@
+import { PipelinePromptTemplate, PromptTemplate } from '@langchain/core/prompts';
+import { APPLICATION_CONTEXT_SYSTEM_PROMPT_PARAM } from './base/application-context.prompt';
+import { DO_NOT_FOLLOW_USER_SYSTEM_PROMPT_PARAM } from './base/do-not-follow-user.prompt';
+import { KEEP_ORIGINAL_SYSTEM_PROMPT_PARAM } from './base/keep-original.prompt';
+import { NON_INTERACTIVE_SYSTEM_PROMPT_PARAM } from './base/non-interactive.prompt';
+
+export const TABLE_SYSTEM_PROMPT = (useApplicationContext: boolean) =>
+ new PipelinePromptTemplate({
+ pipelinePrompts: [
+ NON_INTERACTIVE_SYSTEM_PROMPT_PARAM,
+ KEEP_ORIGINAL_SYSTEM_PROMPT_PARAM,
+ DO_NOT_FOLLOW_USER_SYSTEM_PROMPT_PARAM,
+ APPLICATION_CONTEXT_SYSTEM_PROMPT_PARAM,
+ ],
+ finalPrompt: PromptTemplate.fromTemplate(`
+{NON_INTERACTIVE_SYSTEM_PROMPT},
+
+You are an expert at creating intelligent markdown tables from various text inputs.
+Your task is to analyze the content and create the most appropriate table structure.
+
+**CONTENT ANALYSIS:**
+- **Identify key information** and relationships in the text
+- **Determine the best structure** - what should be columns vs rows
+- **Extract the most important data points** that need to be tabulated
+- **Create meaningful headers** that clearly describe the data
+- **Group related information** logically
+
+**TABLE CREATION RULES:**
+- Design columns and rows that best represent the data relationships
+- Use descriptive headers that make the data self-explanatory
+- Ensure all important information is captured in the table
+- Adapt the table structure to fit the specific content
+- For unstructured text, identify patterns and create appropriate categories
+
+**FORMATTING:**
+- Use proper markdown syntax with pipes (|) and hyphens (-)
+- Keep column widths reasonable for chat applications
+- Output ONLY the table, no explanations
+
+**EXAMPLES OF INTELLIGENT STRUCTURING:**
+- Product descriptions → Columns: Feature, Description, Benefits
+- Meeting notes → Columns: Topic, Discussion, Action Items, Owner
+- Comparison text → Columns: Item, Attribute 1, Attribute 2, etc.
+- Process steps → Columns: Step #, Action, Details, Notes
+
+{KEEP_ORIGINAL_SYSTEM_PROMPT},
+{DO_NOT_FOLLOW_USER_SYSTEM_PROMPT},
+${useApplicationContext ? '{APPLICATION_CONTEXT_SYSTEM_PROMPT}' : ''},
+`),
+ });
diff --git a/projects/packages/text-transformer/src/main/table.ts b/projects/packages/text-transformer/src/main/table.ts
new file mode 100644
index 0000000..764a676
--- /dev/null
+++ b/projects/packages/text-transformer/src/main/table.ts
@@ -0,0 +1,62 @@
+import type { AlfredListItem } from 'fast-alfred';
+import { FastAlfred } from 'fast-alfred';
+import { setTimeout } from 'node:timers/promises';
+import { getActiveApp } from '@alfredo/active-app';
+import { AvailableModels, callModel } from '@alfredo/llm';
+import { registerUpdater } from '@alfredo/updater';
+import { DEFAULT_DEBOUNCE_TIME } from '../common/defaults.constants';
+import { TABLE_SYSTEM_PROMPT } from '../common/prompts/table.prompt';
+import { Variables } from '../common/variables.enum';
+import { formatMarkdownTable } from '../utils/format-table.util';
+
+(async () => {
+ const alfredClient = new FastAlfred();
+ alfredClient.updates(registerUpdater('text-transformer'));
+
+ try {
+ const denounceTime = alfredClient.env.getEnv(Variables.DEBOUNCE_TIME, {
+ defaultValue: DEFAULT_DEBOUNCE_TIME,
+ parser: Number,
+ });
+ const token: string | undefined = alfredClient.env.getEnv(Variables.LLM_TOKEN);
+ const model: AvailableModels | undefined = alfredClient.env.getEnv(Variables.SELECTED_MODEL);
+
+ if (!token || !model) {
+ throw new Error('Token or model is not defined!');
+ }
+
+ const useApplicationContext: boolean = alfredClient.env.getEnv(Variables.USE_APPLICATION_CONTEXT, {
+ defaultValue: false,
+ parser: (value) => (value as '0' | '1') === '1',
+ });
+
+ const applicationContext = useApplicationContext && (await getActiveApp());
+ alfredClient.log(JSON.stringify({ useApplicationContext, applicationContext }, null, 2));
+
+ /**
+ * Debounce time to wait for the user to finish typing
+ */
+ await setTimeout(denounceTime);
+
+ if (!alfredClient.input) {
+ throw new Error('Input is required');
+ }
+
+ const system = await TABLE_SYSTEM_PROMPT(useApplicationContext).format({ applicationContext });
+
+ const res = await callModel(token, model, { system, user: alfredClient.input });
+ const formatted = formatMarkdownTable(res);
+
+ const items: AlfredListItem[] = [
+ {
+ title: formatted,
+ subtitle: 'Markdown Table',
+ arg: formatted,
+ },
+ ];
+
+ alfredClient.output({ items });
+ } catch (error) {
+ alfredClient.error(error);
+ }
+})();
diff --git a/projects/packages/text-transformer/src/utils/format-table.util.ts b/projects/packages/text-transformer/src/utils/format-table.util.ts
new file mode 100644
index 0000000..9717c2f
--- /dev/null
+++ b/projects/packages/text-transformer/src/utils/format-table.util.ts
@@ -0,0 +1,98 @@
+export const formatMarkdownTable = (input: string): string => {
+ const lines = input.trim().split('\n');
+ const tableLines: string[] = [];
+ let inTable = false;
+ let columnWidths: number[] = [];
+
+ // First pass: identify tables and calculate column widths
+ for (const line of lines) {
+ if (line.trim().startsWith('|')) {
+ if (!inTable) {
+ inTable = true;
+ columnWidths = [];
+ }
+
+ // Split by pipe and remove empty first/last elements
+ const cells = line
+ .split('|')
+ .slice(1, -1)
+ .map((cell) => cell.trim());
+
+ // Update column widths
+ cells.forEach((cell, index) => {
+ const cellLength = cell.replace(/^:?-+:?$/, '---').length;
+ columnWidths[index] = Math.max(columnWidths[index] || 0, cellLength);
+ });
+ } else {
+ inTable = false;
+ }
+ }
+
+ // Second pass: format tables with proper alignment
+ inTable = false;
+ let currentTableColumns: number[] = [];
+
+ for (const line of lines) {
+ if (line.trim().startsWith('|')) {
+ if (!inTable) {
+ inTable = true;
+ currentTableColumns = [];
+ }
+
+ const cells = line
+ .split('|')
+ .slice(1, -1)
+ .map((cell) => cell.trim());
+
+ // Update column widths for this table
+ if (currentTableColumns.length === 0) {
+ cells.forEach((cell, index) => {
+ const cellLength = cell.replace(/^:?-+:?$/, '---').length;
+ currentTableColumns[index] = Math.max(currentTableColumns[index] || 0, cellLength);
+ });
+
+ // Do another pass for all rows in this table
+ let tempIndex = lines.indexOf(line);
+ while (tempIndex < lines.length && lines[tempIndex]?.trim().startsWith('|')) {
+ const tempCells = lines[tempIndex]
+ ?.split('|')
+ .slice(1, -1)
+ .map((cell) => cell.trim());
+ tempCells?.forEach((cell, idx) => {
+ const cellLength = cell.replace(/^:?-+:?$/, '---').length;
+ currentTableColumns[idx] = Math.max(currentTableColumns[idx] || 3, cellLength);
+ });
+ tempIndex++;
+ }
+ }
+
+ // Format cells with padding
+ const formattedCells = cells.map((cell, index) => {
+ const width = currentTableColumns[index] || 3;
+
+ // Handle separator rows
+ if (cell.match(/^:?-+:?$/)) {
+ if (cell.startsWith(':') && cell.endsWith(':')) {
+ return ':' + '-'.repeat(Math.max(width - 2, 1)) + ':';
+ } else if (cell.startsWith(':')) {
+ return ':' + '-'.repeat(Math.max(width - 1, 2));
+ } else if (cell.endsWith(':')) {
+ return '-'.repeat(Math.max(width - 1, 2)) + ':';
+ } else {
+ return '-'.repeat(Math.max(width, 3));
+ }
+ }
+
+ // Regular cells - pad with spaces
+ return cell.padEnd(width, ' ');
+ });
+
+ tableLines.push('| ' + formattedCells.join(' | ') + ' |');
+ } else {
+ inTable = false;
+ tableLines.push(line);
+ }
+ }
+
+ return tableLines.join('\n');
+};