Skip to content

Commit 5d57cca

Browse files
authored
Merge pull request #4 from MobileReality/feat/more-examples
Feat/more examples
2 parents b8b288a + 14740b4 commit 5d57cca

17 files changed

Lines changed: 3264 additions & 13 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ pnpm eval:view
319319
## Initial Roadmap
320320

321321
### v0.2 — Developer Experience
322-
- [ ] More examples (10+ real-world use cases)
322+
- [x] More examples (14 real-world use cases)
323323
- [x] CLI tool for prompt creation (MDMA flows)
324324
- [ ] Improved error messages in parser and validator
325325
- [ ] File upload field type for forms

demo/src/ChatView.tsx

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ChatSettings } from './chat/ChatSettings.js';
55
import { ChatMessage } from './chat/ChatMessage.js';
66
import { ChatInput } from './chat/ChatInput.js';
77
import { ChatActionLog } from './chat/ChatActionLog.js';
8+
import { exampleFlows } from './example-flows.js';
89
import type { MdmaRenderCustomizations } from '@mobile-reality/mdma-renderer-react';
910
import type { ZodType } from 'zod';
1011

@@ -47,8 +48,41 @@ export function ChatView({ customizations, systemPrompt, userSuffix, storageKey,
4748
stop,
4849
clear,
4950
updateMessage,
51+
startFlow,
52+
advanceFlow,
5053
} = useChat(chatOptions);
5154

55+
const advanceFlowRef = useRef(advanceFlow);
56+
advanceFlowRef.current = advanceFlow;
57+
58+
// Subscribe to ACTION_TRIGGERED events on assistant message stores to advance the flow
59+
const subscribedStores = useRef(new Set<import('@mobile-reality/mdma-runtime').DocumentStore>());
60+
61+
useEffect(() => {
62+
for (const msg of messages) {
63+
if (msg.role === 'assistant' && msg.store && !subscribedStores.current.has(msg.store)) {
64+
subscribedStores.current.add(msg.store);
65+
msg.store.getEventBus().on('ACTION_TRIGGERED', () => {
66+
// Small delay so the user sees the interaction before the next step loads
67+
setTimeout(() => advanceFlowRef.current(), 500);
68+
});
69+
}
70+
}
71+
}, [messages]);
72+
73+
// Clean up on unmount
74+
useEffect(() => {
75+
return () => { subscribedStores.current.clear(); };
76+
}, []);
77+
78+
const handleLoadFlow = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
79+
const key = e.target.value;
80+
if (!key) return;
81+
const flow = exampleFlows[key];
82+
if (flow) startFlow(flow.steps, flow.customPrompt);
83+
e.target.value = '';
84+
}, [startFlow]);
85+
5286
const { events, isOpen, setIsOpen, clearEvents } = useChatActionLog(messages);
5387

