Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/dsl-aware-mdma-il-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@mobile-reality/mdma-prompt-pack": minor
---

Make the `mobile-reality/mdma-il` author variant DSL-aware. The MDMA-IL model
reads an MDMA-IL DSL intent, so its system prompt must describe the DSL grammar;
the previous variant had none. `getAuthorPromptVariant('mobile-reality/mdma-il')`
now returns the full authoring prompt — DSL input grammar, authoring rules, and
worked form/table/chart examples — as the single source of truth (previously
duplicated in the eval harness). The registry label/description are unchanged.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<a href="https://mobilereality.github.io/mdma/#/docs"><b>📖 Docs</b></a>
&nbsp;&nbsp;·&nbsp;&nbsp;
<a href="https://discord.gg/etGSuCuR7B"><b>💬 Discord</b></a>
&nbsp;&nbsp;·&nbsp;&nbsp;
<a href="https://huggingface.co/MobileReality/mdma-gemma4-26b-dsl-unsloth-v1"><b>🤗 Model</b></a>
</p>


Expand Down Expand Up @@ -63,6 +65,17 @@ onAction: submit
````


## Speed comparison

Same scenario, two models. GPT-5.5 and our own hosted MDMA-IL model.

Our model is available on Hugging Face: [MobileReality/mdma-gemma4-26b-dsl-unsloth-v1](https://huggingface.co/MobileReality/mdma-gemma4-26b-dsl-unsloth-v1)

| GPT-5.5 | Our own hosted model |
| :---: | :---: |
| <img src="assets/gpt-5.5.gif" width="100%"> | <img src="assets/own-model.gif" width="100%"> |


## MDMA_AUTHOR prompt matrix

Each cell shows the pass rate of the model-specialized MDMA_AUTHOR prompt variant on the listed eval suite.
Expand Down
Binary file added assets/gpt-5.5.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/own-model.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 87 additions & 1 deletion demo/src/AgentChatView.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,43 @@
import { useRef, useEffect, useCallback } from 'react';
import { useRef, useEffect, useCallback, useState } from 'react';
import { useAgent } from './agent/use-agent.js';
import { useAgentActionLog } from './agent/use-agent-action-log.js';
import { AgentMessage } from './agent/AgentMessage.js';
import { AgentSettings } from './agent/AgentSettings.js';
import { ChatActionLog } from './chat/ChatActionLog.js';
import { ChatInput } from './chat/ChatInput.js';
import type { AssistantTurn, AgentDisplayTurn } from './agent/types.js';

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

// Scripted conversation for the auto-play demo (each entry is one user message).
const DEMO_SCRIPT = [
'hi',
'generate sample form',
'sample chart',
'and table',
'whats Product Name',
'ok could make chart from this table',
'line pls',
];

/**
* Serialize the conversation to a raw transcript: the user's messages and the
* agent's PURE responses (conversational text + the generate_mdma document),
* with only `You:` / `Agent:` to mark who spoke — no other added labels.
*/
function buildRawTranscript(turns: AgentDisplayTurn[]): string {
return turns
.map((turn) => {
if (turn.role === 'user') return turn.hidden ? '' : `You:\n${turn.content}`;
const body = (turn as AssistantTurn).blocks
.map((b) => (b.type === 'tool_use' ? b.document : b.content))
.filter(Boolean)
.join('\n\n');
return `Agent:\n${body}`;
})
.filter(Boolean)
.join('\n\n');
}

export function AgentChatView() {
const {
Expand All @@ -16,13 +49,62 @@ export function AgentChatView() {
config,
updateConfig,
send,
sendText,
stop,
clear,
inputRef,
} = useAgent({ useAuthorSubAgent: true });

const { events, isOpen: logOpen, setIsOpen: setLogOpen, clearEvents } = useAgentActionLog(turns);

const [copiedRaw, setCopiedRaw] = useState(false);
const handleCopyRaw = useCallback(async () => {
try {
await navigator.clipboard.writeText(buildRawTranscript(turns));
setCopiedRaw(true);
setTimeout(() => setCopiedRaw(false), 1500);
} catch {
/* clipboard unavailable */
}
}, [turns]);

// ── Auto-play demo ──────────────────────────────────────────────────────────
// Replays a scripted conversation through the real agent: types each message,
// sends it, waits for the full response, then the next. For demo recordings.
const [isPlaying, setIsPlaying] = useState(false);
const playingRef = useRef(false);
const handlePlayDemo = useCallback(async () => {
if (playingRef.current) {
// already running → stop
playingRef.current = false;
setIsPlaying(false);
return;
}
playingRef.current = true;
setIsPlaying(true);
clear();
clearEvents();
await sleep(500);

for (const msg of DEMO_SCRIPT) {
if (!playingRef.current) break;
// Typewriter the message into the input for a natural look.
for (let k = 1; k <= msg.length; k++) {
if (!playingRef.current) break;
setInput(msg.slice(0, k));
await sleep(28);
}
await sleep(350);
if (!playingRef.current) break;
setInput('');
await sendText(msg); // renders the user bubble + awaits the agent's reply
await sleep(1100); // beat between turns
}

playingRef.current = false;
setIsPlaying(false);
}, [clear, clearEvents, sendText, setInput]);

const chatEndRef = useRef<HTMLDivElement>(null);
const prevCountRef = useRef(turns.length);

Expand Down Expand Up @@ -72,6 +154,10 @@ export function AgentChatView() {
isGenerating={isGenerating}
hasMessages={turns.length > 0}
inputRef={inputRef}
onCopyRaw={handleCopyRaw}
copiedRaw={copiedRaw}
onPlayDemo={handlePlayDemo}
isPlaying={isPlaying}
/>
</div>

Expand Down
18 changes: 16 additions & 2 deletions demo/src/PreviewView.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { useRef, useEffect, useCallback, useState } from 'react';
import { useAgent } from './agent/use-agent.js';
import { useCallback, useEffect, useRef, useState } from 'react';
import { AgentMessage } from './agent/AgentMessage.js';
import { AgentSettings } from './agent/AgentSettings.js';
import { useAgent } from './agent/use-agent.js';
import { ChatInput } from './chat/ChatInput.js';
import { BackendLogDrawer } from './preview/BackendLogPane.js';
import { PreviewPanel } from './preview/PreviewPanel.js';
import { clearSubmissionLog } from './preview/insurance-backend.js';
import { INSURANCE_FLOW_PROMPT } from './preview/insurance-flow-prompt.js';
import { useInsuranceFlow } from './preview/use-insurance-flow.js';
import { usePreviewAutoplay } from './preview/use-preview-autoplay.js';
import { usePreviewValidation } from './preview/use-preview-validation.js';

function countToolUseBlocks(turns: ReturnType<typeof useAgent>['turns']): number {
Expand All @@ -29,6 +30,7 @@ export function PreviewView() {
config,
updateConfig,
send,
sendText,
sendHidden,
stop,
clear,
Expand Down Expand Up @@ -74,6 +76,16 @@ export function PreviewView() {
insuranceFlow.reset();
}, [clear, insuranceFlow]);

// Auto-play the full claim flow hands-free (kickoff message → fill & submit
// each step form). Mirrors the scripted demo in the Agent Chat view.
const { isPlaying, play } = usePreviewAutoplay({
previewState,
isGenerating,
sendText,
setInput,
reset: handleClear,
});

return (
<div className="preview-layout">
<div className="preview-chat">
Expand Down Expand Up @@ -115,6 +127,8 @@ export function PreviewView() {
isGenerating={isGenerating}
hasMessages={turns.length > 0}
inputRef={inputRef}
onPlayDemo={play}
isPlaying={isPlaying}
/>
</div>

Expand Down
75 changes: 54 additions & 21 deletions demo/src/agent/AgentMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { memo } from 'react';
import { memo, useState } from 'react';
import type { ReactNode } from 'react';
import { MdmaDocument } from '@mobile-reality/mdma-renderer-react';
import { customizations } from '../custom-components.js';
import type {
AgentBlock,
AgentDisplayTurn,
AssistantTurn,
ThinkingBlock,
TextBlock,
ToolUseBlock,
} from './types.js';

// Raw dump of an assistant turn — the pure model output (conversational text +
// the generate_mdma document), with no added labels, for debugging messaging.
function buildRawDump(blocks: AgentBlock[]): string {
return blocks
.map((b) => (b.type === 'tool_use' ? b.document : b.content))
.filter(Boolean)
.join('\n\n');
}

// ── Inline markdown renderer ──────────────────────────────────────────────────

function parseInline(text: string): ReactNode {
Expand Down Expand Up @@ -270,6 +280,8 @@ export const AgentMessage = memo(function AgentMessage({
activeToolUseId,
onSelectToolUse,
}: AgentMessageProps) {
const [showRaw, setShowRaw] = useState(false);

if (turn.role === 'user') {
if (turn.hidden) return null;
return (
Expand All @@ -286,32 +298,53 @@ export const AgentMessage = memo(function AgentMessage({

const { blocks } = turn as AssistantTurn;

const hasContent = blocks.some(
(b) => (b.type === 'text' || b.type === 'thinking' ? b.content : (b as ToolUseBlock).document),
);

return (
<div className="chat-msg chat-msg--assistant">
<div className="chat-msg-header">
<span className="chat-msg-label">Agent</span>
</div>
<div className="chat-msg-body agent-blocks">
{blocks.length === 0 ? (
<span className="chat-msg-typing">Starting…</span>
) : (
blocks.map((block) => {
if (block.type === 'thinking')
return <ThinkingBlockView key={block.id} block={block} />;
if (block.type === 'text') return <TextBlockView key={block.id} block={block} />;
if (block.type === 'tool_use')
return (
<ToolUseBlockView
key={block.id}
block={block}
compact={compactToolUse}
isActive={activeToolUseId === block.id}
onSelect={onSelectToolUse ? () => onSelectToolUse(block.id) : undefined}
/>
);
})
{hasContent && (
<button
type="button"
className="agent-raw-toggle"
data-active={showRaw ? 'true' : undefined}
onClick={() => setShowRaw((v) => !v)}
title="Show the raw model output (text + generate_mdma document) for debugging"
>
{showRaw ? 'Hide raw' : 'Raw'}
</button>
)}
</div>
{showRaw ? (
<div className="chat-msg-body agent-blocks">
<pre className="agent-raw">{buildRawDump(blocks)}</pre>
</div>
) : (
<div className="chat-msg-body agent-blocks">
{blocks.length === 0 ? (
<span className="chat-msg-typing">Starting…</span>
) : (
blocks.map((block) => {
if (block.type === 'thinking')
return <ThinkingBlockView key={block.id} block={block} />;
if (block.type === 'text') return <TextBlockView key={block.id} block={block} />;
if (block.type === 'tool_use')
return (
<ToolUseBlockView
key={block.id}
block={block}
compact={compactToolUse}
isActive={activeToolUseId === block.id}
onSelect={onSelectToolUse ? () => onSelectToolUse(block.id) : undefined}
/>
);
})
)}
</div>
)}
</div>
);
});
Loading