@loopstack/meeting-notes-example-workflow 0.20.6 → 0.21.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.
- package/README.md +40 -40
- package/dist/documents/meeting-notes-document.d.ts +2 -5
- package/dist/documents/meeting-notes-document.d.ts.map +1 -1
- package/dist/documents/meeting-notes-document.js +3 -11
- package/dist/documents/meeting-notes-document.js.map +1 -1
- package/dist/documents/meeting-notes-document.yaml +10 -10
- package/dist/documents/optimized-notes-document.d.ts +6 -3
- package/dist/documents/optimized-notes-document.d.ts.map +1 -1
- package/dist/documents/optimized-notes-document.js +7 -11
- package/dist/documents/optimized-notes-document.js.map +1 -1
- package/dist/documents/optimized-notes-document.yaml +33 -33
- package/dist/meeting-notes-example.module.js +3 -3
- package/dist/meeting-notes-example.module.js.map +1 -1
- package/dist/meeting-notes.ui.yaml +10 -0
- package/dist/meeting-notes.workflow.d.ts +15 -16
- package/dist/meeting-notes.workflow.d.ts.map +1 -1
- package/dist/meeting-notes.workflow.js +57 -41
- package/dist/meeting-notes.workflow.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/meeting-notes.workflow.spec.ts +87 -168
- package/src/documents/meeting-notes-document.ts +5 -9
- package/src/documents/meeting-notes-document.yaml +10 -10
- package/src/documents/optimized-notes-document.ts +9 -7
- package/src/documents/optimized-notes-document.yaml +33 -33
- package/src/meeting-notes-example.module.ts +3 -3
- package/src/meeting-notes.ui.yaml +10 -0
- package/src/meeting-notes.workflow.ts +51 -33
- package/dist/meeting-notes.workflow.yaml +0 -77
- package/src/meeting-notes.workflow.yaml +0 -77
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { TestingModule } from '@nestjs/testing';
|
|
2
|
-
import {
|
|
3
|
-
import { RunContext,
|
|
4
|
-
import { WorkflowProcessorService } from '@loopstack/core';
|
|
5
|
-
import {
|
|
6
|
-
import { ToolMock, createWorkflowTest } from '@loopstack/testing';
|
|
2
|
+
import { ClaudeGenerateDocument, ClaudeModule } from '@loopstack/claude-module';
|
|
3
|
+
import { RunContext, WorkflowEntity, getBlockTools } from '@loopstack/common';
|
|
4
|
+
import { LoopCoreModule, WorkflowProcessorService } from '@loopstack/core';
|
|
5
|
+
import { ToolMock, createStatelessContext, createWorkflowTest } from '@loopstack/testing';
|
|
7
6
|
import { MeetingNotesDocument } from '../documents/meeting-notes-document';
|
|
8
7
|
import { OptimizedNotesDocument } from '../documents/optimized-notes-document';
|
|
9
8
|
import { MeetingNotesWorkflow } from '../meeting-notes.workflow';
|
|
@@ -13,30 +12,21 @@ describe('MeetingNotesWorkflow', () => {
|
|
|
13
12
|
let workflow: MeetingNotesWorkflow;
|
|
14
13
|
let processor: WorkflowProcessorService;
|
|
15
14
|
|
|
16
|
-
let
|
|
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
|
-
};
|
|
15
|
+
let mockClaudeGenerateDocument: ToolMock;
|
|
25
16
|
|
|
26
17
|
beforeEach(async () => {
|
|
27
18
|
module = await createWorkflowTest()
|
|
28
19
|
.forWorkflow(MeetingNotesWorkflow)
|
|
29
|
-
.withImports(
|
|
20
|
+
.withImports(LoopCoreModule, ClaudeModule)
|
|
30
21
|
.withProvider(MeetingNotesDocument)
|
|
31
22
|
.withProvider(OptimizedNotesDocument)
|
|
32
|
-
.withToolOverride(
|
|
33
|
-
.withToolOverride(AiGenerateDocument)
|
|
23
|
+
.withToolOverride(ClaudeGenerateDocument)
|
|
34
24
|
.compile();
|
|
35
25
|
|
|
36
26
|
workflow = module.get(MeetingNotesWorkflow);
|
|
37
27
|
processor = module.get(WorkflowProcessorService);
|
|
38
28
|
|
|
39
|
-
|
|
29
|
+
mockClaudeGenerateDocument = module.get(ClaudeGenerateDocument);
|
|
40
30
|
});
|
|
41
31
|
|
|
42
32
|
afterEach(async () => {
|
|
@@ -46,152 +36,103 @@ describe('MeetingNotesWorkflow', () => {
|
|
|
46
36
|
describe('initialization', () => {
|
|
47
37
|
it('should be defined with correct tools', () => {
|
|
48
38
|
expect(workflow).toBeDefined();
|
|
49
|
-
expect(getBlockTools(workflow)).toContain('
|
|
50
|
-
expect(getBlockTools(workflow)).toContain('aiGenerateDocument');
|
|
39
|
+
expect(getBlockTools(workflow)).toContain('claudeGenerateDocument');
|
|
51
40
|
});
|
|
52
41
|
});
|
|
53
42
|
|
|
54
43
|
describe('initial step', () => {
|
|
55
|
-
const context = {} as RunContext;
|
|
56
|
-
|
|
57
44
|
it('should execute initial step and stop at waiting_for_response', async () => {
|
|
58
|
-
|
|
59
|
-
data: { content: mockInitialNotes },
|
|
60
|
-
});
|
|
45
|
+
const context = createStatelessContext();
|
|
61
46
|
|
|
62
47
|
const result = await processor.process(workflow, {}, context);
|
|
63
48
|
|
|
64
|
-
// Should execute without errors and stop at waiting_for_response (manual step)
|
|
65
49
|
expect(result.hasError).toBe(false);
|
|
66
50
|
expect(result.stop).toBe(true);
|
|
51
|
+
expect(result.place).toBe('waiting_for_response');
|
|
67
52
|
|
|
68
|
-
|
|
69
|
-
expect(
|
|
70
|
-
expect(mockCreateDocument.execute).toHaveBeenCalledWith(
|
|
53
|
+
expect(result.documents).toHaveLength(1);
|
|
54
|
+
expect(result.documents[0]).toEqual(
|
|
71
55
|
expect.objectContaining({
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
},
|
|
77
|
-
},
|
|
56
|
+
className: 'MeetingNotesDocument',
|
|
57
|
+
content: expect.objectContaining({
|
|
58
|
+
text: expect.stringContaining('1.1.2025'),
|
|
59
|
+
}),
|
|
78
60
|
}),
|
|
79
|
-
expect.anything(),
|
|
80
|
-
expect.anything(),
|
|
81
|
-
expect.anything(),
|
|
82
61
|
);
|
|
83
|
-
|
|
84
|
-
// // Verify history contains expected places
|
|
85
|
-
// const history = result.state.getHistory();
|
|
86
|
-
// const places = history.map((h) => h.metadata?.place);
|
|
87
|
-
// expect(places).toContain('waiting_for_response');
|
|
88
62
|
});
|
|
89
|
-
});
|
|
90
63
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const mockUserEditedNotes = {
|
|
94
|
-
text: `Meeting Notes - January 1, 2025
|
|
95
|
-
- Budget discussion: need to cut costs (Sarah's input)
|
|
96
|
-
- Hiring: new person needed for marketing
|
|
97
|
-
- Vendor pricing: follow up needed by Anna`,
|
|
98
|
-
};
|
|
64
|
+
it('should use custom input text when provided', async () => {
|
|
65
|
+
const context = createStatelessContext();
|
|
99
66
|
|
|
100
|
-
const
|
|
101
|
-
date: '2025-01-01',
|
|
102
|
-
summary: 'Budget and hiring discussion',
|
|
103
|
-
participants: ['Sarah', 'Anna'],
|
|
104
|
-
decisions: ['Cut costs', 'Hire for marketing'],
|
|
105
|
-
actionItems: ['Follow up on vendor pricing'],
|
|
106
|
-
};
|
|
67
|
+
const result = await processor.process(workflow, { inputText: 'Custom meeting notes here' }, context);
|
|
107
68
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Create module with existing workflow state
|
|
111
|
-
const moduleWithState = await createWorkflowTest()
|
|
112
|
-
.forWorkflow(MeetingNotesWorkflow)
|
|
113
|
-
.withImports(CoreUiModule, AiModule)
|
|
114
|
-
.withProvider(MeetingNotesDocument)
|
|
115
|
-
.withProvider(OptimizedNotesDocument)
|
|
116
|
-
.withToolOverride(CreateDocument)
|
|
117
|
-
.withToolOverride(AiGenerateDocument)
|
|
118
|
-
.withExistingWorkflow({
|
|
119
|
-
id: '123',
|
|
120
|
-
place: 'waiting_for_response',
|
|
121
|
-
hashRecord: {
|
|
122
|
-
options: generateObjectFingerprint(args), // previously run with same arguments
|
|
123
|
-
},
|
|
124
|
-
})
|
|
125
|
-
.compile();
|
|
69
|
+
expect(result.hasError).toBe(false);
|
|
70
|
+
expect(result.stop).toBe(true);
|
|
126
71
|
|
|
127
|
-
|
|
128
|
-
|
|
72
|
+
expect(result.documents[0]).toEqual(
|
|
73
|
+
expect.objectContaining({
|
|
74
|
+
content: expect.objectContaining({
|
|
75
|
+
text: expect.stringContaining('Custom meeting notes here'),
|
|
76
|
+
}),
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
129
81
|
|
|
130
|
-
|
|
131
|
-
|
|
82
|
+
describe('resume from waiting_for_response', () => {
|
|
83
|
+
it('should process user response and call LLM to optimize notes', async () => {
|
|
84
|
+
const workflowId = '00000000-0000-0000-0000-000000000001';
|
|
132
85
|
|
|
133
|
-
|
|
134
|
-
data: { content: mockUserEditedNotes },
|
|
135
|
-
});
|
|
136
|
-
mockAiGenerateDocumentWithState.execute.mockResolvedValue({
|
|
137
|
-
data: { content: mockOptimizedNotes },
|
|
138
|
-
});
|
|
86
|
+
mockClaudeGenerateDocument.call.mockResolvedValue({ data: undefined });
|
|
139
87
|
|
|
140
|
-
|
|
141
|
-
|
|
88
|
+
const context = {
|
|
89
|
+
workflowEntity: {
|
|
90
|
+
id: workflowId,
|
|
91
|
+
place: 'waiting_for_response',
|
|
92
|
+
documents: [],
|
|
93
|
+
} as Partial<WorkflowEntity>,
|
|
142
94
|
payload: {
|
|
143
95
|
transition: {
|
|
144
|
-
id: '
|
|
145
|
-
workflowId
|
|
146
|
-
payload:
|
|
96
|
+
id: 'userResponse',
|
|
97
|
+
workflowId,
|
|
98
|
+
payload: { text: 'Cleaned up meeting notes from user' },
|
|
147
99
|
},
|
|
148
100
|
},
|
|
149
|
-
} as RunContext;
|
|
101
|
+
} as unknown as RunContext;
|
|
150
102
|
|
|
151
|
-
const result = await
|
|
103
|
+
const result = await processor.process(workflow, {}, context);
|
|
152
104
|
|
|
153
|
-
// Should execute and stop at notes_optimized (next manual step)
|
|
154
105
|
expect(result.hasError).toBe(false);
|
|
155
106
|
expect(result.stop).toBe(true);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
expect(
|
|
160
|
-
expect.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
expect.anything(),
|
|
107
|
+
expect(result.place).toBe('notes_optimized');
|
|
108
|
+
|
|
109
|
+
// User response should have been saved as document
|
|
110
|
+
expect(result.documents).toEqual(
|
|
111
|
+
expect.arrayContaining([
|
|
112
|
+
expect.objectContaining({
|
|
113
|
+
className: 'MeetingNotesDocument',
|
|
114
|
+
}),
|
|
115
|
+
]),
|
|
166
116
|
);
|
|
167
117
|
|
|
168
|
-
//
|
|
169
|
-
expect(
|
|
170
|
-
expect(
|
|
118
|
+
// LLM should have been called to optimize notes
|
|
119
|
+
expect(mockClaudeGenerateDocument.call).toHaveBeenCalledTimes(1);
|
|
120
|
+
expect(mockClaudeGenerateDocument.call).toHaveBeenCalledWith(
|
|
171
121
|
expect.objectContaining({
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
},
|
|
122
|
+
claude: { model: 'claude-sonnet-4-6' },
|
|
123
|
+
response: expect.objectContaining({ id: 'final' }),
|
|
124
|
+
prompt: expect.stringContaining('meeting notes'),
|
|
176
125
|
}),
|
|
177
|
-
|
|
178
|
-
expect.anything(),
|
|
179
|
-
expect.anything(),
|
|
126
|
+
undefined,
|
|
180
127
|
);
|
|
181
|
-
|
|
182
|
-
// // Verify history contains expected places
|
|
183
|
-
// const history = result.state.getHistory();
|
|
184
|
-
// const places = history.map((h) => h.metadata?.place);
|
|
185
|
-
// expect(places).toContain('response_received');
|
|
186
|
-
// expect(places).toContain('notes_optimized');
|
|
187
|
-
|
|
188
|
-
await moduleWithState.close();
|
|
189
128
|
});
|
|
190
129
|
});
|
|
191
130
|
|
|
192
|
-
describe('
|
|
131
|
+
describe('resume from notes_optimized', () => {
|
|
193
132
|
it('should complete workflow when user confirms optimized notes', async () => {
|
|
194
|
-
const
|
|
133
|
+
const workflowId = '00000000-0000-0000-0000-000000000002';
|
|
134
|
+
|
|
135
|
+
const optimizedPayload = {
|
|
195
136
|
date: '2025-01-01',
|
|
196
137
|
summary: 'Budget discussion with updates',
|
|
197
138
|
participants: ['Sarah', 'Anna', 'Bob'],
|
|
@@ -199,60 +140,38 @@ describe('MeetingNotesWorkflow', () => {
|
|
|
199
140
|
actionItems: ['Follow up on vendor pricing by Friday'],
|
|
200
141
|
};
|
|
201
142
|
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const moduleWithState = await createWorkflowTest()
|
|
206
|
-
.forWorkflow(MeetingNotesWorkflow)
|
|
207
|
-
.withImports(CoreUiModule, AiModule)
|
|
208
|
-
.withProvider(MeetingNotesDocument)
|
|
209
|
-
.withProvider(OptimizedNotesDocument)
|
|
210
|
-
.withToolOverride(CreateDocument)
|
|
211
|
-
.withToolOverride(AiGenerateDocument)
|
|
212
|
-
.withExistingWorkflow({
|
|
213
|
-
id: '123',
|
|
143
|
+
const context = {
|
|
144
|
+
workflowEntity: {
|
|
145
|
+
id: workflowId,
|
|
214
146
|
place: 'notes_optimized',
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
},
|
|
218
|
-
})
|
|
219
|
-
.compile();
|
|
220
|
-
|
|
221
|
-
const workflowWithState = moduleWithState.get(MeetingNotesWorkflow);
|
|
222
|
-
const processorWithState = moduleWithState.get(WorkflowProcessorService);
|
|
223
|
-
|
|
224
|
-
const mockCreateDocumentWithState: ToolMock = moduleWithState.get(CreateDocument);
|
|
225
|
-
|
|
226
|
-
mockCreateDocumentWithState.execute.mockResolvedValue({
|
|
227
|
-
data: { content: mockFinalNotes },
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
// Context with user confirmation for manual transition
|
|
231
|
-
const contextWithPayload = {
|
|
147
|
+
documents: [],
|
|
148
|
+
} as Partial<WorkflowEntity>,
|
|
232
149
|
payload: {
|
|
233
150
|
transition: {
|
|
234
151
|
id: 'confirm',
|
|
235
|
-
workflowId
|
|
236
|
-
payload:
|
|
152
|
+
workflowId,
|
|
153
|
+
payload: optimizedPayload,
|
|
237
154
|
},
|
|
238
155
|
},
|
|
239
|
-
} as RunContext;
|
|
156
|
+
} as unknown as RunContext;
|
|
240
157
|
|
|
241
|
-
const result = await
|
|
158
|
+
const result = await processor.process(workflow, {}, context);
|
|
242
159
|
|
|
243
|
-
// Should complete and reach end state
|
|
244
160
|
expect(result.hasError).toBe(false);
|
|
245
161
|
expect(result.stop).toBe(false);
|
|
162
|
+
expect(result.place).toBe('end');
|
|
163
|
+
|
|
164
|
+
// Confirmed payload should have been saved as OptimizedNotesDocument
|
|
165
|
+
expect(result.documents).toEqual(
|
|
166
|
+
expect.arrayContaining([
|
|
167
|
+
expect.objectContaining({
|
|
168
|
+
className: 'OptimizedNotesDocument',
|
|
169
|
+
}),
|
|
170
|
+
]),
|
|
171
|
+
);
|
|
246
172
|
|
|
247
|
-
//
|
|
248
|
-
expect(
|
|
249
|
-
|
|
250
|
-
// // Verify history contains expected places including end
|
|
251
|
-
// const history = result.state.getHistory();
|
|
252
|
-
// const places = history.map((h) => h.metadata?.place);
|
|
253
|
-
// expect(places).toContain('end');
|
|
254
|
-
|
|
255
|
-
await moduleWithState.close();
|
|
173
|
+
// No additional LLM calls during confirmation
|
|
174
|
+
expect(mockClaudeGenerateDocument.call).not.toHaveBeenCalled();
|
|
256
175
|
});
|
|
257
176
|
});
|
|
258
177
|
});
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { Document
|
|
2
|
+
import { Document } from '@loopstack/common';
|
|
3
3
|
|
|
4
4
|
export const MeetingNotesDocumentSchema = z.object({
|
|
5
5
|
text: z.string(),
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
@Document({
|
|
9
|
-
|
|
9
|
+
schema: MeetingNotesDocumentSchema,
|
|
10
|
+
uiConfig: __dirname + '/meeting-notes-document.yaml',
|
|
10
11
|
})
|
|
11
|
-
export class MeetingNotesDocument
|
|
12
|
-
|
|
13
|
-
schema: MeetingNotesDocumentSchema,
|
|
14
|
-
})
|
|
15
|
-
content: {
|
|
16
|
-
text: string;
|
|
17
|
-
};
|
|
12
|
+
export class MeetingNotesDocument {
|
|
13
|
+
text: string;
|
|
18
14
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
type: document
|
|
2
2
|
ui:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
text:
|
|
6
|
-
title: Text
|
|
7
|
-
widget: textarea
|
|
8
|
-
actions:
|
|
9
|
-
- type: button
|
|
10
|
-
widget: button
|
|
3
|
+
widgets:
|
|
4
|
+
- widget: form
|
|
11
5
|
options:
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
properties:
|
|
7
|
+
text:
|
|
8
|
+
title: Text
|
|
9
|
+
widget: textarea
|
|
10
|
+
actions:
|
|
11
|
+
- type: button
|
|
12
|
+
transition: userResponse
|
|
13
|
+
label: 'Optimize Notes'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { Document
|
|
2
|
+
import { Document } from '@loopstack/common';
|
|
3
3
|
|
|
4
4
|
export const OptimizedMeetingNotesDocumentSchema = z.object({
|
|
5
5
|
date: z.string(),
|
|
@@ -10,11 +10,13 @@ export const OptimizedMeetingNotesDocumentSchema = z.object({
|
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
@Document({
|
|
13
|
-
|
|
13
|
+
schema: OptimizedMeetingNotesDocumentSchema,
|
|
14
|
+
uiConfig: __dirname + '/optimized-notes-document.yaml',
|
|
14
15
|
})
|
|
15
|
-
export class OptimizedNotesDocument
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
export class OptimizedNotesDocument {
|
|
17
|
+
date: string;
|
|
18
|
+
summary: string;
|
|
19
|
+
participants: string[];
|
|
20
|
+
decisions: string[];
|
|
21
|
+
actionItems: string[];
|
|
20
22
|
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
type: document
|
|
2
2
|
ui:
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
3
|
+
widgets:
|
|
4
|
+
- widget: form
|
|
34
5
|
options:
|
|
35
|
-
|
|
36
|
-
|
|
6
|
+
order:
|
|
7
|
+
- date
|
|
8
|
+
- summary
|
|
9
|
+
- participants
|
|
10
|
+
- decisions
|
|
11
|
+
- actionItems
|
|
12
|
+
properties:
|
|
13
|
+
date:
|
|
14
|
+
title: Date
|
|
15
|
+
summary:
|
|
16
|
+
title: Summary
|
|
17
|
+
widget: textarea
|
|
18
|
+
participants:
|
|
19
|
+
title: Participants
|
|
20
|
+
collapsed: true
|
|
21
|
+
items:
|
|
22
|
+
title: Participant
|
|
23
|
+
decisions:
|
|
24
|
+
title: Decisions
|
|
25
|
+
collapsed: true
|
|
26
|
+
items:
|
|
27
|
+
title: Decision
|
|
28
|
+
actionItems:
|
|
29
|
+
title: Action Items
|
|
30
|
+
collapsed: true
|
|
31
|
+
items:
|
|
32
|
+
title: Action Item
|
|
33
|
+
actions:
|
|
34
|
+
- type: button
|
|
35
|
+
transition: confirm
|
|
36
|
+
label: 'Confirm'
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ClaudeModule } from '@loopstack/claude-module';
|
|
3
|
+
import { LoopCoreModule } from '@loopstack/core';
|
|
4
4
|
import { MeetingNotesDocument } from './documents/meeting-notes-document';
|
|
5
5
|
import { OptimizedNotesDocument } from './documents/optimized-notes-document';
|
|
6
6
|
import { MeetingNotesWorkflow } from './meeting-notes.workflow';
|
|
7
7
|
|
|
8
8
|
@Module({
|
|
9
|
-
imports: [
|
|
9
|
+
imports: [LoopCoreModule, ClaudeModule],
|
|
10
10
|
providers: [MeetingNotesWorkflow, MeetingNotesDocument, OptimizedNotesDocument],
|
|
11
11
|
exports: [MeetingNotesWorkflow],
|
|
12
12
|
})
|
|
@@ -1,43 +1,61 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { CreateDocument } from '@loopstack/core-ui-module';
|
|
2
|
+
import { ClaudeGenerateDocument } from '@loopstack/claude-module';
|
|
3
|
+
import { BaseWorkflow, Final, Initial, InjectTool, Transition, Workflow } from '@loopstack/common';
|
|
5
4
|
import { MeetingNotesDocument, MeetingNotesDocumentSchema } from './documents/meeting-notes-document';
|
|
6
5
|
import { OptimizedMeetingNotesDocumentSchema, OptimizedNotesDocument } from './documents/optimized-notes-document';
|
|
7
6
|
|
|
8
7
|
@Workflow({
|
|
9
|
-
|
|
8
|
+
uiConfig: __dirname + '/meeting-notes.ui.yaml',
|
|
9
|
+
schema: z.object({
|
|
10
|
+
inputText: z
|
|
11
|
+
.string()
|
|
12
|
+
.default(
|
|
13
|
+
'- 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',
|
|
14
|
+
),
|
|
15
|
+
}),
|
|
10
16
|
})
|
|
11
|
-
export class MeetingNotesWorkflow {
|
|
12
|
-
@InjectTool()
|
|
13
|
-
@InjectTool() createDocument: CreateDocument;
|
|
14
|
-
@InjectDocument() meetingNotesDocument: MeetingNotesDocument;
|
|
15
|
-
@InjectDocument() optimizedNotesDocument: OptimizedNotesDocument;
|
|
17
|
+
export class MeetingNotesWorkflow extends BaseWorkflow<{ inputText: string }> {
|
|
18
|
+
@InjectTool() claudeGenerateDocument: ClaudeGenerateDocument;
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
inputText: z
|
|
20
|
-
.string()
|
|
21
|
-
.default(
|
|
22
|
-
'- 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',
|
|
23
|
-
),
|
|
24
|
-
}),
|
|
25
|
-
})
|
|
26
|
-
args: {
|
|
27
|
-
inputText: string;
|
|
28
|
-
};
|
|
20
|
+
meetingNotes?: z.infer<typeof MeetingNotesDocumentSchema>;
|
|
21
|
+
optimizedNotes?: z.infer<typeof OptimizedMeetingNotesDocumentSchema>;
|
|
29
22
|
|
|
30
|
-
@
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
23
|
+
@Initial({ to: 'waiting_for_response' })
|
|
24
|
+
async createForm(args: { inputText: string }) {
|
|
25
|
+
await this.repository.save(
|
|
26
|
+
MeetingNotesDocument,
|
|
27
|
+
{
|
|
28
|
+
text: `Unstructured Notes:\n\n${args.inputText}`,
|
|
29
|
+
},
|
|
30
|
+
{ id: 'input' },
|
|
31
|
+
);
|
|
32
|
+
}
|
|
40
33
|
|
|
41
|
-
@
|
|
42
|
-
|
|
34
|
+
@Transition({ from: 'waiting_for_response', to: 'response_received', wait: true, schema: MeetingNotesDocumentSchema })
|
|
35
|
+
async userResponse(payload: z.infer<typeof MeetingNotesDocumentSchema>) {
|
|
36
|
+
const result = await this.repository.save(MeetingNotesDocument, payload, { id: 'input' });
|
|
37
|
+
this.meetingNotes = result.content as z.infer<typeof MeetingNotesDocumentSchema>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Transition({ from: 'response_received', to: 'notes_optimized' })
|
|
41
|
+
async optimizeNotes() {
|
|
42
|
+
await this.claudeGenerateDocument.call({
|
|
43
|
+
claude: { model: 'claude-sonnet-4-6' },
|
|
44
|
+
response: {
|
|
45
|
+
id: 'final',
|
|
46
|
+
document: OptimizedNotesDocument,
|
|
47
|
+
},
|
|
48
|
+
prompt: `Extract all information from the provided meeting notes into the structured document.
|
|
49
|
+
|
|
50
|
+
<Meeting Notes>
|
|
51
|
+
${this.meetingNotes?.text}
|
|
52
|
+
</Meeting Notes>`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Final({ from: 'notes_optimized', wait: true, schema: OptimizedMeetingNotesDocumentSchema })
|
|
57
|
+
async confirm(payload: z.infer<typeof OptimizedMeetingNotesDocumentSchema>) {
|
|
58
|
+
const result = await this.repository.save(OptimizedNotesDocument, payload, { id: 'final' });
|
|
59
|
+
this.optimizedNotes = result.content as z.infer<typeof OptimizedMeetingNotesDocumentSchema>;
|
|
60
|
+
}
|
|
43
61
|
}
|