fm1320 commited on
Commit
5a1fd0a
·
1 Parent(s): d4ff4a4

UI: showcase SIE with diagram, code snippets, CTAs

Browse files
Files changed (3) hide show
  1. web/public/app.js +59 -0
  2. web/public/index.html +54 -7
  3. web/public/style.css +98 -5
web/public/app.js CHANGED
@@ -13,9 +13,13 @@ const els = {
13
  footer: document.getElementById("footer"),
14
  sieUrl: document.getElementById("sie-url"),
15
  timings: document.getElementById("timings"),
 
 
 
16
  };
17
 
18
  let activeSampleId = null;
 
19
  let timings = { recognitionMs: 0, donutMs: 0, glinerMs: 0 };
20
  let donutBuf = { entities: [], data: null };
21
  let glinerBuf = [];
@@ -38,6 +42,57 @@ function escapeHtml(s) {
38
  );
39
  }
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  function populateDropdown(selectEl, options, defaultId) {
42
  selectEl.innerHTML = "";
43
  for (const opt of options) {
@@ -56,6 +111,7 @@ function populateDropdown(selectEl, options, defaultId) {
56
  node.title = opt.description;
57
  selectEl.appendChild(node);
58
  }
 
59
  }
60
 
61
  function renderSamples(samples, onClick) {
@@ -78,6 +134,8 @@ function renderSamples(samples, onClick) {
78
  node.addEventListener("click", () => {
79
  for (const n of els.events.querySelectorAll(".event")) n.classList.remove("active");
80
  node.classList.add("active");
 
 
81
  onClick(node.dataset.id);
82
  });
83
  }
@@ -219,6 +277,7 @@ async function init() {
219
  populateDropdown(els.selectRecognition, modelConfig.recognition, modelConfig.defaults.recognition);
220
  populateDropdown(els.selectStructured, modelConfig.structured, modelConfig.defaults.structured);
221
  populateDropdown(els.selectNer, modelConfig.ner, modelConfig.defaults.ner);
 
222
  } catch (e) {
223
  console.error("failed to load model config", e);
224
  }
 
13
  footer: document.getElementById("footer"),
14
  sieUrl: document.getElementById("sie-url"),
15
  timings: document.getElementById("timings"),
16
+ snippetRecognition: document.getElementById("snippet-recognition"),
17
+ snippetStructured: document.getElementById("snippet-structured"),
18
+ snippetNer: document.getElementById("snippet-ner"),
19
  };
20
 
21
  let activeSampleId = null;
22
+ let activeSample = null;
23
  let timings = { recognitionMs: 0, donutMs: 0, glinerMs: 0 };
24
  let donutBuf = { entities: [], data: null };
25
  let glinerBuf = [];
 
42
  );
43
  }
44
 
