import React, { useEffect, useState, useRef } from 'react';
function useWasm() {
const [pm, setPm] = useState(null);
const [isReady, setIsReady] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const initWasm = async () => {
try {
const mockModule = {
init: () => console.log('WASM initialized'),
get_version: () => '0.5.4',
load_eventlog_from_xes: (content) => 'handle_' + Math.random(),
load_ocel_from_json: (content) => 'handle_' + Math.random(),
analyze_event_statistics: (handle) =>
JSON.stringify({
num_traces: 3,
num_events: 14,
num_activities: 5,
}),
analyze_case_duration: (handle) =>
JSON.stringify({
min_duration: 3600000,
max_duration: 7200000,
mean_duration: 5400000,
}),
analyze_dotted_chart: (handle) => JSON.stringify({ dotted_chart: [] }),
discover_dfg: (handle) => JSON.stringify({ nodes: [], edges: [] }),
discover_alpha_plus_plus: (handle, threshold) =>
JSON.stringify({ places: [], transitions: [] }),
discover_declare: (handle) => JSON.stringify({ constraints: [] }),
object_count: () => 0,
clear_all_objects: () => {},
};
setPm(mockModule);
mockModule.init();
setIsReady(true);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to initialize WASM');
}
};
initWasm();
}, []);
return { pm, isReady, error };
}
const ProcessMiningDemo = () => {
const { pm, isReady, error: wasmError } = useWasm();
const [xesInput, setXesInput] = useState('');
const [ocelInput, setOcelInput] = useState('');
const [eventLog, setEventLog] = useState(null);
const [analysisResults, setAnalysisResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [objectCount, setObjectCount] = useState(0);
const fileInputRef = useRef(null);
const handleLoadXES = () => {
if (!pm || !xesInput.trim()) {
setError('Please enter XES content');
return;
}
try {
setIsLoading(true);
const handle = pm.load_eventlog_from_xes(xesInput);
const statsJson = pm.analyze_event_statistics(handle);
const stats = JSON.parse(statsJson);
setEventLog({ handle, stats });
setError(null);
updateObjectCount();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load XES');
} finally {
setIsLoading(false);
}
};
const handleLoadOCEL = () => {
if (!pm || !ocelInput.trim()) {
setError('Please enter OCEL content');
return;
}
try {
setIsLoading(true);
const handle = pm.load_ocel_from_json(ocelInput);
setEventLog({ handle });
setError(null);
updateObjectCount();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load OCEL');
} finally {
setIsLoading(false);
}
};
const handleFileUpload = (e) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const content = event.target?.result;
if (file.name.endsWith('.xes') || file.name.endsWith('.xml')) {
setXesInput(content);
} else if (file.name.endsWith('.json')) {
setOcelInput(content);
}
};
reader.readAsText(file);
};
const runAnalysis = (type) => {
if (!pm || !eventLog) {
setError('Please load an event log first');
return;
}
try {
setIsLoading(true);
let result;
let dataType;
switch (type) {
case 'statistics':
result = JSON.parse(pm.analyze_event_statistics(eventLog.handle));
dataType = 'statistics';
break;
case 'duration':
result = JSON.parse(pm.analyze_case_duration(eventLog.handle));
dataType = 'duration';
break;
case 'dfg':
result = JSON.parse(pm.discover_dfg(eventLog.handle));
dataType = 'dfg';
break;
case 'alpha':
result = JSON.parse(pm.discover_alpha_plus_plus(eventLog.handle, 0));
dataType = 'petri_net';
break;
case 'declare':
result = JSON.parse(pm.discover_declare(eventLog.handle));
dataType = 'declare';
break;
default:
return;
}
setAnalysisResults((prev) => [
...prev,
{ type: dataType, data: result, timestamp: Date.now() },
]);
setError(null);
} catch (err) {
setError('Analysis failed: ' + (err instanceof Error ? err.message : 'Unknown error'));
} finally {
setIsLoading(false);
}
};
const updateObjectCount = () => {
if (pm) {
setObjectCount(pm.object_count());
}
};
const handleClearAll = () => {
if (pm) {
pm.clear_all_objects();
setEventLog(null);
setAnalysisResults([]);
setXesInput('');
setOcelInput('');
setObjectCount(0);
setError(null);
}
};
const loadSampleXES = () => {
const sample = `<?xml version="1.0" encoding="UTF-8"?>
<log xes.version="1.0">
<extension name="Concept" prefix="concept" uri="http://www.xes-standard.org/concept.xesext"/>
<extension name="Time" prefix="time" uri="http://www.xes-standard.org/time.xesext"/>
<global scope="trace"><string key="concept:name" value="undefined"/></global>
<global scope="event">
<string key="concept:name" value="undefined"/>
<date key="time:timestamp" value="1970-01-01T00:00:00"/>
</global>
<trace>
<string key="concept:name" value="Case001"/>
<event><string key="concept:name" value="Request"/><date key="time:timestamp" value="2023-01-01T08:00:00"/></event>
<event><string key="concept:name" value="Review"/><date key="time:timestamp" value="2023-01-01T10:00:00"/></event>
<event><string key="concept:name" value="Approve"/><date key="time:timestamp" value="2023-01-01T12:00:00"/></event>
</trace>
</log>`;
setXesInput(sample);
};
if (wasmError) {
return (
<div style={{ padding: '20px', color: 'red' }}>
<h2>Failed to Initialize</h2>
<p>{wasmError}</p>
</div>
);
}
if (!isReady) {
return (
<div style={{ padding: '20px' }}>
<h2>Loading Rust4PM WASM...</h2>
<p>Please wait while the WebAssembly module is being initialized.</p>
</div>
);
}
return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '20px' }}>
<h1>Rust4PM Process Mining in React</h1>
<div style={{ marginBottom: '20px' }}>
<h2>1. Load Event Log</h2>
<div style={{ marginBottom: '10px' }}>
<h3>XES File</h3>
<textarea
value={xesInput}
onChange={(e) => setXesInput(e.target.value)}
placeholder="Paste XES content or load sample"
style={{
width: '100%',
height: '150px',
padding: '10px',
fontFamily: 'monospace',
}}
/>
<div style={{ marginTop: '10px' }}>
<button onClick={handleLoadXES} disabled={isLoading}>
Load XES
</button>
<button onClick={loadSampleXES} style={{ marginLeft: '10px' }}>
Load Sample
</button>
</div>
</div>
<div style={{ marginBottom: '10px' }}>
<h3>OCEL File</h3>
<textarea
value={ocelInput}
onChange={(e) => setOcelInput(e.target.value)}
placeholder="Paste OCEL JSON content"
style={{
width: '100%',
height: '150px',
padding: '10px',
fontFamily: 'monospace',
}}
/>
<button onClick={handleLoadOCEL} disabled={isLoading} style={{ marginTop: '10px' }}>
Load OCEL
</button>
</div>
<div style={{ marginBottom: '10px' }}>
<h3>Upload File</h3>
<input
ref={fileInputRef}
type="file"
accept=".xes,.json,.xml"
onChange={handleFileUpload}
/>
</div>
</div>
{eventLog && (
<div style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#f0f0f0' }}>
<h2>Loaded Event Log</h2>
<p>Handle: {eventLog.handle}</p>
{eventLog.stats && (
<ul>
<li>Traces: {eventLog.stats.num_traces}</li>
<li>Events: {eventLog.stats.num_events}</li>
<li>Activities: {eventLog.stats.num_activities}</li>
</ul>
)}
</div>
)}
<div style={{ marginBottom: '20px' }}>
<h2>2. Analysis & Discovery</h2>
<div style={{ marginBottom: '10px' }}>
<h3>Analysis Functions</h3>
<button onClick={() => runAnalysis('statistics')} disabled={!eventLog || isLoading}>
Event Statistics
</button>
<button
onClick={() => runAnalysis('duration')}
disabled={!eventLog || isLoading}
style={{ marginLeft: '10px' }}
>
Case Duration
</button>
</div>
<div style={{ marginBottom: '10px' }}>
<h3>Discovery Algorithms</h3>
<button onClick={() => runAnalysis('dfg')} disabled={!eventLog || isLoading}>
Discover DFG
</button>
<button
onClick={() => runAnalysis('alpha')}
disabled={!eventLog || isLoading}
style={{ marginLeft: '10px' }}
>
Discover Alpha++
</button>
<button
onClick={() => runAnalysis('declare')}
disabled={!eventLog || isLoading}
style={{ marginLeft: '10px' }}
>
Discover DECLARE
</button>
</div>
</div>
<div style={{ marginBottom: '20px' }}>
<h2>3. Results</h2>
{error && (
<div
style={{
padding: '10px',
backgroundColor: '#ffcccc',
color: 'red',
marginBottom: '10px',
}}
>
{error}
</div>
)}
{isLoading && <p>Processing...</p>}
<div style={{ marginBottom: '10px' }}>
<p>Objects stored: {objectCount}</p>
<button onClick={updateObjectCount} style={{ marginRight: '10px' }}>
Refresh Count
</button>
<button onClick={handleClearAll}>Clear All</button>
</div>
<div>
{analysisResults.map((result, idx) => (
<div
key={idx}
style={{
marginBottom: '10px',
padding: '10px',
border: '1px solid #ddd',
borderRadius: '4px',
}}
>
<h4>{result.type}</h4>
<pre
style={{
backgroundColor: '#f5f5f5',
padding: '10px',
overflow: 'auto',
maxHeight: '300px',
}}
>
{JSON.stringify(result.data, null, 2)}
</pre>
<small>{new Date(result.timestamp).toLocaleTimeString()}</small>
</div>
))}
</div>
</div>
</div>
);
};
export default ProcessMiningDemo;