@loopstack/meeting-notes-example-workflow 0.15.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+
2
+ > @loopstack/meeting-notes-example-workflow@0.18.0 build
3
+ > nest build
4
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # @loopstack/meeting-notes-example-workflow
2
+
3
+ ## 0.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#13](https://github.com/loopstack-ai/loopstack/pull/13) [`13ab1a7`](https://github.com/loopstack-ai/loopstack/commit/13ab1a792a12bc46e0a21cf1ac038a7e69c566df) Thanks [@jakobklippel](https://github.com/jakobklippel)! - Added from external repository
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`eea1b9f`](https://github.com/loopstack-ai/loopstack/commit/eea1b9ff10eaf8eda2a1bd4c2042148f964d5a39), [`e556176`](https://github.com/loopstack-ai/loopstack/commit/e5561769b365218f1ffdc890b887e7b607d06101), [`4ee0af1`](https://github.com/loopstack-ai/loopstack/commit/4ee0af1536e7802eb9d69a788c10184e3c5a7a11), [`3fd1db5`](https://github.com/loopstack-ai/loopstack/commit/3fd1db5d0de8ad26e3e22348f7f1593024a74273)]:
12
+ - @loopstack/ai-module@0.18.0
13
+ - @loopstack/core-ui-module@0.18.0
14
+ - @loopstack/core@0.18.0
15
+ - @loopstack/common@0.18.0
16
+
17
+ ## 0.18.0-rc.0
18
+
19
+ ### Minor Changes
20
+
21
+ - [#13](https://github.com/loopstack-ai/loopstack/pull/13) [`13ab1a7`](https://github.com/loopstack-ai/loopstack/commit/13ab1a792a12bc46e0a21cf1ac038a7e69c566df) Thanks [@jakobklippel](https://github.com/jakobklippel)! - Added from external repository
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [[`eea1b9f`](https://github.com/loopstack-ai/loopstack/commit/eea1b9ff10eaf8eda2a1bd4c2042148f964d5a39)]:
26
+ - @loopstack/ai-module@0.18.0-rc.0
27
+ - @loopstack/common@0.18.0-rc.1
28
+ - @loopstack/core@0.18.0-rc.1
package/README.md CHANGED
@@ -1,2 +1,241 @@
1
+ # @loopstack/meeting-notes-example-workflow
1
2
 
2
- # Loopstack Dynamic Routing Example Workflow
3
+ > A module for the [Loopstack AI](https://loopstack.ai) automation framework.
4
+
5
+ This module provides an example workflow demonstrating how to build human-in-the-loop AI workflows with manual triggers and interactive documents.
6
+
7
+ ## Overview
8
+
9
+ The Meeting Notes Example Workflow shows how to create workflows that pause for user input and allow users to review and edit AI-generated content. It takes unstructured meeting notes and uses AI to extract structured information like date, participants, decisions, and action items.
10
+
11
+ By using this workflow as a reference, you'll learn how to:
12
+
13
+ - Use manual triggers to pause workflows for user input
14
+ - Create interactive documents with action buttons
15
+ - Handle transition payloads from user interactions
16
+ - Transform unstructured text into structured data with AI
17
+ - Build review-and-confirm patterns for AI outputs
18
+
19
+ This example is essential for developers building workflows that require human oversight or approval steps.
20
+
21
+ ## Installation
22
+
23
+ ### Prerequisites
24
+
25
+ Create a new Loopstack project if you haven't already:
26
+
27
+ ```bash
28
+ npx create-loopstack-app my-project
29
+ cd my-project
30
+ ```
31
+
32
+ Start Environment
33
+
34
+ ```bash
35
+ cd my-project
36
+ docker compose up -d
37
+ ```
38
+
39
+ ### Add the Module
40
+
41
+ ```bash
42
+ loopstack add @loopstack/meeting-notes-example-workflow
43
+ ```
44
+
45
+ This copies the source files into your `src` directory.
46
+
47
+ > Using the `loopstack add` command is a great way to explore the code to learn new concepts or add own customizations.
48
+
49
+ ## Setup
50
+
51
+ ### 1. Import the Module
52
+
53
+ Add `MeetingNotesExampleModule` to your `default.module.ts` (included in the skeleton app) or to your own module:
54
+
55
+ ```typescript
56
+ import { Module } from '@nestjs/common';
57
+ import { AiModule } from '@loopstack/ai-module';
58
+ import { LoopCoreModule } from '@loopstack/core';
59
+ import { CoreUiModule } from '@loopstack/core-ui-module';
60
+ import { MeetingNotesExampleModule } from './@loopstack/meeting-notes-example-workflow';
61
+ import { DefaultWorkspace } from './default.workspace';
62
+
63
+ @Module({
64
+ imports: [LoopCoreModule, MeetingNotesExampleModule],
65
+ providers: [DefaultWorkspace],
66
+ })
67
+ export class DefaultModule {}
68
+ ```
69
+
70
+ ### 2. Register in Your Workspace
71
+
72
+ Add the workflow to your workspace class using the `@Workflow()` decorator:
73
+
74
+ ```typescript
75
+ import { Injectable } from '@nestjs/common';
76
+ import { BlockConfig, Workflow } from '@loopstack/common';
77
+ import { WorkspaceBase } from '@loopstack/core';
78
+ import { MeetingNotesWorkflow } from './@loopstack/meeting-notes-example-workflow';
79
+
80
+ @Injectable()
81
+ @BlockConfig({
82
+ config: {
83
+ title: 'My Workspace',
84
+ description: 'A workspace with the meeting notes example workflow',
85
+ },
86
+ })
87
+ export class MyWorkspace extends WorkspaceBase {
88
+ @Workflow() meetingNotesWorkflow: MeetingNotesWorkflow;
89
+ }
90
+ ```
91
+
92
+ ### 3. Configure API Key
93
+
94
+ Set your OpenAI API key as an environment variable:
95
+
96
+ ```bash
97
+ OPENAI_API_KEY=sk-...
98
+ ```
99
+
100
+ ## How It Works
101
+
102
+ ### Workflow Flow
103
+
104
+ 1. **Start** - User provides unstructured meeting notes
105
+ 2. **Wait for Input** - User can edit the notes, then clicks "Optimize Notes"
106
+ 3. **AI Processing** - LLM extracts structured information into a formatted document
107
+ 4. **Review** - User reviews and can edit the structured output
108
+ 5. **Confirm** - User clicks "Confirm" to finalize
109
+
110
+ ### Key Concepts
111
+
112
+ #### 1. Manual Triggers
113
+
114
+ Use `trigger: manual` to pause the workflow and wait for user interaction:
115
+
116
+ ```yaml
117
+ - id: user_response
118
+ from: waiting_for_response
119
+ to: response_received
120
+ trigger: manual
121
+ ```
122
+
123
+ The workflow pauses at `waiting_for_response` until the user triggers the transition.
124
+
125
+ #### 2. Document Actions with Buttons
126
+
127
+ Add action buttons to documents that trigger transitions:
128
+
129
+ ```yaml
130
+ ui:
131
+ form:
132
+ properties:
133
+ text:
134
+ title: Text
135
+ widget: textarea
136
+ actions:
137
+ - type: button
138
+ widget: button
139
+ transition: user_response
140
+ options:
141
+ label: 'Optimize Notes'
142
+ ```
143
+
144
+ When clicked, the button triggers the `user_response` transition with the current document content.
145
+
146
+ #### 3. Transition Payloads
147
+
148
+ Access user input from the transition payload:
149
+
150
+ ```yaml
151
+ - id: user_response
152
+ trigger: manual
153
+ call:
154
+ - tool: createDocument
155
+ args:
156
+ update:
157
+ content: ${ transition.payload }
158
+ assign:
159
+ meetingNotes: ${ result.data.content }
160
+ ```
161
+
162
+ The `transition.payload` contains the document content when the user clicked the button.
163
+
164
+ #### 4. Structured Output Documents
165
+
166
+ Define complex document schemas for structured AI output:
167
+
168
+ ```typescript
169
+ export const OptimizedMeetingNotesDocumentSchema = z.object({
170
+ date: z.string(),
171
+ summary: z.string(),
172
+ participants: z.array(z.string()),
173
+ decisions: z.array(z.string()),
174
+ actionItems: z.array(z.string()),
175
+ });
176
+ ```
177
+
178
+ #### 5. Array Fields with Collapsible UI
179
+
180
+ Configure array fields with custom item titles and collapsed display:
181
+
182
+ ```yaml
183
+ ui:
184
+ form:
185
+ properties:
186
+ participants:
187
+ title: Participants
188
+ collapsed: true
189
+ items:
190
+ title: Participant
191
+ actionItems:
192
+ title: Action Items
193
+ collapsed: true
194
+ items:
195
+ title: Action Item
196
+ ```
197
+
198
+ #### 6. AI Document Generation
199
+
200
+ Use `aiGenerateDocument` to populate a structured document:
201
+
202
+ ```yaml
203
+ - tool: aiGenerateDocument
204
+ args:
205
+ llm:
206
+ provider: openai
207
+ model: gpt-4o
208
+ response:
209
+ id: final
210
+ document: optimizedNotesDocument
211
+ prompt: |
212
+ Extract all information from the provided meeting notes into the structured document.
213
+
214
+ <Meeting Notes>
215
+ {{ meetingNotes.text }}
216
+ </Meeting Notes>
217
+ assign:
218
+ optimizedNotes: ${ result.data.content }
219
+ ```
220
+
221
+ The LLM automatically fills in the document fields based on the schema.
222
+
223
+ ## Dependencies
224
+
225
+ This workflow uses the following Loopstack modules:
226
+
227
+ - `@loopstack/core` - Core framework functionality
228
+ - `@loopstack/core-ui-module` - Provides `CreateDocument` tool
229
+ - `@loopstack/ai-module` - Provides `AiGenerateDocument` tool
230
+
231
+ ## About
232
+
233
+ Author: [Jakob Klippel](https://www.linkedin.com/in/jakob-klippel/)
234
+
235
+ License: Apache-2.0
236
+
237
+ ### Additional Resources
238
+
239
+ - [Loopstack Documentation](https://loopstack.ai/docs)
240
+ - [Getting Started with Loopstack](https://loopstack.ai/docs/getting-started)
241
+ - Find more Loopstack examples in the [Loopstack Registry](https://loopstack.ai/registry)
@@ -2,10 +2,6 @@ import { z } from 'zod';
2
2
  import { DocumentBase } from '@loopstack/core';
3
3
  export declare const MeetingNotesDocumentSchema: z.ZodObject<{
4
4
  text: z.ZodString;
5
- }, "strip", z.ZodTypeAny, {
6
- text?: string;
7
- }, {
8
- text?: string;
9
- }>;
5
+ }, z.core.$strip>;
10
6
  export declare class MeetingNotesDocument extends DocumentBase {
11
7
  }
@@ -7,10 +7,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  };
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.MeetingNotesDocument = exports.MeetingNotesDocumentSchema = void 0;
10
- const common_1 = require("@loopstack/common");
10
+ const common_1 = require("@nestjs/common");
11
11
  const zod_1 = require("zod");
12
+ const common_2 = require("@loopstack/common");
12
13
  const core_1 = require("@loopstack/core");
13
- const common_2 = require("@nestjs/common");
14
14
  exports.MeetingNotesDocumentSchema = zod_1.z.object({
15
15
  text: zod_1.z.string(),
16
16
  });
@@ -18,10 +18,10 @@ let MeetingNotesDocument = class MeetingNotesDocument extends core_1.DocumentBas
18
18
  };
19
19
  exports.MeetingNotesDocument = MeetingNotesDocument;
20
20
  exports.MeetingNotesDocument = MeetingNotesDocument = __decorate([
21
- (0, common_2.Injectable)(),
22
- (0, common_1.BlockConfig)({
21
+ (0, common_1.Injectable)(),
22
+ (0, common_2.BlockConfig)({
23
23
  configFile: __dirname + '/meeting-notes-document.yaml',
24
24
  }),
25
- (0, common_1.WithArguments)(exports.MeetingNotesDocumentSchema)
25
+ (0, common_2.WithArguments)(exports.MeetingNotesDocumentSchema)
26
26
  ], MeetingNotesDocument);
27
27
  //# sourceMappingURL=meeting-notes-document.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"meeting-notes-document.js","sourceRoot":"","sources":["../../src/documents/meeting-notes-document.ts"],"names":[],"mappings":";;;;;;;;;AAAA,8CAA+D;AAC/D,6BAAwB;AACxB,0CAA+C;AAC/C,2CAA4C;AAE/B,QAAA,0BAA0B,GAAG,OAAC,CAAC,MAAM,CAAC;IACjD,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;CACjB,CAAC,CAAC;AAOI,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,mBAAY;CAAG,CAAA;AAA5C,oDAAoB;+BAApB,oBAAoB;IALhC,IAAA,mBAAU,GAAE;IACZ,IAAA,oBAAW,EAAC;QACX,UAAU,EAAE,SAAS,GAAG,8BAA8B;KACvD,CAAC;IACD,IAAA,sBAAa,EAAC,kCAA0B,CAAC;GAC7B,oBAAoB,CAAwB"}
1
+ {"version":3,"file":"meeting-notes-document.js","sourceRoot":"","sources":["../../src/documents/meeting-notes-document.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAC5C,6BAAwB;AACxB,8CAA+D;AAC/D,0CAA+C;AAElC,QAAA,0BAA0B,GAAG,OAAC,CAAC,MAAM,CAAC;IACjD,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;CACjB,CAAC,CAAC;AAOI,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,mBAAY;CAAG,CAAA;AAA5C,oDAAoB;+BAApB,oBAAoB;IALhC,IAAA,mBAAU,GAAE;IACZ,IAAA,oBAAW,EAAC;QACX,UAAU,EAAE,SAAS,GAAG,8BAA8B;KACvD,CAAC;IACD,IAAA,sBAAa,EAAC,kCAA0B,CAAC;GAC7B,oBAAoB,CAAwB"}
@@ -10,4 +10,4 @@ ui:
10
10
  widget: button
11
11
  transition: user_response
12
12
  options:
13
- label: "Optimize Notes"
13
+ label: 'Optimize Notes'
@@ -3,21 +3,9 @@ import { DocumentBase } from '@loopstack/core';
3
3
  export declare const OptimizedMeetingNotesDocumentSchema: z.ZodObject<{
4
4
  date: z.ZodString;
5
5
  summary: z.ZodString;
6
- participants: z.ZodArray<z.ZodString, "many">;
7
- decisions: z.ZodArray<z.ZodString, "many">;
8
- actionItems: z.ZodArray<z.ZodString, "many">;
9
- }, "strip", z.ZodTypeAny, {
10
- date?: string;
11
- summary?: string;
12
- participants?: string[];
13
- decisions?: string[];
14
- actionItems?: string[];
15
- }, {
16
- date?: string;
17
- summary?: string;
18
- participants?: string[];
19
- decisions?: string[];
20
- actionItems?: string[];
21
- }>;
6
+ participants: z.ZodArray<z.ZodString>;
7
+ decisions: z.ZodArray<z.ZodString>;
8
+ actionItems: z.ZodArray<z.ZodString>;
9
+ }, z.core.$strip>;
22
10
  export declare class OptimizedNotesDocument extends DocumentBase {
23
11
  }
@@ -7,10 +7,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  };
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.OptimizedNotesDocument = exports.OptimizedMeetingNotesDocumentSchema = void 0;
10
- const common_1 = require("@loopstack/common");
10
+ const common_1 = require("@nestjs/common");
11
11
  const zod_1 = require("zod");
12
+ const common_2 = require("@loopstack/common");
12
13
  const core_1 = require("@loopstack/core");
13
- const common_2 = require("@nestjs/common");
14
14
  exports.OptimizedMeetingNotesDocumentSchema = zod_1.z.object({
15
15
  date: zod_1.z.string(),
16
16
  summary: zod_1.z.string(),
@@ -22,10 +22,10 @@ let OptimizedNotesDocument = class OptimizedNotesDocument extends core_1.Documen
22
22
  };
23
23
  exports.OptimizedNotesDocument = OptimizedNotesDocument;
24
24
  exports.OptimizedNotesDocument = OptimizedNotesDocument = __decorate([
25
- (0, common_2.Injectable)(),
26
- (0, common_1.BlockConfig)({
25
+ (0, common_1.Injectable)(),
26
+ (0, common_2.BlockConfig)({
27
27
  configFile: __dirname + '/optimized-notes-document.yaml',
28
28
  }),
29
- (0, common_1.WithArguments)(exports.OptimizedMeetingNotesDocumentSchema)
29
+ (0, common_2.WithArguments)(exports.OptimizedMeetingNotesDocumentSchema)
30
30
  ], OptimizedNotesDocument);
31
31
  //# sourceMappingURL=optimized-notes-document.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"optimized-notes-document.js","sourceRoot":"","sources":["../../src/documents/optimized-notes-document.ts"],"names":[],"mappings":";;;;;;;;;AAAA,8CAA+D;AAC/D,6BAAwB;AACxB,0CAA+C;AAC/C,2CAA4C;AAE/B,QAAA,mCAAmC,GAAG,OAAC,CAAC,MAAM,CAAC;IAC1D,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;IAChB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,YAAY,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;IACjC,SAAS,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;IAC9B,WAAW,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;CACjC,CAAC,CAAC;AAOI,IAAM,sBAAsB,GAA5B,MAAM,sBAAuB,SAAQ,mBAAY;CAAG,CAAA;AAA9C,wDAAsB;iCAAtB,sBAAsB;IALlC,IAAA,mBAAU,GAAE;IACZ,IAAA,oBAAW,EAAC;QACX,UAAU,EAAE,SAAS,GAAG,gCAAgC;KACzD,CAAC;IACD,IAAA,sBAAa,EAAC,2CAAmC,CAAC;GACtC,sBAAsB,CAAwB"}
1
+ {"version":3,"file":"optimized-notes-document.js","sourceRoot":"","sources":["../../src/documents/optimized-notes-document.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAC5C,6BAAwB;AACxB,8CAA+D;AAC/D,0CAA+C;AAElC,QAAA,mCAAmC,GAAG,OAAC,CAAC,MAAM,CAAC;IAC1D,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;IAChB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,YAAY,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;IACjC,SAAS,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;IAC9B,WAAW,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;CACjC,CAAC,CAAC;AAOI,IAAM,sBAAsB,GAA5B,MAAM,sBAAuB,SAAQ,mBAAY;CAAG,CAAA;AAA9C,wDAAsB;iCAAtB,sBAAsB;IALlC,IAAA,mBAAU,GAAE;IACZ,IAAA,oBAAW,EAAC;QACX,UAAU,EAAE,SAAS,GAAG,gCAAgC;KACzD,CAAC;IACD,IAAA,sBAAa,EAAC,2CAAmC,CAAC;GACtC,sBAAsB,CAAwB"}
@@ -33,4 +33,4 @@ ui:
33
33
  widget: button
34
34
  transition: confirm
35
35
  options:
36
- label: "Confirm"
36
+ label: 'Confirm'
@@ -8,26 +8,20 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.MeetingNotesExampleModule = void 0;
10
10
  const common_1 = require("@nestjs/common");
11
+ const ai_module_1 = require("@loopstack/ai-module");
11
12
  const core_1 = require("@loopstack/core");
12
13
  const core_ui_module_1 = require("@loopstack/core-ui-module");
13
- const ai_module_1 = require("@loopstack/ai-module");
14
- const meeting_notes_workflow_1 = require("./meeting-notes.workflow");
15
14
  const meeting_notes_document_1 = require("./documents/meeting-notes-document");
16
15
  const optimized_notes_document_1 = require("./documents/optimized-notes-document");
16
+ const meeting_notes_workflow_1 = require("./meeting-notes.workflow");
17
17
  let MeetingNotesExampleModule = class MeetingNotesExampleModule {
18
18
  };
19
19
  exports.MeetingNotesExampleModule = MeetingNotesExampleModule;
20
20
  exports.MeetingNotesExampleModule = MeetingNotesExampleModule = __decorate([
21
21
  (0, common_1.Module)({
22
22
  imports: [core_1.LoopCoreModule, core_ui_module_1.CoreUiModule, ai_module_1.AiModule],
23
- providers: [
24
- meeting_notes_workflow_1.MeetingNotesWorkflow,
25
- meeting_notes_document_1.MeetingNotesDocument,
26
- optimized_notes_document_1.OptimizedNotesDocument,
27
- ],
28
- exports: [
29
- meeting_notes_workflow_1.MeetingNotesWorkflow,
30
- ]
23
+ providers: [meeting_notes_workflow_1.MeetingNotesWorkflow, meeting_notes_document_1.MeetingNotesDocument, optimized_notes_document_1.OptimizedNotesDocument],
24
+ exports: [meeting_notes_workflow_1.MeetingNotesWorkflow],
31
25
  })
32
26
  ], MeetingNotesExampleModule);
33
27
  //# sourceMappingURL=meeting-notes-example.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"meeting-notes-example.module.js","sourceRoot":"","sources":["../src/meeting-notes-example.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,0CAAiD;AACjD,8DAAyD;AACzD,oDAAgD;AAChD,qEAAgE;AAChE,+EAA0E;AAC1E,mFAA8E;AAavE,IAAM,yBAAyB,GAA/B,MAAM,yBAAyB;CAAG,CAAA;AAA5B,8DAAyB;oCAAzB,yBAAyB;IAXrC,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,qBAAc,EAAE,6BAAY,EAAE,oBAAQ,CAAC;QACjD,SAAS,EAAE;YACT,6CAAoB;YACpB,6CAAoB;YACpB,iDAAsB;SACvB;QACD,OAAO,EAAE;YACP,6CAAoB;SACrB;KACF,CAAC;GACW,yBAAyB,CAAG"}
1
+ {"version":3,"file":"meeting-notes-example.module.js","sourceRoot":"","sources":["../src/meeting-notes-example.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,oDAAgD;AAChD,0CAAiD;AACjD,8DAAyD;AACzD,+EAA0E;AAC1E,mFAA8E;AAC9E,qEAAgE;AAOzD,IAAM,yBAAyB,GAA/B,MAAM,yBAAyB;CAAG,CAAA;AAA5B,8DAAyB;oCAAzB,yBAAyB;IALrC,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,qBAAc,EAAE,6BAAY,EAAE,oBAAQ,CAAC;QACjD,SAAS,EAAE,CAAC,6CAAoB,EAAE,6CAAoB,EAAE,iDAAsB,CAAC;QAC/E,OAAO,EAAE,CAAC,6CAAoB,CAAC;KAChC,CAAC;GACW,yBAAyB,CAAG"}
@@ -1,8 +1,8 @@
1
+ import { AiGenerateDocument } from '@loopstack/ai-module';
1
2
  import { WorkflowBase } from '@loopstack/core';
3
+ import { CreateDocument } from '@loopstack/core-ui-module';
2
4
  import { MeetingNotesDocument } from './documents/meeting-notes-document';
3
5
  import { OptimizedNotesDocument } from './documents/optimized-notes-document';
4
- import { AiGenerateDocument } from '@loopstack/ai-module';
5
- import { CreateDocument } from '@loopstack/core-ui-module';
6
6
  export declare class MeetingNotesWorkflow extends WorkflowBase {
7
7
  aiGenerateDocument: AiGenerateDocument;
8
8
  createDocument: CreateDocument;
@@ -10,13 +10,13 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.MeetingNotesWorkflow = void 0;
13
- const core_1 = require("@loopstack/core");
14
- const common_1 = require("@loopstack/common");
15
- const meeting_notes_document_1 = require("./documents/meeting-notes-document");
16
- const optimized_notes_document_1 = require("./documents/optimized-notes-document");
17
13
  const zod_1 = require("zod");
18
14
  const ai_module_1 = require("@loopstack/ai-module");
15
+ const common_1 = require("@loopstack/common");
16
+ const core_1 = require("@loopstack/core");
19
17
  const core_ui_module_1 = require("@loopstack/core-ui-module");
18
+ const meeting_notes_document_1 = require("./documents/meeting-notes-document");
19
+ const optimized_notes_document_1 = require("./documents/optimized-notes-document");
20
20
  let MeetingNotesWorkflow = class MeetingNotesWorkflow extends core_1.WorkflowBase {
21
21
  aiGenerateDocument;
22
22
  createDocument;
@@ -45,7 +45,9 @@ exports.MeetingNotesWorkflow = MeetingNotesWorkflow = __decorate([
45
45
  configFile: __dirname + '/meeting-notes.workflow.yaml',
46
46
  }),
47
47
  (0, common_1.WithArguments)(zod_1.z.object({
48
- inputText: zod_1.z.string().default("- meeting 1.1.2025\n- budget: need 2 cut costs sarah said\n- hire new person?? --> marketing\n- vendor pricing - follow up needed by anna"),
48
+ inputText: zod_1.z
49
+ .string()
50
+ .default('- meeting 1.1.2025\n- budget: need 2 cut costs sarah said\n- hire new person?? --> marketing\n- vendor pricing - follow up needed by anna'),
49
51
  })),
50
52
  (0, common_1.WithState)(zod_1.z.object({
51
53
  meetingNotes: meeting_notes_document_1.MeetingNotesDocumentSchema.optional(),
@@ -1 +1 @@
1
- {"version":3,"file":"meeting-notes.workflow.js","sourceRoot":"","sources":["../src/meeting-notes.workflow.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,0CAA+C;AAC/C,8CAA0F;AAC1F,+EAAsG;AACtG,mFAAmH;AACnH,6BAAwB;AACxB,oDAA0D;AAC1D,8DAA2D;AAYpD,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,mBAAY;IAC5C,kBAAkB,CAAqB;IACvC,cAAc,CAAiB;IAC3B,oBAAoB,CAAuB;IAC3C,sBAAsB,CAAyB;CAC5D,CAAA;AALY,oDAAoB;AACvB;IAAP,IAAA,aAAI,GAAE;8BAAqB,8BAAkB;gEAAC;AACvC;IAAP,IAAA,aAAI,GAAE;8BAAiB,+BAAc;4DAAC;AAC3B;IAAX,IAAA,iBAAQ,GAAE;8BAAuB,6CAAoB;kEAAC;AAC3C;IAAX,IAAA,iBAAQ,GAAE;8BAAyB,iDAAsB;oEAAC;+BAJhD,oBAAoB;IAVhC,IAAA,oBAAW,EAAC;QACX,UAAU,EAAE,SAAS,GAAG,8BAA8B;KACvD,CAAC;IACD,IAAA,sBAAa,EAAC,OAAC,CAAC,MAAM,CAAC;QACtB,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,2IAA2I,CAAC;KAC3K,CAAC,CAAC;IACF,IAAA,kBAAS,EAAC,OAAC,CAAC,MAAM,CAAC;QAClB,YAAY,EAAE,mDAA0B,CAAC,QAAQ,EAAE;QACnD,cAAc,EAAE,8DAAmC,CAAC,QAAQ,EAAE;KAC/D,CAAC,CAAC;GACU,oBAAoB,CAKhC"}
1
+ {"version":3,"file":"meeting-notes.workflow.js","sourceRoot":"","sources":["../src/meeting-notes.workflow.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6BAAwB;AACxB,oDAA0D;AAC1D,8CAA0F;AAC1F,0CAA+C;AAC/C,8DAA2D;AAC3D,+EAAsG;AACtG,mFAAmH;AAoB5G,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,mBAAY;IAC5C,kBAAkB,CAAqB;IACvC,cAAc,CAAiB;IAC3B,oBAAoB,CAAuB;IAC3C,sBAAsB,CAAyB;CAC5D,CAAA;AALY,oDAAoB;AACvB;IAAP,IAAA,aAAI,GAAE;8BAAqB,8BAAkB;gEAAC;AACvC;IAAP,IAAA,aAAI,GAAE;8BAAiB,+BAAc;4DAAC;AAC3B;IAAX,IAAA,iBAAQ,GAAE;8BAAuB,6CAAoB;kEAAC;AAC3C;IAAX,IAAA,iBAAQ,GAAE;8BAAyB,iDAAsB;oEAAC;+BAJhD,oBAAoB;IAlBhC,IAAA,oBAAW,EAAC;QACX,UAAU,EAAE,SAAS,GAAG,8BAA8B;KACvD,CAAC;IACD,IAAA,sBAAa,EACZ,OAAC,CAAC,MAAM,CAAC;QACP,SAAS,EAAE,OAAC;aACT,MAAM,EAAE;aACR,OAAO,CACN,2IAA2I,CAC5I;KACJ,CAAC,CACH;IACA,IAAA,kBAAS,EACR,OAAC,CAAC,MAAM,CAAC;QACP,YAAY,EAAE,mDAA0B,CAAC,QAAQ,EAAE;QACnD,cAAc,EAAE,8DAAmC,CAAC,QAAQ,EAAE;KAC/D,CAAC,CACH;GACY,oBAAoB,CAKhC"}
@@ -1,6 +1,6 @@
1
- title: "Human-in-the-loop Demo (Meeting Notes Optimizer)"
1
+ title: 'Human-in-the-loop Demo (Meeting Notes Optimizer)'
2
2
 
3
- description: "A demo workflow to demonstrate how to use AI to structure meeting notes."
3
+ description: 'A demo workflow to demonstrate how to use AI to structure meeting notes.'
4
4
 
5
5
  ui:
6
6
  form:
@@ -23,7 +23,7 @@ transitions:
23
23
  content:
24
24
  text: |
25
25
  Unstructured Notes:
26
-
26
+
27
27
  {{ args.inputText }}
28
28
 
29
29
  - id: user_response
@@ -76,4 +76,4 @@ transitions:
76
76
  update:
77
77
  content: ${ transition.payload }
78
78
  assign:
79
- optimizedNotes: ${ result.data.content }
79
+ optimizedNotes: ${ result.data.content }
package/nest-cli.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "assets": ["**/*.yaml", "**/*.yml"],
7
+ "watchAssets": true,
8
+ "deleteOutDir": true
9
+ }
10
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@loopstack/meeting-notes-example-workflow",
3
3
  "displayName": "Loopstack Meeting Notes Example Worflow",
4
4
  "description": "A complete workflow demonstrating how to create a workflow in loopstack based on a meeting notes summary use case.",
5
- "version": "0.15.0",
5
+ "version": "0.18.0",
6
6
  "author": {
7
7
  "name": "Jakob Klippel",
8
8
  "url": "https://www.linkedin.com/in/jakob-klippel/"
@@ -17,62 +17,35 @@
17
17
  "license": "Apache-2.0",
18
18
  "main": "dist/index.js",
19
19
  "types": "dist/index.d.ts",
20
+ "exports": {
21
+ ".": "./dist/index.js",
22
+ "./src/*": "./src/*"
23
+ },
20
24
  "scripts": {
21
25
  "build": "nest build",
22
- "watch": "nest build --watch",
23
26
  "compile": "tsc --noEmit",
24
- "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
25
- "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
26
- "test": "jest",
27
- "test:watch": "jest --watch",
28
- "test:cov": "jest --coverage",
29
- "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
30
- "test:e2e": "jest --config ./test/jest-e2e.json"
27
+ "format": "prettier --write .",
28
+ "lint": "eslint .",
29
+ "test": "jest --passWithNoTests",
30
+ "watch": "nest build --watch"
31
31
  },
32
32
  "dependencies": {
33
- "@loopstack/ai-module": "workspace:^0.15.0",
34
- "@loopstack/common": "^0.15.0",
35
- "@loopstack/core": "^0.15.0",
36
- "@loopstack/core-ui-module": "^0.15.0",
37
- "@nestjs/common": "^11.0.12",
38
- "zod": "^3.24.1"
39
- },
40
- "devDependencies": {
41
- "@eslint/eslintrc": "^3.3.0",
42
- "@eslint/js": "^9.22.0",
43
- "@nestjs/cli": "^11.0.5",
44
- "@nestjs/testing": "^11.0.12",
45
- "@swc/cli": "^0.6.0",
46
- "@swc/core": "^1.11.11",
47
- "@types/jest": "^29.5.14",
48
- "@types/node": "^22.13.10",
49
- "eslint": "^9.22.0",
50
- "eslint-config-prettier": "^10.1.1",
51
- "eslint-plugin-prettier": "^5.2.3",
52
- "globals": "^16.0.0",
53
- "jest": "^29.7.0",
54
- "prettier": "^3.5.3",
55
- "ts-jest": "^29.2.6",
56
- "tsconfig-paths": "^4.2.0",
57
- "typescript": "^5.8.3",
58
- "typescript-eslint": "^8.38.0"
33
+ "@loopstack/ai-module": "^0.18.0",
34
+ "@loopstack/common": "^0.18.0",
35
+ "@loopstack/core": "^0.18.0",
36
+ "@loopstack/core-ui-module": "^0.18.0",
37
+ "@nestjs/common": "^11.1.12",
38
+ "zod": "^4.3.5"
59
39
  },
60
40
  "jest": {
61
- "moduleFileExtensions": [
62
- "js",
63
- "json",
64
- "ts"
65
- ],
41
+ "testEnvironment": "node",
66
42
  "rootDir": "src",
67
43
  "testRegex": ".*\\.spec\\.ts$",
68
44
  "transform": {
69
45
  "^.+\\.ts$": "ts-jest"
70
46
  },
71
- "collectCoverageFrom": [
72
- "**/*.(t|j)s"
73
- ],
74
- "coverageDirectory": "../coverage",
75
- "testEnvironment": "node",
47
+ "testTimeout": 10000,
48
+ "forceExit": true,
76
49
  "maxWorkers": 1
77
50
  }
78
- }
51
+ }
@@ -0,0 +1,260 @@
1
+ import { TestingModule } from '@nestjs/testing';
2
+ import { AiGenerateDocument, AiModule } from '@loopstack/ai-module';
3
+ import { generateObjectFingerprint } from '@loopstack/common';
4
+ import { BlockExecutionContextDto, LoopCoreModule, WorkflowProcessorService } from '@loopstack/core';
5
+ import { CoreUiModule, CreateDocument } from '@loopstack/core-ui-module';
6
+ import { ToolMock, createWorkflowTest } from '@loopstack/testing';
7
+ import { MeetingNotesDocument } from '../documents/meeting-notes-document';
8
+ import { OptimizedNotesDocument } from '../documents/optimized-notes-document';
9
+ import { MeetingNotesWorkflow } from '../meeting-notes.workflow';
10
+
11
+ describe('MeetingNotesWorkflow', () => {
12
+ let module: TestingModule;
13
+ let workflow: MeetingNotesWorkflow;
14
+ let processor: WorkflowProcessorService;
15
+
16
+ let mockCreateDocument: ToolMock;
17
+
18
+ const mockInitialNotes = {
19
+ text: `
20
+ - meeting 1.1.2025
21
+ - budget: need 2 cut costs sarah said
22
+ - hire new person?? --> marketing
23
+ - vendor pricing - follow up needed by anna`,
24
+ };
25
+
26
+ beforeEach(async () => {
27
+ module = await createWorkflowTest()
28
+ .forWorkflow(MeetingNotesWorkflow)
29
+ .withImports(LoopCoreModule, CoreUiModule, AiModule)
30
+ .withProvider(MeetingNotesDocument)
31
+ .withProvider(OptimizedNotesDocument)
32
+ .withToolOverride(CreateDocument)
33
+ .withToolOverride(AiGenerateDocument)
34
+ .compile();
35
+
36
+ workflow = module.get(MeetingNotesWorkflow);
37
+ processor = module.get(WorkflowProcessorService);
38
+
39
+ mockCreateDocument = module.get(CreateDocument);
40
+ });
41
+
42
+ afterEach(async () => {
43
+ await module.close();
44
+ });
45
+
46
+ describe('initialization', () => {
47
+ it('should be defined with correct tools', () => {
48
+ expect(workflow).toBeDefined();
49
+ expect(workflow.tools).toContain('createDocument');
50
+ expect(workflow.tools).toContain('aiGenerateDocument');
51
+ });
52
+
53
+ it('should apply default argument value', () => {
54
+ const result = workflow.validate({});
55
+ expect(result.inputText).toContain('meeting 1.1.2025');
56
+ });
57
+ });
58
+
59
+ describe('initial step', () => {
60
+ const context = new BlockExecutionContextDto({});
61
+
62
+ it('should execute initial step and stop at waiting_for_response', async () => {
63
+ mockCreateDocument.execute.mockResolvedValue({
64
+ data: { content: mockInitialNotes },
65
+ });
66
+
67
+ const result = await processor.process(workflow, {}, context);
68
+
69
+ // Should execute without errors and stop at waiting_for_response (manual step)
70
+ expect(result.runtime.error).toBe(false);
71
+ expect(result.runtime.stop).toBe(true);
72
+
73
+ // Should call CreateDocument once for the initial form
74
+ expect(mockCreateDocument.execute).toHaveBeenCalledTimes(1);
75
+ expect(mockCreateDocument.execute).toHaveBeenCalledWith(
76
+ expect.objectContaining({
77
+ id: 'input',
78
+ update: {
79
+ content: {
80
+ text: expect.stringContaining('1.1.2025'),
81
+ },
82
+ },
83
+ }),
84
+ expect.anything(),
85
+ expect.anything(),
86
+ );
87
+
88
+ // Verify history contains expected places
89
+ const history = result.state.caretaker.getHistory();
90
+ const places = history.map((h) => h.metadata?.place);
91
+ expect(places).toContain('waiting_for_response');
92
+ });
93
+ });
94
+
95
+ describe('user response step', () => {
96
+ it('should process user response and generate optimized notes', async () => {
97
+ const mockUserEditedNotes = {
98
+ text: `Meeting Notes - January 1, 2025
99
+ - Budget discussion: need to cut costs (Sarah's input)
100
+ - Hiring: new person needed for marketing
101
+ - Vendor pricing: follow up needed by Anna`,
102
+ };
103
+
104
+ const mockOptimizedNotes = {
105
+ date: '2025-01-01',
106
+ summary: 'Budget and hiring discussion',
107
+ participants: ['Sarah', 'Anna'],
108
+ decisions: ['Cut costs', 'Hire for marketing'],
109
+ actionItems: ['Follow up on vendor pricing'],
110
+ };
111
+
112
+ const args = { inputText: mockInitialNotes.text };
113
+
114
+ // Create module with existing workflow state
115
+ const moduleWithState = await createWorkflowTest()
116
+ .forWorkflow(MeetingNotesWorkflow)
117
+ .withImports(LoopCoreModule, CoreUiModule, AiModule)
118
+ .withProvider(MeetingNotesDocument)
119
+ .withProvider(OptimizedNotesDocument)
120
+ .withToolOverride(CreateDocument)
121
+ .withToolOverride(AiGenerateDocument)
122
+ .withExistingWorkflow({
123
+ id: '123',
124
+ place: 'waiting_for_response',
125
+ hashRecord: {
126
+ options: generateObjectFingerprint(args), // previously run with same arguments
127
+ },
128
+ })
129
+ .compile();
130
+
131
+ const workflowWithState = moduleWithState.get(MeetingNotesWorkflow);
132
+ const processorWithState = moduleWithState.get(WorkflowProcessorService);
133
+
134
+ const mockCreateDocumentWithState: ToolMock = moduleWithState.get(CreateDocument);
135
+ const mockAiGenerateDocumentWithState: ToolMock = moduleWithState.get(AiGenerateDocument);
136
+
137
+ mockCreateDocumentWithState.execute.mockResolvedValue({
138
+ data: { content: mockUserEditedNotes },
139
+ });
140
+ mockAiGenerateDocumentWithState.execute.mockResolvedValue({
141
+ data: { content: mockOptimizedNotes },
142
+ });
143
+
144
+ // Context with user payload for manual transition
145
+ const contextWithPayload = new BlockExecutionContextDto({
146
+ payload: {
147
+ transition: {
148
+ id: 'user_response',
149
+ workflowId: '123',
150
+ payload: mockUserEditedNotes,
151
+ },
152
+ },
153
+ });
154
+
155
+ const result = await processorWithState.process(workflowWithState, args, contextWithPayload);
156
+
157
+ // Should execute and stop at notes_optimized (next manual step)
158
+ expect(result.runtime.error).toBe(false);
159
+ expect(result.runtime.stop).toBe(true);
160
+
161
+ // Should call CreateDocument once for user response
162
+ expect(mockCreateDocumentWithState.execute).toHaveBeenCalledTimes(1);
163
+ expect(mockCreateDocumentWithState.execute).toHaveBeenCalledWith(
164
+ expect.objectContaining({
165
+ id: 'input',
166
+ }),
167
+ expect.anything(),
168
+ expect.anything(),
169
+ );
170
+
171
+ // Should call AiGenerateDocument once
172
+ expect(mockAiGenerateDocumentWithState.execute).toHaveBeenCalledTimes(1);
173
+ expect(mockAiGenerateDocumentWithState.execute).toHaveBeenCalledWith(
174
+ expect.objectContaining({
175
+ llm: {
176
+ provider: 'openai',
177
+ model: 'gpt-4o',
178
+ },
179
+ }),
180
+ expect.anything(),
181
+ expect.anything(),
182
+ );
183
+
184
+ // Verify history contains expected places
185
+ const history = result.state.caretaker.getHistory();
186
+ const places = history.map((h) => h.metadata?.place);
187
+ expect(places).toContain('response_received');
188
+ expect(places).toContain('notes_optimized');
189
+
190
+ await moduleWithState.close();
191
+ });
192
+ });
193
+
194
+ describe('confirm step', () => {
195
+ it('should complete workflow when user confirms optimized notes', async () => {
196
+ const mockFinalNotes = {
197
+ date: '2025-01-01',
198
+ summary: 'Budget discussion with updates',
199
+ participants: ['Sarah', 'Anna', 'Bob'],
200
+ decisions: ['Cut costs by 15%'],
201
+ actionItems: ['Follow up on vendor pricing by Friday'],
202
+ };
203
+
204
+ const args = { inputText: 'any text' };
205
+
206
+ // Create module with existing workflow state after AI optimization
207
+ const moduleWithState = await createWorkflowTest()
208
+ .forWorkflow(MeetingNotesWorkflow)
209
+ .withImports(LoopCoreModule, CoreUiModule, AiModule)
210
+ .withProvider(MeetingNotesDocument)
211
+ .withProvider(OptimizedNotesDocument)
212
+ .withToolOverride(CreateDocument)
213
+ .withToolOverride(AiGenerateDocument)
214
+ .withExistingWorkflow({
215
+ id: '123',
216
+ place: 'notes_optimized',
217
+ hashRecord: {
218
+ options: generateObjectFingerprint(args), // previously run with same arguments
219
+ },
220
+ })
221
+ .compile();
222
+
223
+ const workflowWithState = moduleWithState.get(MeetingNotesWorkflow);
224
+ const processorWithState = moduleWithState.get(WorkflowProcessorService);
225
+
226
+ const mockCreateDocumentWithState: ToolMock = moduleWithState.get(CreateDocument);
227
+
228
+ mockCreateDocumentWithState.execute.mockResolvedValue({
229
+ data: { content: mockFinalNotes },
230
+ });
231
+
232
+ // Context with user confirmation for manual transition
233
+ const contextWithPayload = new BlockExecutionContextDto({
234
+ payload: {
235
+ transition: {
236
+ id: 'confirm',
237
+ workflowId: '123',
238
+ payload: mockFinalNotes,
239
+ },
240
+ },
241
+ });
242
+
243
+ const result = await processorWithState.process(workflowWithState, args, contextWithPayload);
244
+
245
+ // Should complete and reach end state
246
+ expect(result.runtime.error).toBe(false);
247
+ expect(result.runtime.stop).toBe(false);
248
+
249
+ // Should call CreateDocument once for final confirmation
250
+ expect(mockCreateDocumentWithState.execute).toHaveBeenCalledTimes(1);
251
+
252
+ // Verify history contains expected places including end
253
+ const history = result.state.caretaker.getHistory();
254
+ const places = history.map((h) => h.metadata?.place);
255
+ expect(places).toContain('end');
256
+
257
+ await moduleWithState.close();
258
+ });
259
+ });
260
+ });
@@ -0,0 +1,15 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { z } from 'zod';
3
+ import { BlockConfig, WithArguments } from '@loopstack/common';
4
+ import { DocumentBase } from '@loopstack/core';
5
+
6
+ export const MeetingNotesDocumentSchema = z.object({
7
+ text: z.string(),
8
+ });
9
+
10
+ @Injectable()
11
+ @BlockConfig({
12
+ configFile: __dirname + '/meeting-notes-document.yaml',
13
+ })
14
+ @WithArguments(MeetingNotesDocumentSchema)
15
+ export class MeetingNotesDocument extends DocumentBase {}
@@ -0,0 +1,13 @@
1
+ type: document
2
+ ui:
3
+ form:
4
+ properties:
5
+ text:
6
+ title: Text
7
+ widget: textarea
8
+ actions:
9
+ - type: button
10
+ widget: button
11
+ transition: user_response
12
+ options:
13
+ label: 'Optimize Notes'
@@ -0,0 +1,19 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { z } from 'zod';
3
+ import { BlockConfig, WithArguments } from '@loopstack/common';
4
+ import { DocumentBase } from '@loopstack/core';
5
+
6
+ export const OptimizedMeetingNotesDocumentSchema = z.object({
7
+ date: z.string(),
8
+ summary: z.string(),
9
+ participants: z.array(z.string()),
10
+ decisions: z.array(z.string()),
11
+ actionItems: z.array(z.string()),
12
+ });
13
+
14
+ @Injectable()
15
+ @BlockConfig({
16
+ configFile: __dirname + '/optimized-notes-document.yaml',
17
+ })
18
+ @WithArguments(OptimizedMeetingNotesDocumentSchema)
19
+ export class OptimizedNotesDocument extends DocumentBase {}
@@ -0,0 +1,36 @@
1
+ type: document
2
+ ui:
3
+ form:
4
+ order:
5
+ - date
6
+ - summary
7
+ - participants
8
+ - decisions
9
+ - actionItems
10
+ properties:
11
+ date:
12
+ title: Date
13
+ summary:
14
+ title: Summary
15
+ widget: textarea
16
+ participants:
17
+ title: Participants
18
+ collapsed: true
19
+ items:
20
+ title: Participant
21
+ decisions:
22
+ title: Decisions
23
+ collapsed: true
24
+ items:
25
+ title: Decision
26
+ actionItems:
27
+ title: Action Items
28
+ collapsed: true
29
+ items:
30
+ title: Action Item
31
+ actions:
32
+ - type: button
33
+ widget: button
34
+ transition: confirm
35
+ options:
36
+ label: 'Confirm'
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './meeting-notes-example.module';
2
+ export * from './meeting-notes.workflow';
@@ -0,0 +1,14 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { AiModule } from '@loopstack/ai-module';
3
+ import { LoopCoreModule } from '@loopstack/core';
4
+ import { CoreUiModule } from '@loopstack/core-ui-module';
5
+ import { MeetingNotesDocument } from './documents/meeting-notes-document';
6
+ import { OptimizedNotesDocument } from './documents/optimized-notes-document';
7
+ import { MeetingNotesWorkflow } from './meeting-notes.workflow';
8
+
9
+ @Module({
10
+ imports: [LoopCoreModule, CoreUiModule, AiModule],
11
+ providers: [MeetingNotesWorkflow, MeetingNotesDocument, OptimizedNotesDocument],
12
+ exports: [MeetingNotesWorkflow],
13
+ })
14
+ export class MeetingNotesExampleModule {}
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ import { AiGenerateDocument } from '@loopstack/ai-module';
3
+ import { BlockConfig, Document, Tool, WithArguments, WithState } from '@loopstack/common';
4
+ import { WorkflowBase } from '@loopstack/core';
5
+ import { CreateDocument } from '@loopstack/core-ui-module';
6
+ import { MeetingNotesDocument, MeetingNotesDocumentSchema } from './documents/meeting-notes-document';
7
+ import { OptimizedMeetingNotesDocumentSchema, OptimizedNotesDocument } from './documents/optimized-notes-document';
8
+
9
+ @BlockConfig({
10
+ configFile: __dirname + '/meeting-notes.workflow.yaml',
11
+ })
12
+ @WithArguments(
13
+ z.object({
14
+ inputText: z
15
+ .string()
16
+ .default(
17
+ '- meeting 1.1.2025\n- budget: need 2 cut costs sarah said\n- hire new person?? --> marketing\n- vendor pricing - follow up needed by anna',
18
+ ),
19
+ }),
20
+ )
21
+ @WithState(
22
+ z.object({
23
+ meetingNotes: MeetingNotesDocumentSchema.optional(),
24
+ optimizedNotes: OptimizedMeetingNotesDocumentSchema.optional(),
25
+ }),
26
+ )
27
+ export class MeetingNotesWorkflow extends WorkflowBase {
28
+ @Tool() aiGenerateDocument: AiGenerateDocument;
29
+ @Tool() createDocument: CreateDocument;
30
+ @Document() meetingNotesDocument: MeetingNotesDocument;
31
+ @Document() optimizedNotesDocument: OptimizedNotesDocument;
32
+ }
@@ -0,0 +1,79 @@
1
+ title: 'Human-in-the-loop Demo (Meeting Notes Optimizer)'
2
+
3
+ description: 'A demo workflow to demonstrate how to use AI to structure meeting notes.'
4
+
5
+ ui:
6
+ form:
7
+ properties:
8
+ inputText:
9
+ title: 'Text'
10
+ widget: 'textarea'
11
+
12
+ transitions:
13
+ - id: create_form
14
+ from: start
15
+ to: waiting_for_response
16
+ call:
17
+ - id: form
18
+ tool: createDocument
19
+ args:
20
+ id: input
21
+ document: meetingNotesDocument
22
+ update:
23
+ content:
24
+ text: |
25
+ Unstructured Notes:
26
+
27
+ {{ args.inputText }}
28
+
29
+ - id: user_response
30
+ from: waiting_for_response
31
+ to: response_received
32
+ trigger: manual
33
+ call:
34
+ - id: create_response
35
+ tool: createDocument
36
+ args:
37
+ id: input
38
+ document: meetingNotesDocument
39
+ update:
40
+ content: ${ transition.payload }
41
+ assign:
42
+ meetingNotes: ${ result.data.content }
43
+
44
+ - id: optimize_notes
45
+ from: response_received
46
+ to: notes_optimized
47
+ call:
48
+ - id: prompt
49
+ tool: aiGenerateDocument
50
+ args:
51
+ llm:
52
+ provider: openai
53
+ model: gpt-4o
54
+ response:
55
+ id: final
56
+ document: optimizedNotesDocument
57
+ prompt: |
58
+ Extract all information from the provided meeting notes into the structured document.
59
+
60
+ <Meeting Notes>
61
+ {{ meetingNotes.text }}
62
+ </Meeting Notes>
63
+ assign:
64
+ optimizedNotes: ${ result.data.content }
65
+
66
+ - id: confirm
67
+ from: notes_optimized
68
+ to: end
69
+ trigger: manual
70
+ call:
71
+ - id: create_response
72
+ tool: createDocument
73
+ args:
74
+ id: final
75
+ document: optimizedNotesDocument
76
+ update:
77
+ content: ${ transition.payload }
78
+ assign:
79
+ optimizedNotes: ${ result.data.content }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "module": "CommonJS",
5
+ "declaration": true,
6
+ "removeComments": true,
7
+ "emitDecoratorMetadata": true,
8
+ "experimentalDecorators": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "sourceMap": true,
11
+ "outDir": "./dist",
12
+ "rootDir": "./src",
13
+ "skipLibCheck": true,
14
+ "strictNullChecks": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "noImplicitAny": false,
17
+ "strictBindCallApply": false,
18
+ "noFallthroughCasesInSwitch": false
19
+ },
20
+ "include": ["./src"]
21
+ }
package/LICENSE DELETED
@@ -1,19 +0,0 @@
1
- Apache License
2
- Version 2.0, January 2004
3
- http://www.apache.org/licenses/
4
-
5
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
-
7
- Copyright 2025 Loopstack GmbH
8
-
9
- Licensed under the Apache License, Version 2.0 (the "License");
10
- you may not use this skeleton application except in compliance with the License.
11
- You may obtain a copy of the License at
12
-
13
- http://www.apache.org/licenses/LICENSE-2.0
14
-
15
- Unless required by applicable law or agreed to in writing, software
16
- distributed under the License is distributed on an "AS IS" BASIS,
17
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
- See the License for the specific language governing permissions and
19
- limitations under the License.