45
+ function findModel(list, id) {
46
+ return list ? list.find((m) => m.id === id) : null;
47
+ }
48
+
49
+ function snippetRecognition(modelId) {
50
+ if (!modelConfig) return "";
51
+ const opt = findModel(modelConfig.recognition, modelId);
52
+ const hasOpts = opt && opt.options && Object.keys(opt.options).length > 0;
53
+ const lines = [
54
+ 'client.extract(',
55
+ ` <span class="str">"${escapeHtml(modelId)}"</span>,`,
56
+ ' Item(images=[image_bytes]),',
57
+ ];
58
+ if (hasOpts) {
59
+ const optsJson = JSON.stringify(opt.options).replace(/"/g, '"');
60
+ lines.push(` <span class="arg">options</span>=${escapeHtml(optsJson)},`);
61
+ }
62
+ lines.push(')');
63
+ return `<span class="com"># Recognition: one call against SIE</span>\n` + lines.join("\n");
64
+ }
65
+
66
+ function snippetStructured(modelId) {
67
+ return (
68
+ `<span class="com"># Structured: same client.extract, different model_id</span>\n` +
69
+ `client.extract(\n` +
70
+ ` <span class="str">"${escapeHtml(modelId)}"</span>,\n` +
71
+ ` Item(images=[image_bytes]),\n` +
72
+ `)`
73
+ );
74
+ }
75
+
76
+ function snippetNer(modelId, sample) {
77
+ const labels = sample ? sample.labels : ["merchant", "total", "date"];
78
+ const labelsStr = "[" + labels.map((l) => `<span class="str">"${escapeHtml(l)}"</span>`).join(", ") + "]";
79
+ return (
80
+ `<span class="com"># NER: text input this time, declared labels</span>\n` +
81
+ `client.extract(\n` +
82
+ ` <span class="str">"${escapeHtml(modelId)}"</span>,\n` +
83
+ ` Item(text=recognized_markdown),\n` +
84
+ ` <span class="arg">labels</span>=${labelsStr},\n` +
85
+ `)`
86
+ );
87
+ }
88
+
89
+ function updateSnippets() {
90
+ if (!els.snippetRecognition) return;
91
+ els.snippetRecognition.innerHTML = snippetRecognition(els.selectRecognition.value);
92
+ els.snippetStructured.innerHTML = snippetStructured(els.selectStructured.value);
93
+ els.snippetNer.innerHTML = snippetNer(els.selectNer.value, activeSample);
94
+ }
95
+
96
  function populateDropdown(selectEl, options, defaultId) {
97
  selectEl.innerHTML = "";
98
  for (const opt of options) {
 
111
  node.title = opt.description;
112
  selectEl.appendChild(node);
113
  }
114
+ selectEl.addEventListener("change", updateSnippets);
115
  }
116
 
117
  function renderSamples(samples, onClick) {
 
134
  node.addEventListener("click", () => {
135
  for (const n of els.events.querySelectorAll(".event")) n.classList.remove("active");
136
  node.classList.add("active");
137
+ activeSample = samples.find((s) => s.id === node.dataset.id) || null;
138
+ updateSnippets();
139
  onClick(node.dataset.id);
140
  });
141
  }
 
277
  populateDropdown(els.selectRecognition, modelConfig.recognition, modelConfig.defaults.recognition);
278
  populateDropdown(els.selectStructured, modelConfig.structured, modelConfig.defaults.structured);
279
  populateDropdown(els.selectNer, modelConfig.ner, modelConfig.defaults.ner);
280
+ updateSnippets();
281
  } catch (e) {
282
  console.error("failed to load model config", e);
283
  }
web/public/index.html CHANGED
@@ -15,17 +15,54 @@
15
  </div>
16
  <div class="meta" id="models">SIE: <code>...</code></div>
17
  <div class="meta" id="sie-state">checking SIE...</div>
 
 
 
 
 
 
 
 
18
  </header>
19
 
20
  <section class="hero">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  <p>
22
- OCR is rarely a single-model problem. This demo runs three model
23
- classes through one SIE server: a <strong>VLM-OCR</strong> recognizes
24
- the document into Markdown, a <strong>fine-tuned Donut</strong> emits
25
- a JSON tree directly, and a <strong>zero-shot NER (GLiNER)</strong>
26
- pulls typed fields out of the recognition output. Pick a sample on
27
- the left, swap any of the three models on the right, watch SIE
28
- hot-swap them with one identifier change.
29
  </p>
30
  </section>
31
 
@@ -54,6 +91,10 @@
54
  <h2>Recognition (Markdown)</h2>
55
  <span class="hint" id="recognition-meta"></span>
56
  </header>
 
 
 
 
57
  <div class="markdown" id="recognition">
58
  <p class="hint">Click a sample on the left.</p>
59
  </div>
@@ -64,6 +105,12 @@
64
  <h2>Extraction</h2>
65
  <span class="hint" id="extraction-meta"></span>
66
  </header>
 
 
 
 
 
 
67
  <div class="extraction" id="extraction">
