Optitransfer commited on
Commit
d24d9f9
Β·
verified Β·
1 Parent(s): 06c3a56

Deploy crdt-merge-data v0.9.4

Browse files
Files changed (3) hide show
  1. README.md +37 -6
  2. app.py +448 -0
  3. requirements.txt +6 -0
README.md CHANGED
@@ -1,12 +1,43 @@
1
  ---
2
- title: Crdt Merge Data
3
- emoji: πŸ’»
4
- colorFrom: pink
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 6.11.0
8
  app_file: app.py
9
  pinned: false
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: crdt-merge Data Playground
3
+ colorFrom: gray
4
+ colorTo: gray
 
5
  sdk: gradio
6
+ sdk_version: 4.44.0
7
  app_file: app.py
8
  pinned: false
9
+ license: other
10
+ license_name: BUSL-1.1
11
+ license_link: https://github.com/mgillr/crdt-merge/blob/main/LICENSE
12
+ tags:
13
+ - crdt
14
+ - merge
15
+ - dataframe
16
+ - conflict-free
17
+ - distributed
18
  ---
19
 
20
+ # crdt-merge Data Playground
21
+
22
+ Tabular CRDT merge, conflict analysis, and core primitive demonstrations powered by [crdt-merge v0.9.4](https://github.com/mgillr/crdt-merge).
23
+
24
+ ## Tabs
25
+
26
+ **Dataset Merge** β€” Loads glue/sst2 from HuggingFace datasets (or synthetic fallback). Merges two node partitions (150 + 100 records, 50 overlapping) with configurable strategy. Verifies commutativity: merge(A,B) == merge(B,A).
27
+
28
+ **Conflict Analysis** β€” Runs all four strategies (LWW, MaxWins, MinWins, Union) on the same dataset and computes per-field conflict rates between strategy pairs as a heatmap.
29
+
30
+ **Core CRDT Primitives** β€” Live demonstration of GCounter, PNCounter, LWWRegister, and ORSet. Each primitive is operated on two independent nodes then merged in both directions. Commutativity is verified for all four primitives.
31
+
32
+ ## Installation
33
+
34
+ ```
35
+ pip install crdt-merge>=0.9.4
36
+ ```
37
+
38
+ ## License
39
+
40
+ Business Source License 1.1. Converts to Apache 2.0 on 2028-03-29.
41
+ Patent Pending UK 2607132.4.
42
+
43
+ crdt-merge v0.9.4 Β· [github.com/mgillr/crdt-merge](https://github.com/mgillr/crdt-merge)
app.py ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: BUSL-1.1
2
+ # Copyright 2026 Ryan Gillespie / Optitransfer
3
+ #
4
+ # Licensed under the Business Source License 1.1 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # https://github.com/mgillr/crdt-merge/blob/main/LICENSE
9
+ #
10
+ # Change Date: 2028-03-29
11
+ # Change License: Apache License, Version 2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # On 2028-03-29 this file converts to Apache License, Version 2.0.
17
+
18
+ """
19
+ crdt-merge v0.9.4 β€” Data Playground HuggingFace Space
20
+ Tabular CRDT merge, conflict analysis, and core primitive demonstrations.
21
+ """
22
+
23
+ import os
24
+ import json
25
+ import time
26
+ import numpy as np
27
+ import gradio as gr
28
+ import plotly.graph_objects as go
29
+
30
+ CSS = """
31
+ .gradio-container { background: #09090b !important; font-family: 'Inter', system-ui, sans-serif !important; }
32
+ .gr-button-primary { background: #2563eb !important; border: none !important; color: #fafafa !important; }
33
+ footer { display: none !important; }
34
+ .tab-nav button { color: #71717a !important; font-size: 13px !important; letter-spacing: 0.05em !important; text-transform: uppercase !important; }
35
+ .tab-nav button.selected { color: #fafafa !important; border-bottom: 2px solid #3b82f6 !important; }
36
+ code, .monospace { font-family: 'JetBrains Mono', ui-monospace, monospace !important; font-size: 12px !important; }
37
+ """
38
+
39
+ PLOTLY_LAYOUT = dict(
40
+ paper_bgcolor="#09090b",
41
+ plot_bgcolor="#18181b",
42
+ font=dict(color="#a1a1aa", family="Inter"),
43
+ xaxis=dict(gridcolor="#27272a", linecolor="#27272a"),
44
+ yaxis=dict(gridcolor="#27272a", linecolor="#27272a"),
45
+ margin=dict(l=60, r=20, t=40, b=60),
46
+ )
47
+
48
+ THEME = gr.themes.Base(
49
+ primary_hue=gr.themes.colors.blue,
50
+ neutral_hue=gr.themes.colors.zinc,
51
+ )
52
+
53
+ HERO_MD = """
54
+ # crdt-merge v0.9.4 β€” Data Playground
55
+
56
+ Tabular CRDT merge for DataFrames and datasets. Conflict-free record merge, deduplication, and provenance tracking.
57
+
58
+ `pip install crdt-merge` Β· [github.com/mgillr/crdt-merge](https://github.com/mgillr/crdt-merge)
59
+ """
60
+
61
+ STRATEGIES_DF = ["LWW", "MaxWins", "MinWins", "Union"]
62
+
63
+
64
+ # ─────────────────────────────────────────────────────────────────
65
+ # Data loading
66
+ # ─────────────────────────────────────────────────────────────────
67
+
68
+ def _load_dataset_records():
69
+ """Try HF datasets first, fallback to synthetic."""
70
+ source = "synthetic"
71
+ records_a = []
72
+ records_b = []
73
+
74
+ try:
75
+ from datasets import load_dataset
76
+ ds = load_dataset("glue", "sst2", split="train[:200]")
77
+ all_records = [{"id": i, "sentence": ds[i]["sentence"], "label": ds[i]["label"], "_ts": i}
78
+ for i in range(len(ds))]
79
+ records_a = all_records[:150]
80
+ records_b = all_records[100:] # 50 overlapping + 50 new
81
+ source = "glue/sst2 (HuggingFace datasets, first 200 rows)"
82
+ except Exception:
83
+ pass
84
+
85
+ if not records_a:
86
+ rng = np.random.RandomState(7)
87
+ adjectives = ["good", "bad", "great", "poor", "excellent", "terrible", "fine", "awful"]
88
+ nouns = ["film", "movie", "picture", "show", "performance", "script", "cast", "story"]
89
+ for i in range(200):
90
+ adj = adjectives[i % len(adjectives)]
91
+ noun = nouns[i % len(nouns)]
92
+ records_a.append({"id": i, "sentence": f"A {adj} {noun}.", "label": i % 2, "_ts": i})
93
+ for i in range(100, 200):
94
+ adj = adjectives[(i + 3) % len(adjectives)]
95
+ noun = nouns[(i + 2) % len(nouns)]
96
+ records_b.append({"id": i, "sentence": f"An {adj} {noun}.", "label": (i + 1) % 2, "_ts": i + 50})
97
+ for i in range(200, 250):
98
+ adj = adjectives[i % len(adjectives)]
99
+ noun = nouns[i % len(nouns)]
100
+ records_b.append({"id": i, "sentence": f"The {adj} {noun}.", "label": i % 2, "_ts": i})
101
+ source = "synthetic (SST-2 style, 150 + 100 records with 50 overlap)"
102
+
103
+ return records_a, records_b, source
104
+
105
+
106
+ # ─────────────────────────────────────────────────────────────────
107
+ # TAB 1 β€” Dataset Merge
108
+ # ─────────────────────────────────────────────────────────────────
109
+
110
+ def run_dataset_merge(strategy_name: str):
111
+ from crdt_merge.dataframe import merge as df_merge
112
+ from crdt_merge.strategies import MergeSchema, LWW, MaxWins, MinWins, Union
113
+
114
+ strategy_map = {
115
+ "LWW": LWW(),
116
+ "MaxWins": MaxWins(),
117
+ "MinWins": MinWins(),
118
+ "Union": Union(),
119
+ }
120
+ schema = MergeSchema(default=strategy_map[strategy_name])
121
+
122
+ records_a, records_b, source = _load_dataset_records()
123
+ t0 = time.perf_counter()
124
+
125
+ try:
126
+ merged = df_merge(records_a, records_b, key="id", schema=schema, timestamp_col="_ts")
127
+ elapsed = (time.perf_counter() - t0) * 1000
128
+
129
+ # Verify commutativity
130
+ merged_ba = df_merge(records_b, records_a, key="id", schema=schema, timestamp_col="_ts")
131
+ ids_ab = sorted([r["id"] for r in merged])
132
+ ids_ba = sorted([r["id"] for r in merged_ba])
133
+ comm_pass = ids_ab == ids_ba
134
+
135
+ summary_md = f"""
136
+ **Dataset Merge Complete**
137
+
138
+ | Metric | Value |
139
+ |---|---|
140
+ | Source | {source} |
141
+ | Strategy | {strategy_name} |
142
+ | Node A records | {len(records_a)} |
143
+ | Node B records | {len(records_b)} |
144
+ | Overlapping IDs | {len(set(r['id'] for r in records_a) & set(r['id'] for r in records_b))} |
145
+ | Merged records | {len(merged)} |
146
+ | Elapsed | {elapsed:.1f}ms |
147
+ | Commutative (merge_AB == merge_BA) | **{"PASS" if comm_pass else "FAIL"}** |
148
+ """
149
+
150
+ display_rows = merged[:20]
151
+ return display_rows, summary_md
152
+
153
+ except Exception as e:
154
+ return [], f"Error: {e}"
155
+
156
+
157
+ # ─────────────────────────────────────────────────────────────────
158
+ # TAB 2 β€” Conflict Analysis
159
+ # ─────────────────────────────────────────────────────────────────
160
+
161
+ def run_conflict_analysis():
162
+ from crdt_merge.dataframe import merge as df_merge
163
+ from crdt_merge.strategies import MergeSchema, LWW, MaxWins, MinWins, Union
164
+
165
+ records_a, records_b, source = _load_dataset_records()
166
+ overlap_ids = set(r["id"] for r in records_a) & set(r["id"] for r in records_b)
167
+
168
+ strategy_map = {
169
+ "LWW": LWW(),
170
+ "MaxWins": MaxWins(),
171
+ "MinWins": MinWins(),
172
+ "Union": Union(),
173
+ }
174
+
175
+ fields = ["sentence", "label"]
176
+ results_by_strategy = {}
177
+
178
+ for strat_name, strat in strategy_map.items():
179
+ schema = MergeSchema(default=strat)
180
+ try:
181
+ merged = df_merge(records_a, records_b, key="id", schema=schema, timestamp_col="_ts")
182
+ results_by_strategy[strat_name] = {r["id"]: r for r in merged if r["id"] in overlap_ids}
183
+ except Exception as e:
184
+ results_by_strategy[strat_name] = {}
185
+
186
+ # Build conflict matrix: per-field, per-strategy-pair, how many records differ
187
+ strat_names = list(strategy_map.keys())
188
+ conflict_matrix = {}
189
+ for field in fields:
190
+ conflict_matrix[field] = np.zeros((len(strat_names), len(strat_names)), dtype=np.float32)
191
+ for i, s1 in enumerate(strat_names):
192
+ for j, s2 in enumerate(strat_names):
193
+ if i == j:
194
+ continue
195
+ diffs = 0
196
+ total = 0
197
+ for rid in overlap_ids:
198
+ r1 = results_by_strategy[s1].get(rid)
199
+ r2 = results_by_strategy[s2].get(rid)
200
+ if r1 is not None and r2 is not None:
201
+ total += 1
202
+ if str(r1.get(field, "")) != str(r2.get(field, "")):
203
+ diffs += 1
204
+ conflict_matrix[field][i, j] = diffs / max(total, 1)
205
+
206
+ # Heatmap: combine fields side by side
207
+ combined_z = np.concatenate([conflict_matrix[f] for f in fields], axis=1)
208
+ col_labels = [f"{f}:{s}" for f in fields for s in strat_names]
209
+
210
+ fig = go.Figure(data=go.Heatmap(
211
+ z=combined_z.tolist(),
212
+ x=col_labels,
213
+ y=strat_names,
214
+ colorscale=[[0, "#18181b"], [1, "#3b82f6"]],
215
+ showscale=True,
216
+ colorbar=dict(title="Conflict Rate"),
217
+ ))
218
+ fig.update_layout(
219
+ **PLOTLY_LAYOUT,
220
+ title=f"Per-Field Conflict Matrix β€” Strategy vs Strategy (source: {source[:40]}...)",
221
+ xaxis_title="Field : Strategy (column)",
222
+ yaxis_title="Strategy (row)",
223
+ )
224
+
225
+ # Summary table: how many overlapping records each strategy resolves differently from LWW
226
+ summary_rows = []
227
+ for strat_name in strat_names:
228
+ diffs_vs_lww = 0
229
+ for rid in overlap_ids:
230
+ r_lww = results_by_strategy["LWW"].get(rid)
231
+ r_s = results_by_strategy[strat_name].get(rid)
232
+ if r_lww and r_s:
233
+ for field in fields:
234
+ if str(r_lww.get(field, "")) != str(r_s.get(field, "")):
235
+ diffs_vs_lww += 1
236
+ break
237
+ summary_rows.append({
238
+ "Strategy": strat_name,
239
+ "Conflicts vs LWW": diffs_vs_lww,
240
+ "Overlap Records": len(overlap_ids),
241
+ "Conflict Rate": f"{diffs_vs_lww / max(len(overlap_ids), 1):.2%}",
242
+ })
243
+
244
+ return summary_rows, fig
245
+
246
+
247
+ # ─────────────────────────────────────────────────────────────────
248
+ # TAB 3 β€” Core CRDT Primitives
249
+ # ─────────────────────────────────────────────────────────────────
250
+
251
+ def run_primitives_demo():
252
+ from crdt_merge.core import GCounter, PNCounter, LWWRegister, ORSet
253
+
254
+ results = {}
255
+
256
+ # GCounter
257
+ gc_a = GCounter()
258
+ gc_a.increment("node_A", 5)
259
+ gc_a.increment("node_A", 3)
260
+ gc_b = GCounter()
261
+ gc_b.increment("node_B", 7)
262
+ gc_merged_ab = gc_a.merge(gc_b)
263
+ gc_merged_ba = gc_b.merge(gc_a)
264
+ results["GCounter"] = {
265
+ "node_A_ops": "gc_a.increment('node_A', 5); gc_a.increment('node_A', 3) # value=8",
266
+ "node_B_ops": "gc_b.increment('node_B', 7) # value=7",
267
+ "merge_AB_value": gc_merged_ab.value,
268
+ "merge_BA_value": gc_merged_ba.value,
269
+ "commutative": gc_merged_ab.value == gc_merged_ba.value,
270
+ }
271
+
272
+ # PNCounter
273
+ pn_a = PNCounter()
274
+ pn_a.increment("n", 10)
275
+ pn_a.decrement("n", 3)
276
+ pn_b = PNCounter()
277
+ pn_b.increment("n", 5)
278
+ pn_merged_ab = pn_a.merge(pn_b)
279
+ pn_merged_ba = pn_b.merge(pn_a)
280
+ results["PNCounter"] = {
281
+ "node_A_ops": "pn_a.increment('n', 10); pn_a.decrement('n', 3) # value=7",
282
+ "node_B_ops": "pn_b.increment('n', 5) # value=5",
283
+ "merge_AB_value": pn_merged_ab.value,
284
+ "merge_BA_value": pn_merged_ba.value,
285
+ "commutative": pn_merged_ab.value == pn_merged_ba.value,
286
+ }
287
+
288
+ # LWWRegister
289
+ lww_a = LWWRegister()
290
+ lww_a.set("model_v1", timestamp=1.0)
291
+ lww_a.set("model_v2", timestamp=3.0)
292
+ lww_b = LWWRegister()
293
+ lww_b.set("model_v3", timestamp=2.0)
294
+ lww_merged_ab = lww_a.merge(lww_b)
295
+ lww_merged_ba = lww_b.merge(lww_a)
296
+ results["LWWRegister"] = {
297
+ "node_A_ops": "lww_a.set('model_v1', timestamp=1.0); lww_a.set('model_v2', timestamp=3.0)",
298
+ "node_B_ops": "lww_b.set('model_v3', timestamp=2.0)",
299
+ "merge_AB_value": str(lww_merged_ab.value),
300
+ "merge_BA_value": str(lww_merged_ba.value),
301
+ "commutative": str(lww_merged_ab.value) == str(lww_merged_ba.value),
302
+ }
303
+
304
+ # ORSet
305
+ orset_a = ORSet()
306
+ orset_a.add("alpha")
307
+ orset_a.add("beta")
308
+ tag_beta = orset_a.add("gamma")
309
+ orset_b = ORSet()
310
+ orset_b.add("beta")
311
+ orset_b.add("delta")
312
+ orset_merged_ab = orset_a.merge(orset_b)
313
+ orset_merged_ba = orset_b.merge(orset_a)
314
+ results["ORSet"] = {
315
+ "node_A_ops": "orset_a.add('alpha'); orset_a.add('beta'); orset_a.add('gamma')",
316
+ "node_B_ops": "orset_b.add('beta'); orset_b.add('delta')",
317
+ "merge_AB_value": str(sorted(orset_merged_ab.value)),
318
+ "merge_BA_value": str(sorted(orset_merged_ba.value)),
319
+ "commutative": sorted(orset_merged_ab.value) == sorted(orset_merged_ba.value),
320
+ }
321
+
322
+ rows = []
323
+ for name, data in results.items():
324
+ rows.append({
325
+ "Primitive": name,
326
+ "Node A Operations": data["node_A_ops"],
327
+ "Node B Operations": data["node_B_ops"],
328
+ "merge(A,B) Value": str(data["merge_AB_value"]),
329
+ "merge(B,A) Value": str(data["merge_BA_value"]),
330
+ "Commutative": "PASS" if data["commutative"] else "FAIL",
331
+ })
332
+
333
+ return rows
334
+
335
+
336
+
337
+ # ─────────────────────────────────────────────────────────────────
338
+ # Gradio UI
339
+ # ─────────────────────────────────────────────────────────────────
340
+
341
+ with gr.Blocks(theme=THEME, css=CSS, title="crdt-merge β€” Data Playground") as demo:
342
+ gr.Markdown(HERO_MD)
343
+
344
+ with gr.Tabs():
345
+
346
+ # ── TAB 1 ──────────────────────────────────────────────────────
347
+ with gr.Tab("Dataset Merge"):
348
+ gr.Markdown("""
349
+ ## Dataset Merge
350
+
351
+ Loads glue/sst2 from HuggingFace datasets (first 200 rows) or uses synthetic fallback.
352
+ Splits into two node partitions with 50 overlapping records.
353
+ Demonstrates conflict-free merge with configurable strategy.
354
+ """)
355
+
356
+ with gr.Row():
357
+ strat_dd = gr.Dropdown(
358
+ choices=STRATEGIES_DF,
359
+ value="LWW",
360
+ label="Merge Strategy",
361
+ info="LWW = Last Write Wins (by timestamp). MaxWins/MinWins = field max/min. Union = set union.",
362
+ )
363
+ merge_ds_btn = gr.Button("Run Dataset Merge", variant="primary")
364
+
365
+ merge_summary_md = gr.Markdown()
366
+ merge_result_table = gr.Dataframe(
367
+ headers=["id", "sentence", "label", "_ts"],
368
+ label="Merged Records (first 20 rows)",
369
+ wrap=True,
370
+ )
371
+
372
+ def _run_ds_merge(strategy):
373
+ rows, summary = run_dataset_merge(strategy)
374
+ df_data = [[r.get("id", ""), r.get("sentence", ""), r.get("label", ""), r.get("_ts", "")] for r in rows]
375
+ return summary, df_data
376
+
377
+ merge_ds_btn.click(_run_ds_merge, inputs=[strat_dd], outputs=[merge_summary_md, merge_result_table])
378
+ demo.load(lambda: _run_ds_merge("LWW"), outputs=[merge_summary_md, merge_result_table])
379
+
380
+ # ── TAB 2 ──────────────────────────────────────────────────────
381
+ with gr.Tab("Conflict Analysis"):
382
+ gr.Markdown("""
383
+ ## Conflict Analysis
384
+
385
+ Runs the same dataset through all 4 strategies and computes per-field conflict rates
386
+ between strategy pairs. The heatmap shows how often two strategies disagree on a record.
387
+ """)
388
+
389
+ with gr.Row():
390
+ conflict_btn = gr.Button("Run Conflict Analysis", variant="primary")
391
+
392
+ conflict_chart = gr.Plot(label="Per-Field Conflict Matrix Heatmap")
393
+ conflict_table = gr.Dataframe(
394
+ headers=["Strategy", "Conflicts vs LWW", "Overlap Records", "Conflict Rate"],
395
+ label="Strategy Comparison",
396
+ )
397
+
398
+ def _run_conflict():
399
+ rows, fig = run_conflict_analysis()
400
+ df_data = [
401
+ [r["Strategy"], r["Conflicts vs LWW"], r["Overlap Records"], r["Conflict Rate"]]
402
+ for r in rows
403
+ ]
404
+ return fig, df_data
405
+
406
+ conflict_btn.click(_run_conflict, outputs=[conflict_chart, conflict_table])
407
+ demo.load(_run_conflict, outputs=[conflict_chart, conflict_table])
408
+
409
+ # ── TAB 3 ──────────────────────────────────────────────────────
410
+ with gr.Tab("Core CRDT Primitives"):
411
+ gr.Markdown("""
412
+ ## Core CRDT Primitives
413
+
414
+ Live demonstration of GCounter, PNCounter, LWWRegister, and ORSet.
415
+ Each primitive is operated on two nodes independently, then merged in both directions.
416
+ Commutativity is verified: merge(A,B) must equal merge(B,A).
417
+
418
+ Note: `.value` is a property (no parentheses required).
419
+ """)
420
+
421
+ with gr.Row():
422
+ prim_btn = gr.Button("Run Primitives Demo", variant="primary")
423
+
424
+ prim_table = gr.Dataframe(
425
+ headers=["Primitive", "Node A Operations", "Node B Operations",
426
+ "merge(A,B) Value", "merge(B,A) Value", "Commutative"],
427
+ label="Primitive Commutativity Proof",
428
+ wrap=True,
429
+ )
430
+
431
+ def _run_prims():
432
+ rows = run_primitives_demo()
433
+ return [
434
+ [r["Primitive"], r["Node A Operations"], r["Node B Operations"],
435
+ r["merge(A,B) Value"], r["merge(B,A) Value"], r["Commutative"]]
436
+ for r in rows
437
+ ]
438
+
439
+ prim_btn.click(_run_prims, outputs=[prim_table])
440
+ demo.load(_run_prims, outputs=[prim_table])
441
+
442
+ gr.Markdown(
443
+ "crdt-merge v0.9.4 Β· Patent Pending UK 2607132.4 Β· "
444
+ "[github.com/mgillr/crdt-merge](https://github.com/mgillr/crdt-merge)"
445
+ )
446
+
447
+ if __name__ == "__main__":
448
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ crdt-merge>=0.9.4
2
+ gradio>=4.44.0
3
+ plotly>=5.18.0
4
+ numpy>=1.24.0
5
+ datasets>=2.14.0
6
+ huggingface_hub>=0.20.0