5488
const chatEndRef = useRef<HTMLDivElement>(null);
@@ -65,6 +99,7 @@ export function ChatView({ customizations, systemPrompt, userSuffix, storageKey,
6599
const handleClear = useCallback(() => {
66100
clear();
67101
clearEvents();
102+
subscribedStores.current.clear();
68103
}, [clear, clearEvents]);
69104

70105
const lastMsgId = messages[messages.length - 1]?.id;
@@ -83,8 +118,28 @@ export function ChatView({ customizations, systemPrompt, userSuffix, storageKey,
83118
<div className="chat-empty">
84119
<p className="chat-empty-title">MDMA Chat</p>
85120
<p className="chat-empty-hint">
86-
Describe an interactive document and the AI will generate it as a live, interactive MDMA form.
121+
Describe an interactive document and the AI will generate it, or try an example flow:
87122
</p>
123+
<select
124+
defaultValue=""
125+
onChange={handleLoadFlow}
126+
style={{
127+
padding: '8px 12px',
128+
borderRadius: '6px',
129+
border: '1px solid #d1d5db',
130+
background: '#fff',
131+
color: '#374151',
132+
fontSize: '14px',
133+
cursor: 'pointer',
134+
marginTop: '8px',
135+
minWidth: '220px',
136+
}}
137+
>
138+
<option value="" disabled>Load an example flow…</option>
139+
{Object.entries(exampleFlows).map(([key, flow]) => (
140+
<option key={key} value={key}>{flow.label}</option>
141+
))}
142+
</select>
88143
</div>
89144
)}
90145

demo/src/chat/use-chat.ts

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ export function useChat(options?: UseChatOptions) {
8686

8787
// Stable refs for options that shouldn't trigger re-renders
8888
const stableStorageKey = useRef(options?.storageKey ?? 'chat').current;
89-
const stableSystemPrompt = useRef(
89+
const defaultSystemPrompt = useRef(
9090
buildSystemPrompt({ customPrompt: options?.systemPrompt }),
9191
).current;
92+
// Active system prompt — can be overridden by a flow's customPrompt
93+
const systemPromptRef = useRef(defaultSystemPrompt);
9294
const stableUserSuffix = useRef(
9395
options?.userSuffix !== undefined ? options.userSuffix : DEFAULT_USER_SUFFIX,
9496
).current;
@@ -229,7 +231,7 @@ export function useChat(options?: UseChatOptions) {
229231

230232
// Build conversation history for the LLM
231233
const history: LlmMessage[] = [
232-
{ role: 'system', content: stableSystemPrompt },
234+
{ role: 'system', content: systemPromptRef.current },
233235
];
234236

235237
for (const m of [...messages, userMsg]) {
@@ -295,8 +297,10 @@ export function useChat(options?: UseChatOptions) {
295297
setInput('');
296298
clearSavedHistory(stableStorageKey);
297299
msgIdRef.current = 0;
300+
flowRef.current = null;
301+
systemPromptRef.current = defaultSystemPrompt;
298302
inputRef.current?.focus();
299-
}, []);
303+
}, [defaultSystemPrompt]);
300304

301305
/** Update an assistant message's content and re-parse it. */
302306
const updateMessage = useCallback(
@@ -309,6 +313,74 @@ export function useChat(options?: UseChatOptions) {
309313
[reparseLastAssistant],
310314
);
311315

316+
// Active flow state for multi-step example flows
317+
const flowRef = useRef<{ steps: { userMessage: string; markdown: string }[]; currentStep: number } | null>(null);
318+
319+
/** Inject a single user+assistant message pair and parse the markdown. */
320+
const injectStep = useCallback(
321+
async (userMessage: string, markdown: string) => {
322+
const userMsg: ChatMsg = {
323+
id: ++msgIdRef.current,
324+
role: 'user',
325+
content: userMessage,
326+
ast: null,
327+
store: null,
328+
};
329+
const assistantMsg: ChatMsg = {
330+
id: ++msgIdRef.current,
331+
role: 'assistant',
332+
content: markdown,
333+
ast: null,
334+
store: null,
335+
};
336+
setMessages((prev) => [...prev, userMsg, assistantMsg]);
337+
338+
const asstId = assistantMsg.id;
339+
try {
340+
const { ast, store } = await parseMarkdownRef.current(markdown);
341+
setMessages((prev) =>
342+
prev.map((m) => (m.id === asstId ? { ...m, ast, store } : m)),
343+
);
344+
} catch {
345+
// parse error — content is still shown as raw text
346+
}
347+
},
348+
[],
349+
);
350+
351+
/** Start a multi-step example flow. Loads the first step immediately. */
352+
const startFlow = useCallback(
353+
async (steps: { userMessage: string; markdown: string }[], customPrompt?: string) => {
354+
if (steps.length === 0) return;
355+
// Override system prompt if a flow-specific custom prompt is provided
356+
systemPromptRef.current = customPrompt
357+
? buildSystemPrompt({ customPrompt })
358+
: defaultSystemPrompt;
359+
flowRef.current = { steps, currentStep: 0 };
360+
await injectStep(steps[0].userMessage, steps[0].markdown);
361+
flowRef.current!.currentStep = 1;
362+
},
363+
[injectStep, defaultSystemPrompt],
364+
);
365+
366+
/** Advance the active flow to the next step (if any). */
367+
const advanceFlow = useCallback(async () => {
368+
const flow = flowRef.current;
369+
if (!flow || flow.currentStep >= flow.steps.length) return;
370+
const step = flow.steps[flow.currentStep];
371+
flow.currentStep++;
372+
await injectStep(step.userMessage, step.markdown);
373+
}, [injectStep]);
374+
375+
/** Inject a pre-built markdown document as a user+assistant message pair. */
376+
const injectDocument = useCallback(
377+
async (label: string, markdown: string) => {
378+
flowRef.current = null; // clear any active flow
379+
await injectStep(`Show me: ${label}`, markdown);
380+
},
381+
[injectStep],
382+
);
383+
312384
return {
313385
config,
314386
messages,
@@ -323,5 +395,8 @@ export function useChat(options?: UseChatOptions) {
323395
stop,
324396
clear,
325397
updateMessage,
398+
injectDocument,
399+
startFlow,
400+
advanceFlow,
326401
};
327402
}

demo/src/documents.ts

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,80 @@
11
import basicForm from '../../examples/basic-form/document.md?raw';
2+
import approvalWorkflow from '../../examples/approval-workflow/document.md?raw';
3+
import employeeOnboarding from '../../examples/employee-onboarding/document.md?raw';
4+
import bugReport from '../../examples/bug-report/document.md?raw';
5+
import surveyFeedback from '../../examples/survey-feedback/document.md?raw';
6+
import orderTracking from '../../examples/order-tracking/document.md?raw';
7+
import meetingNotes from '../../examples/meeting-notes/document.md?raw';
8+
import salesDashboard from '../../examples/sales-dashboard/document.md?raw';
9+
import featureRequest from '../../examples/feature-request/document.md?raw';
210
import incidentTriage from '../../blueprints/incident-triage/document.md?raw';
311
import changeManagement from '../../blueprints/change-management/document.md?raw';
12+
import customerEscalation from '../../blueprints/customer-escalation/document.md?raw';
13+
import clinicalOps from '../../blueprints/clinical-ops/document.md?raw';
14+
import kycCase from '../../blueprints/kyc-case/document.md?raw';
415

516
export interface DocumentEntry {
617
label: string;
718
markdown: string;
819
}
920

1021
export const documents: Record<string, DocumentEntry> = {
22+
// Examples — common use cases
1123
'basic-form': {
12-
label: 'Contact Form (Basic)',
24+
label: 'Contact Form',
1325
markdown: basicForm,
1426
},
27+
'approval-workflow': {
28+
label: 'Budget Approval',
29+
markdown: approvalWorkflow,
30+
},
31+
'employee-onboarding': {
32+
label: 'Employee Onboarding',
33+
markdown: employeeOnboarding,
34+
},
35+
'bug-report': {
36+
label: 'Bug Report',
37+
markdown: bugReport,
38+
},
39+
'survey-feedback': {
40+
label: 'Customer Survey',
41+
markdown: surveyFeedback,
42+
},
43+
'order-tracking': {
44+
label: 'Order Tracking',
45+
markdown: orderTracking,
46+
},
47+
'meeting-notes': {
48+
label: 'Sprint Retrospective',
49+
markdown: meetingNotes,
50+
},
51+
'sales-dashboard': {
52+
label: 'Sales Dashboard',
53+
markdown: salesDashboard,
54+
},
55+
'feature-request': {
56+
label: 'Feature Request',
57+
markdown: featureRequest,
58+
},
59+
// Blueprints — industry-specific workflows
1560
'incident-triage': {
16-
label: 'Incident Triage (Blueprint)',
61+
label: 'Incident Triage',
1762
markdown: incidentTriage,
1863
},
1964
'change-management': {
20-
label: 'Change Management (Blueprint)',
65+
label: 'Change Management',
2166
markdown: changeManagement,
2267
},
68+
'customer-escalation': {
69+
label: 'Customer Escalation',
70+
markdown: customerEscalation,
71+
},
72+
'clinical-ops': {
73+
label: 'Clinical Procedure Approval',
74+
markdown: clinicalOps,
75+
},
76+
'kyc-case': {
77+
label: 'KYC Case Review',
78+
markdown: kycCase,
79+
},
2380
};

0 commit comments

Comments
 (0)