68
  <p class="hint">Typed fields will appear here.</p>
69
  </div>
 
15
  </div>
16
  <div class="meta" id="models">SIE: <code>...</code></div>
17
  <div class="meta" id="sie-state">checking SIE...</div>
18
+ <div class="cta-row">
19
+ <a class="cta" href="https://github.com/superlinked/brave-new-demos/tree/main/document-ocr" target="_blank" rel="noopener">
20
+ <span>↗</span> Source on GitHub
21
+ </a>
22
+ <a class="cta" href="https://github.com/superlinked/sie" target="_blank" rel="noopener">
23
+ <span>★</span> SIE repo
24
+ </a>
25
+ </div>
26
  </header>
27
 
28
  <section class="hero">
29
+ <div class="hero-text">
30
+ <p>
31
+ OCR is rarely a single-model problem. This demo runs three model
32
+ classes through <strong>one SIE server</strong>: a VLM-OCR recognizes
33
+ the document into Markdown, a fine-tuned Donut emits a JSON tree
34
+ directly, and a zero-shot NER (GLiNER) pulls typed fields out of
35
+ the recognition output. Pick a sample on the left, swap any of the
36
+ three models in the dropdowns, watch SIE hot-swap them with
37
+ <em>one identifier change</em>.
38
+ </p>
39
+ </div>
40
+ <div class="hero-diagram">
41
+ <div class="diagram">
42
+ <div class="diagram-input">image</div>
43
+ <div class="diagram-arrow">↓</div>
44
+ <div class="diagram-server">one SIE server · <code>client.extract(model_id, item)</code></div>
45
+ <div class="diagram-arrows">
46
+ <span>↓</span><span>↓</span><span>↓</span>
47
+ </div>
48
+ <div class="diagram-models">
49
+ <div class="diagram-box diagram-recognition">VLM-OCR<br><span>(Florence-2, LightOnOCR, GLM-OCR, ...)</span></div>
50
+ <div class="diagram-box diagram-structured">Donut<br><span>(end-to-end JSON)</span></div>
51
+ <div class="diagram-box diagram-ner">GLiNER<br><span>(zero-shot NER)</span></div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </section>
56
+
57
+ <section class="why-sie">
58
+ <h3>Why SIE</h3>
59
  <p>
60
+ Three different model architectures (a vision-language model, a
61
+ fine-tuned encoder-decoder, a span-based NER), one inference engine,
62
+ one HTTP API, one SDK call. Without SIE, this demo would be three
63
+ separate inference services with three SDKs, three auth flows, three
64
+ rate limits. With SIE, swap a string in <code>client.extract(...)</code>
65
+ and the underlying architecture changes.
 
66
  </p>
67
  </section>
68
 
 
91
  <h2>Recognition (Markdown)</h2>
92
  <span class="hint" id="recognition-meta"></span>
93
  </header>
94
+ <details class="sdk-snippet">
95
+ <summary>See the SIE call</summary>
96
+ <pre><code id="snippet-recognition">// pick a recognition model in the dropdown</code></pre>
97
+ </details>
98
  <div class="markdown" id="recognition">
99
  <p class="hint">Click a sample on the left.</p>
100
  </div>
 
105
  <h2>Extraction</h2>
106
  <span class="hint" id="extraction-meta"></span>
107
  </header>
108
+ <details class="sdk-snippet">
109
+ <summary>See the SIE calls</summary>
110
+ <pre><code id="snippet-structured">// structured (Donut)</code>
111
+
112
+ <code id="snippet-ner">// NER (GLiNER)</code></pre>
113
+ </details>
114
  <div class="extraction" id="extraction">
115
  <p class="hint">Typed fields will appear here.</p>
116
  </div>
web/public/style.css CHANGED
@@ -16,13 +16,14 @@
16
  html, body {
17
  margin: 0; padding: 0; background: var(--bg); color: var(--text);
18
  font: 14px ui-monospace, SFMono-Regular, Menlo, monospace;
19
- height: 100%;
20
  }
21
- body { display: flex; flex-direction: column; }
22
 
23
  header {
24
  display: flex; align-items: center; gap: 16px;
25
  padding: 12px 20px; border-bottom: 1px solid var(--line);
 
26
  }
27
  .title { display: flex; align-items: center; gap: 12px; }
28
  .logo { font-size: 18px; }
@@ -37,17 +38,83 @@ h1 { font-size: 16px; margin: 0; font-weight: 600; }
37
  .badge.red { background: rgba(255, 107, 107, 0.18); color: var(--red); }
38
  .meta { color: var(--muted); font-size: 12px; }
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  .hero {
41
- padding: 14px 20px; background: linear-gradient(180deg, rgba(124,93,255,0.10), transparent);
 
42
  border-bottom: 1px solid var(--line);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
44
- .hero p { margin: 0; color: var(--muted); max-width: 980px; line-height: 1.6; }
45
- .hero strong { color: var(--text); }
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  main {
48
  flex: 1; display: grid;
49
  grid-template-columns: 0.95fr 1.4fr 1.2fr;
50
  gap: 12px; padding: 12px 20px; overflow: hidden;
 
51
  }
52
 
53
  .panel {
@@ -73,6 +140,26 @@ main {
73
  flex: 1; overflow: auto; padding: 12px 14px; margin: 0;
74
  }
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  .meta-row {
77
  padding: 10px 14px; border-bottom: 1px solid var(--line);
78
  font-size: 11px; color: var(--muted);
@@ -146,3 +233,9 @@ footer {
146
  display: flex; justify-content: space-between; gap: 12px;
147
  }
148
  code { background: var(--line); padding: 1px 6px; border-radius: 4px; }
 
 
 
 
 
 
 
16
  html, body {
17
  margin: 0; padding: 0; background: var(--bg); color: var(--text);
18
  font: 14px ui-monospace, SFMono-Regular, Menlo, monospace;
19
+ min-height: 100%;
20
  }
21
+ body { display: flex; flex-direction: column; min-height: 100vh; }
22
 
23
  header {
24
  display: flex; align-items: center; gap: 16px;
25
  padding: 12px 20px; border-bottom: 1px solid var(--line);
26
+ flex-wrap: wrap;
27
  }
28
  .title { display: flex; align-items: center; gap: 12px; }
29
  .logo { font-size: 18px; }
 
38
  .badge.red { background: rgba(255, 107, 107, 0.18); color: var(--red); }
39
  .meta { color: var(--muted); font-size: 12px; }
40
 
41
+ .cta-row {
42
+ margin-left: auto; display: flex; gap: 8px;
43
+ }
44
+ .cta {
45
+ display: inline-flex; align-items: center; gap: 6px;
46
+ padding: 5px 10px; border-radius: 6px;
47
+ background: var(--line); color: var(--accent-2);
48
+ font-size: 11px; text-decoration: none;
49
+ border: 1px solid transparent;
50
+ transition: border-color 0.1s;
51
+ }
52
+ .cta:hover { border-color: var(--accent); }
53
+ .cta span { font-size: 12px; }
54
+
55
  .hero {
56
+ padding: 14px 20px;
57
+ background: linear-gradient(180deg, rgba(124,93,255,0.10), transparent);
58
  border-bottom: 1px solid var(--line);
59
+ display: grid; grid-template-columns: 1.4fr 1fr; gap: 24px; align-items: center;
60
+ }
61
+ .hero-text p { margin: 0; color: var(--muted); line-height: 1.6; }
62
+ .hero-text strong { color: var(--text); }
63
+ .hero-text em { color: var(--accent-2); font-style: normal; font-weight: 600; }
64
+
65
+ .diagram {
66
+ display: flex; flex-direction: column; align-items: center; gap: 2px;
67
+ font-size: 11px;
68
+ }
69
+ .diagram-input {
70
+ padding: 4px 14px; border: 1px dashed var(--muted); border-radius: 6px;
71
+ color: var(--text);
72
+ }
73
+ .diagram-arrow { color: var(--muted); font-size: 14px; line-height: 1; }
74
+ .diagram-server {
75
+ padding: 6px 14px; border: 1px solid var(--accent); border-radius: 6px;
76
+ color: var(--text); background: rgba(124,93,255,0.10);
77
+ font-size: 11px;
78
+ }
79
+ .diagram-server code {
80
+ background: transparent; color: var(--accent-2);
81
+ padding: 0;
82
+ }
83
+ .diagram-arrows {
84
+ display: flex; gap: 80px; color: var(--muted); font-size: 14px;
85
+ line-height: 1;
86
+ }
87
+ .diagram-models {
88
+ display: flex; gap: 10px;
89
+ }
90
+ .diagram-box {
91
+ padding: 6px 10px; border: 1px solid var(--line); border-radius: 6px;
92
+ background: var(--panel); color: var(--text); text-align: center;
93
+ min-width: 110px; font-weight: 600;
94
+ }
95
+ .diagram-box span {
96
+ display: block; color: var(--muted); font-weight: normal;
97
+ font-size: 9px; margin-top: 2px;
98
  }
99
+ .diagram-recognition { border-color: rgba(98,182,255,0.4); }
100
+ .diagram-structured { border-color: rgba(200,156,255,0.4); }
101
+ .diagram-ner { border-color: rgba(95,210,139,0.4); }
102
+
103
+ .why-sie {
104
+ padding: 12px 20px; border-bottom: 1px solid var(--line);
105
+ background: rgba(124,93,255,0.04);
106
+ }
107
+ .why-sie h3 {
108
+ margin: 0 0 6px 0; font-size: 11px; letter-spacing: 0.6px;
109
+ text-transform: uppercase; color: var(--accent); font-weight: 600;
110
+ }
111
+ .why-sie p { margin: 0; color: var(--muted); line-height: 1.6; max-width: 1100px; }
112
 
113
  main {
114
  flex: 1; display: grid;
115
  grid-template-columns: 0.95fr 1.4fr 1.2fr;
116
  gap: 12px; padding: 12px 20px; overflow: hidden;
117
+ min-height: 480px;
118
  }
119
 
120
  .panel {
 
140
  flex: 1; overflow: auto; padding: 12px 14px; margin: 0;
141
  }
142
 
143
+ .sdk-snippet {
144
+ border-bottom: 1px solid var(--line);
145
+ background: rgba(0,0,0,0.2);
146
+ font-size: 11px;
147
+ }
148
+ .sdk-snippet summary {
149
+ padding: 6px 14px; cursor: pointer;
150
+ color: var(--accent-2); user-select: none;
151
+ font-weight: 500;
152
+ }
153
+ .sdk-snippet summary::marker { color: var(--accent-2); }
154
+ .sdk-snippet pre {
155
+ margin: 0; padding: 8px 14px 10px;
156
+ white-space: pre-wrap; word-break: break-word;
157
+ color: var(--text); font-size: 11px; line-height: 1.45;
158
+ }
159
+ .sdk-snippet code .arg { color: var(--magenta); }
160
+ .sdk-snippet code .str { color: var(--green); }
161
+ .sdk-snippet code .com { color: var(--muted); }
162
+
163
  .meta-row {
164
  padding: 10px 14px; border-bottom: 1px solid var(--line);
165
  font-size: 11px; color: var(--muted);
 
233
  display: flex; justify-content: space-between; gap: 12px;
234
  }
235
  code { background: var(--line); padding: 1px 6px; border-radius: 4px; }
236
+
237
+ @media (max-width: 900px) {
238
+ .hero { grid-template-columns: 1fr; }
239
+ main { grid-template-columns: 1fr; }
240
+ .cta-row { margin-left: 0; }
241
+ }