entari-plugin-hyw 3.2.104__py3-none-any.whl → 3.2.106__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of entari-plugin-hyw might be problematic. Click here for more details.

@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "assets",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "tailwind.config.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "type": "commonjs",
13
+ "dependencies": {
14
+ "tailwindcss": "^3.4.17"
15
+ }
16
+ }
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
- content: ["./template.html", "../core/render.py"],
2
+ content: ["./template.j2", "../core/render.py"],
3
3
  theme: {
4
4
  extend: {
5
5
  fontFamily: {
@@ -51,7 +51,7 @@ pre::-webkit-scrollbar-thumb {
51
51
  }
52
52
 
53
53
  .markdown-body a {
54
- @apply text-pink-600 underline decoration-pink-300 underline-offset-2 hover:text-pink-700 hover:decoration-pink-700 transition-colors;
54
+ @apply text-blue-600 underline decoration-blue-300 underline-offset-2 hover:text-blue-700 hover:decoration-blue-700 transition-colors;
55
55
  }
56
56
 
57
57
  .markdown-body blockquote {
@@ -109,11 +109,11 @@ pre::-webkit-scrollbar-thumb {
109
109
  }
110
110
 
111
111
  .markdown-body code {
112
- @apply bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono text-pink-600;
112
+ @apply bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono text-gray-700;
113
113
  }
114
114
 
115
115
  .markdown-body pre {
116
- @apply bg-white p-4 rounded-lg overflow-x-auto mb-4 text-sm leading-normal border border-gray-100 shadow-sm;
116
+ @apply bg-gray-50 p-4 rounded-lg overflow-x-auto mb-4 text-sm leading-normal border border-gray-100 shadow-sm;
117
117
  }
118
118
 
119
119
  .markdown-body pre code {
@@ -159,12 +159,12 @@ pre::-webkit-scrollbar-thumb {
159
159
  display: inline-flex;
160
160
  align-items: center;
161
161
  justify-content: center;
162
- vertical-align: super;
162
+ vertical-align: baseline;
163
163
  font-size: 0.75em;
164
164
  font-weight: bold;
165
- color: #db2777;
166
- background-color: #fdf2f8;
167
- border: 1px solid #fce7f3;
165
+ color: #4b5563;
166
+ background-color: #f3f4f6;
167
+ border: 1px solid #e5e7eb;
168
168
  border-radius: 9999px;
169
169
  width: 1.4em;
170
170
  height: 1.4em;
@@ -178,7 +178,7 @@ pre::-webkit-scrollbar-thumb {
178
178
  }
179
179
 
180
180
  .citation-ref:hover {
181
- background-color: #db2777;
181
+ background-color: #4b5563;
182
182
  color: white;
183
183
  text-decoration: none !important;
184
184
  }
@@ -5,7 +5,9 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>Entari Render</title>
8
- <!-- Tailwind CSS (Local) -->
8
+ <!-- @formatter:off -->
9
+ <!-- prettier-ignore -->
10
+ <!-- IMPORTANT: Asset placeholders MUST be {{ name }} on single line! -->
9
11
  <style>
10
12
  {
11
13
  {
@@ -13,8 +15,6 @@
13
15
  }
14
16
  }
15
17
  </style>
16
-
17
- <!-- Highlight.js (Local) -->
18
18
  <style>
19
19
  {
20
20
  {
@@ -22,11 +22,7 @@
22
22
  }
23
23
  }
24
24
  </style>
25
- <script>
26
- { { highlight_js } }
27
- </script>
28
-
29
- <!-- KaTeX (Local) -->
25
+ <script>{ { highlight_js } }</script>
30
26
  <style>
31
27
  {
32
28
  {
@@ -34,35 +30,43 @@
34
30
  }
35
31
  }
36
32
  </style>
37
- <script>
38
- { { katex_js } }
39
- </script>
40
- <script>
41
- { { katex_auto_render_js } }
42
- </script>
43
-
44
-
33
+ <script>{ { katex_js } }</script>
34
+ <script>{ { katex_auto_render_js } }</script>
35
+ <!-- @formatter:on -->
36
+ <style>
37
+ #markdown-content h1,
38
+ #markdown-content h2,
39
+ #markdown-content h3,
40
+ #markdown-content h4,
41
+ #markdown-content h5,
42
+ #markdown-content h6 {
43
+ color: #db2777;
44
+ /* text-pink-600 */
45
+ }
46
+ </style>
45
47
  </head>
46
48
 
47
49
  <body class="bg-[#f2f2f2] p-0 box-border m-0 font-sans text-gray-800">
48
- <div id="main-container" class="w-full max-w-[450px] flex flex-col gap-0 mx-auto bg-[#f2f2f2] p-0 font-sans h-fit">
49
- <!-- Response Card -->
50
+ <div id="main-container" class="w-full max-w-[450px] flex flex-col gap-4 mx-auto bg-[#f2f2f2] p-0 font-sans h-fit">
51
+ <!-- Response Card (Content First) -->
50
52
  <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
51
- {{ response_header }}
52
53
  <div id="markdown-content" class="markdown-body text-[15px] leading-relaxed text-gray-800">
53
54
  {{ content_html }}
54
55
  </div>
55
56
  </div>
56
57
 
57
- <!-- References Card (Optional) -->
58
- {{ references }}
59
58
 
60
- <!-- MCP Steps Card (Optional) -->
61
- {{ mcp_steps }}
62
59
 
63
60
  <!-- Speculation Card (Optional) -->
64
61
  {{ suggestions }}
65
62
 
63
+ <!-- Model Header (Moved to Bottom) -->
64
+ <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
65
+ {{ response_header }}
66
+ <!-- Pipeline Stages -->
67
+ {{ stages }}
68
+ </div>
69
+
66
70
  <!-- Stats Footer -->
67
71
  {{ stats }}
68
72
  </div>
@@ -70,26 +74,20 @@
70
74
  <script>window.REFERENCES = {{ references_json }};</script>
71
75
 
72
76
  <script>
73
- document.addEventListener('DOMContentLoaded', () => {
74
-
75
-
76
- // 3. Render Math (KaTeX)
77
- const contentDiv = document.getElementById('markdown-content');
77
+ document.addEventListener("DOMContentLoaded", () => {
78
+ // Render Math (KaTeX)
79
+ const contentDiv = document.getElementById("markdown-content");
78
80
  renderMathInElement(contentDiv, {
79
81
  delimiters: [
80
- { left: '$$', right: '$$', display: true },
81
- { left: '$', right: '$', display: false },
82
- { left: '\(', right: '\)', display: false },
83
- { left: '\[', right: '\]', display: true }
82
+ { left: "$$", right: "$$", display: true },
83
+ { left: "$", right: "$", display: false },
84
+ { left: "\\(", right: "\\)", display: false },
85
+ { left: "\\[", right: "\\]", display: true }
84
86
  ],
85
87
  throwOnError: false
86
88
  });
87
89
 
88
- // 4. Process Citations (Manual replacement since we don't have marked extension anymore)
89
- // We can do a simple regex replace on the HTML content for [1], [2] etc.
90
- // Be careful not to replace inside attributes.
91
- // A safer way is to walk text nodes.
92
-
90
+ // Process Citations
93
91
  function processCitations(rootNode) {
94
92
  const walker = document.createTreeWalker(
95
93
  rootNode,
@@ -101,7 +99,7 @@
101
99
  const nodesToReplace = [];
102
100
  let node;
103
101
  while (node = walker.nextNode()) {
104
- if (node.parentElement.tagName === 'SCRIPT' || node.parentElement.tagName === 'STYLE' || node.parentElement.tagName === 'A') continue;
102
+ if (node.parentElement.tagName === "SCRIPT" || node.parentElement.tagName === "STYLE" || node.parentElement.tagName === "A") continue;
105
103
  if (/\[\d+\]/.test(node.nodeValue)) {
106
104
  nodesToReplace.push(node);
107
105
  }
@@ -115,17 +113,16 @@
115
113
  let match;
116
114
 
117
115
  while ((match = regex.exec(text)) !== null) {
118
- // Text before match
119
116
  fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
120
117
 
121
118
  const id = match[1];
122
119
  const ref = window.REFERENCES ? window.REFERENCES.find(r => r.id == id) : null;
123
120
 
124
121
  if (ref) {
125
- const span = document.createElement('span');
126
- const title = ref.title.replace(/"/g, '&quot;');
122
+ const span = document.createElement("span");
123
+ const title = ref.title.replace(/"/g, "&quot;");
127
124
  const url = ref.url;
128
- let domain = 'unknown';
125
+ let domain = "unknown";
129
126
  try { domain = new URL(url).hostname; } catch (e) { }
130
127
  const favicon = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
131
128
 
@@ -147,7 +144,6 @@
147
144
  lastIndex = regex.lastIndex;
148
145
  }
149
146
 
150
- // Remaining text
151
147
  fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
152
148
  textNode.parentNode.replaceChild(fragment, textNode);
153
149
  });
@@ -0,0 +1,157 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Entari Render</title>
8
+ <!-- @formatter:off -->
9
+ <!-- prettier-ignore -->
10
+ <!-- IMPORTANT: Asset placeholders MUST be {{ name }} on single line! -->
11
+ <style>
12
+ {
13
+ {
14
+ tailwind_css
15
+ }
16
+ }
17
+ </style>
18
+ <style>
19
+ {
20
+ {
21
+ highlight_css
22
+ }
23
+ }
24
+ </style>
25
+ <script>{ { highlight_js } }</script>
26
+ <style>
27
+ {
28
+ {
29
+ katex_css
30
+ }
31
+ }
32
+ </style>
33
+ <script>{ { katex_js } }</script>
34
+ <script>{ { katex_auto_render_js } }</script>
35
+ <!-- @formatter:on -->
36
+ <style>
37
+ #markdown-content h1,
38
+ #markdown-content h2,
39
+ #markdown-content h3,
40
+ #markdown-content h4,
41
+ #markdown-content h5,
42
+ #markdown-content h6 {
43
+ color: #db2777;
44
+ /* text-pink-600 */
45
+ }
46
+ </style>
47
+ </head>
48
+
49
+ <body class="bg-[#f2f2f2] p-0 box-border m-0 font-sans text-gray-800">
50
+ <div id="main-container" class="w-full max-w-[450px] flex flex-col gap-4 mx-auto bg-[#f2f2f2] p-0 font-sans h-fit">
51
+ <!-- Response Card (Content First) -->
52
+ <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
53
+ <div id="markdown-content" class="markdown-body text-[15px] leading-relaxed text-gray-800">
54
+ {{ content_html }}
55
+ </div>
56
+ </div>
57
+
58
+
59
+
60
+ <!-- Speculation Card (Optional) -->
61
+ {{ suggestions }}
62
+
63
+ <!-- Model Header (Moved to Bottom) -->
64
+ <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
65
+ {{ response_header }}
66
+ <!-- Pipeline Stages -->
67
+ {{ stages }}
68
+ </div>
69
+
70
+ <!-- Stats Footer -->
71
+ {{ stats }}
72
+ </div>
73
+
74
+ <script>window.REFERENCES = {{ references_json }};</script>
75
+
76
+ <script>
77
+ document.addEventListener("DOMContentLoaded", () => {
78
+ // Render Math (KaTeX)
79
+ const contentDiv = document.getElementById("markdown-content");
80
+ renderMathInElement(contentDiv, {
81
+ delimiters: [
82
+ { left: "$$", right: "$$", display: true },
83
+ { left: "$", right: "$", display: false },
84
+ { left: "\\(", right: "\\)", display: false },
85
+ { left: "\\[", right: "\\]", display: true }
86
+ ],
87
+ throwOnError: false
88
+ });
89
+
90
+ // Process Citations
91
+ function processCitations(rootNode) {
92
+ const walker = document.createTreeWalker(
93
+ rootNode,
94
+ NodeFilter.SHOW_TEXT,
95
+ null,
96
+ false
97
+ );
98
+
99
+ const nodesToReplace = [];
100
+ let node;
101
+ while (node = walker.nextNode()) {
102
+ if (node.parentElement.tagName === "SCRIPT" || node.parentElement.tagName === "STYLE" || node.parentElement.tagName === "A") continue;
103
+ if (/\[\d+\]/.test(node.nodeValue)) {
104
+ nodesToReplace.push(node);
105
+ }
106
+ }
107
+
108
+ nodesToReplace.forEach(textNode => {
109
+ const fragment = document.createDocumentFragment();
110
+ let lastIndex = 0;
111
+ const text = textNode.nodeValue;
112
+ const regex = /\[(\d+)\]/g;
113
+ let match;
114
+
115
+ while ((match = regex.exec(text)) !== null) {
116
+ fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
117
+
118
+ const id = match[1];
119
+ const ref = window.REFERENCES ? window.REFERENCES.find(r => r.id == id) : null;
120
+
121
+ if (ref) {
122
+ const span = document.createElement("span");
123
+ const title = ref.title.replace(/"/g, "&quot;");
124
+ const url = ref.url;
125
+ let domain = "unknown";
126
+ try { domain = new URL(url).hostname; } catch (e) { }
127
+ const favicon = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
128
+
129
+ span.innerHTML = `<a href="${url}" target="_blank" class="citation-ref" data-id="${id}">
130
+ ${id}
131
+ <span class="citation-tooltip">
132
+ <span class="citation-tooltip-title">${title}</span>
133
+ <span class="citation-tooltip-url">
134
+ <img src="${favicon}" style="width:12px;height:12px;vertical-align:middle;margin-right:4px;">
135
+ ${domain}
136
+ </span>
137
+ </span>
138
+ </a>`;
139
+ fragment.appendChild(span.firstElementChild);
140
+ } else {
141
+ fragment.appendChild(document.createTextNode(match[0]));
142
+ }
143
+
144
+ lastIndex = regex.lastIndex;
145
+ }
146
+
147
+ fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
148
+ textNode.parentNode.replaceChild(fragment, textNode);
149
+ });
150
+ }
151
+
152
+ processCitations(contentDiv);
153
+ });
154
+ </script>
155
+ </body>
156
+
157
+ </html>
@@ -0,0 +1,259 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link href="https://cdn.jsdelivr.net/npm/remixicon@4.1.0/fonts/remixicon.css" rel="stylesheet"/>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <title>Entari Render</title>
9
+ <!-- @formatter:off -->
10
+ <!-- prettier-ignore -->
11
+ <style>{{ tailwind_css | safe }}</style>
12
+ <style>{{ highlight_css | safe }}</style>
13
+ <script>{{ highlight_js | safe }}</script>
14
+ <style>{{ katex_css | safe }}</style>
15
+ <script>{{ katex_js | safe }}</script>
16
+ <script>{{ katex_auto_render_js | safe }}</script>
17
+ <!-- @formatter:on -->
18
+
19
+ </head>
20
+
21
+ <body class="bg-[#f2f2f2] p-0 box-border m-0 font-sans text-gray-800">
22
+ <div id="main-container" class="w-full max-w-[450px] flex flex-col gap-4 mx-auto bg-[#f2f2f2] p-0 font-sans h-fit">
23
+
24
+ {# --- MACROS --- #}
25
+
26
+ {% macro icon_container(icon_html, box_class="bg-gray-50 rounded-lg border border-gray-100", size_class="w-10 h-10") %}
27
+ <div class="flex items-center justify-center {{ size_class }} {{ box_class }} shrink-0">
28
+ {{ icon_html | safe }}
29
+ </div>
30
+ {% endmacro %}
31
+
32
+ {% macro card_header(title, icon_html=None, subtitle_html=None, is_plain=False, icon_box_class=None) %}
33
+ {% set container_class = "flex items-center gap-3" if is_plain else "flex items-center gap-3 pb-3 mb-3 border-b border-gray-100" %}
34
+ {% set default_box_class = icon_box_class if icon_box_class else "bg-gray-50 rounded-lg border border-gray-100" %}
35
+ <div class="{{ container_class }}">
36
+ {{ icon_container(icon_html, box_class=default_box_class) }}
37
+ <div class="flex flex-col min-w-0">
38
+ <div class="text-sm font-bold text-gray-900 uppercase tracking-wide whitespace-nowrap overflow-hidden text-ellipsis">{{ title }}</div>
39
+ {% if subtitle_html and not is_plain %}
40
+ {{ subtitle_html | safe }}
41
+ {% endif %}
42
+ </div>
43
+ </div>
44
+ {% endmacro %}
45
+
46
+ {% macro list_card(icon_html, title_html, subtitle_html=None, link_url=None, right_content_html=None, is_compact=False, icon_box_class="bg-gray-50 rounded-md shrink-0 ring-1 ring-inset ring-black/5") %}
47
+ {% set tag = "a" if link_url else "div" %}
48
+ {% set href_attr = 'href="' ~ link_url ~ '" target="_blank"' if link_url else "" %}
49
+ {% set hover_class = "transition-colors hover:bg-gray-50" if link_url else "" %}
50
+
51
+ {% set padding_class = "p-2.5" if is_compact else "px-4 py-3.5" %}
52
+ {% set align_class = "items-center" if is_compact else "items-start" %}
53
+ {% set icon_size = "w-6 h-6" if is_compact else "w-8 h-8" %}
54
+
55
+ <{{ tag }} {{ href_attr | safe }} class="flex {{ align_class }} gap-3 {{ padding_class }} rounded-lg border border-gray-100 bg-white shadow-sm no-underline text-inherit {{ hover_class }}">
56
+ <div class="flex items-center justify-center {{ icon_size }} {{ icon_box_class }}">
57
+ {{ icon_html | safe }}
58
+ </div>
59
+ <div class="flex flex-col flex-1 min-w-0 gap-0.5">
60
+ <div class="flex items-center gap-2 leading-tight min-w-0">
61
+ {{ title_html | safe }}
62
+ </div>
63
+ {% if subtitle_html %}
64
+ <div>{{ subtitle_html | safe }}</div>
65
+ {% endif %}
66
+ </div>
67
+ {% if right_content_html %}
68
+ <div class="shrink-0 ml-2">{{ right_content_html | safe }}</div>
69
+ {% endif %}
70
+ </{{ tag }}>
71
+ {% endmacro %}
72
+
73
+
74
+ {# --- MAIN CONTENT --- #}
75
+
76
+ <!-- Response Card (Content First) -->
77
+ <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
78
+ <div id="markdown-content" class="markdown-body text-[15px] leading-relaxed text-gray-800">
79
+ {{ content_html | safe }}
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Speculation Card (Optional) -->
84
+ {% if suggestions %}
85
+ <div class="flex flex-col gap-2 bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
86
+ {% set sug_icon %}
87
+ <i class="ri-magic-line text-lg text-pink-600"></i>
88
+ {% endset %}
89
+ {{ card_header("SUGGESTIONS", icon_html=sug_icon, is_plain=True) }}
90
+
91
+ <div class="grid grid-cols-2 gap-2.5">
92
+ {% for sug in suggestions %}
93
+ <div class="flex items-baseline gap-2 text-sm text-gray-600 px-3.5 py-2.5 bg-white/80 backdrop-blur-sm rounded-full shadow-sm hover:shadow transition-shadow cursor-default">
94
+ <span class="text-gray-500 font-mono font-bold text-[13px] whitespace-nowrap">{{ loop.index }}</span>
95
+ <span class="flex-1 text-[13px] text-gray-600 font-medium whitespace-nowrap overflow-hidden text-ellipsis">{{ sug }}</span>
96
+ </div>
97
+ {% endfor %}
98
+ </div>
99
+ </div>
100
+ {% endif %}
101
+
102
+ <!-- Pipeline & Children (Nested) -->
103
+ <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden flex flex-col gap-4">
104
+
105
+ {% if stages %}
106
+ {% for stage in stages %}
107
+ <div>
108
+ {# Stage Card #}
109
+ {% set color_class = "bg-white text-gray-700 border-gray-200 shadow-sm" %}
110
+
111
+ {% set icon_box_class = color_class + " rounded-md border shrink-0" %}
112
+
113
+ {% set title_html %}
114
+ <span class="text-[11px] font-bold uppercase text-gray-700 shrink-0">{{ stage.name }}</span>
115
+ <span class="text-[11px] font-medium text-gray-700 truncate min-w-0" title="{{ stage.model }}">{{ stage.model_short }}</span>
116
+ <span class="ml-auto text-[10px] text-gray-400 shrink-0 truncate max-w-[80px]">{{ stage.provider }}</span>
117
+ {% endset %}
118
+
119
+ {% set stats_html %}
120
+ <div class="flex items-center gap-3 text-[11px] text-gray-500 font-mono mt-0.5">
121
+ <span>{{ stage.time_str }}</span><span>{{ stage.cost_str }}</span>
122
+ </div>
123
+ {% endset %}
124
+
125
+ {{ list_card(stage.icon_html, title_html, subtitle_html=stats_html, is_compact=True, icon_box_class=icon_box_class) }}
126
+
127
+ {# Nested Children (Indent & Connect) #}
128
+ {% if stage.references or stage.mcp_steps %}
129
+ <div class="ml-4 pl-4 border-l-2 border-gray-200 mt-2 flex flex-col gap-2">
130
+
131
+ {# References #}
132
+ {% if stage.references %}
133
+ <div class="text-[11px] uppercase font-bold text-blue-600 tracking-wider mb-1 mt-1">References</div>
134
+ {% for ref in stage.references %}
135
+ {% set favicon_url = "https://www.google.com/s2/favicons?domain=" + ref.domain + "&sz=32" %}
136
+
137
+ {% set ref_icon %}
138
+ <img src="{{ favicon_url }}" class="w-3.5 h-3.5 rounded-sm opacity-80 decoration-0">
139
+ {% endset %}
140
+
141
+ {% set ref_icon_box = "bg-white rounded border border-gray-100 w-6 h-6 shrink-0" %}
142
+
143
+ {% set title_html = '<div class="text-[11px] font-medium text-gray-900 truncate underline decoration-gray-300 decoration-1 underline-offset-2 hover:text-black hover:decoration-gray-500 transition-colors">' + ref.title + '</div>' %}
144
+ {% set subtitle_html = '<div class="text-[10px] text-gray-700 truncate">' + ref.domain + '</div>' %}
145
+ {% set right_html = '<div class="flex items-center justify-center min-w-[14px] h-3.5 px-0.5 text-[9px] font-bold text-blue-600 bg-blue-50 border border-blue-200 rounded">' + (loop.index|string) + '</div>' %}
146
+
147
+ {{ list_card(ref_icon, title_html, subtitle_html=subtitle_html, link_url=ref.url, right_content_html=right_html, is_compact=True, icon_box_class=ref_icon_box) }}
148
+ {% endfor %}
149
+ {% endif %}
150
+
151
+ {# MCP Steps #}
152
+ {% if stage.mcp_steps %}
153
+ <div class="text-[11px] uppercase font-bold text-orange-600 tracking-wider mb-1 mt-1">MCP Flow</div>
154
+ {% for step in stage.mcp_steps %}
155
+ {% set icon_box_class = "rounded-md border border-gray-100 bg-white text-gray-500 shrink-0" %}
156
+
157
+ {% set title_html = '<div class="text-[11px] font-medium text-gray-900 font-mono">' + step.name + '</div>' %}
158
+ {% set subtitle_html = '<div class="text-[10px] text-gray-700 leading-tight">' + step.description + '</div>' if step.description else None %}
159
+ {% set right_html = '<div class="flex items-center justify-center min-w-[14px] h-3.5 px-0.5 text-[9px] font-bold text-orange-600 bg-orange-50 border border-orange-200 rounded">' + ('abcdefghijklmnopqrstuvwxyz'[loop.index0]) + '</div>' %}
160
+
161
+ {{ list_card(step.icon_svg, title_html, subtitle_html=subtitle_html, right_content_html=right_html, is_compact=True, icon_box_class=icon_box_class) }}
162
+ {% endfor %}
163
+ {% endif %}
164
+
165
+ </div>
166
+ {% endif %}
167
+ </div>
168
+ {% endfor %}
169
+ {% endif %}
170
+ </div>
171
+
172
+
173
+ </div>
174
+
175
+ <script>window.REFERENCES = {{ references_json | safe }};</script>
176
+
177
+ <script>
178
+ document.addEventListener("DOMContentLoaded", () => {
179
+ // Render Math (KaTeX)
180
+ const contentDiv = document.getElementById("markdown-content");
181
+ if(typeof renderMathInElement !== 'undefined') {
182
+ renderMathInElement(contentDiv, {
183
+ delimiters: [
184
+ { left: "$$", right: "$$", display: true },
185
+ { left: "$", right: "$", display: false },
186
+ { left: "\\(", right: "\\)", display: false },
187
+ { left: "\\[", right: "\\]", display: true }
188
+ ],
189
+ throwOnError: false
190
+ });
191
+ }
192
+
193
+ // Process Citations
194
+ function processCitations(rootNode) {
195
+ const walker = document.createTreeWalker(
196
+ rootNode,
197
+ NodeFilter.SHOW_TEXT,
198
+ null,
199
+ false
200
+ );
201
+
202
+ const nodesToReplace = [];
203
+ let node;
204
+ while (node = walker.nextNode()) {
205
+ if (node.parentElement.tagName === "SCRIPT" || node.parentElement.tagName === "STYLE" || node.parentElement.tagName === "A") continue;
206
+ if (/\[\d+\]/.test(node.nodeValue)) {
207
+ nodesToReplace.push(node);
208
+ }
209
+ }
210
+
211
+ nodesToReplace.forEach(textNode => {
212
+ const fragment = document.createDocumentFragment();
213
+ let lastIndex = 0;
214
+ const text = textNode.nodeValue;
215
+ const regex = /\[(\d+)\]/g;
216
+ let match;
217
+
218
+ while ((match = regex.exec(text)) !== null) {
219
+ fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
220
+
221
+ const id = match[1];
222
+ const ref = window.REFERENCES ? window.REFERENCES.find(r => r.id == id) : null;
223
+
224
+ if (ref) {
225
+ const span = document.createElement("span");
226
+ const title = ref.title.replace(/"/g, "&quot;");
227
+ const url = ref.url;
228
+ let domain = "unknown";
229
+ try { domain = new URL(url).hostname; } catch (e) { }
230
+ const favicon = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
231
+
232
+ span.innerHTML = `<a href="${url}" target="_blank" class="citation-ref" data-id="${id}">
233
+ ${id}
234
+ <span class="citation-tooltip">
235
+ <span class="citation-tooltip-title">${title}</span>
236
+ <span class="citation-tooltip-url">
237
+ <img src="${favicon}" style="width:12px;height:12px;vertical-align:middle;margin-right:4px;">
238
+ ${domain}
239
+ </span>
240
+ </span>
241
+ </a>`;
242
+ fragment.appendChild(span.firstElementChild);
243
+ } else {
244
+ fragment.appendChild(document.createTextNode(match[0]));
245
+ }
246
+
247
+ lastIndex = regex.lastIndex;
248
+ }
249
+
250
+ fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
251
+ textNode.parentNode.replaceChild(fragment, textNode);
252
+ });
253
+ }
254
+
255
+ processCitations(contentDiv);
256
+ });
257
+ </script>
258
+ </body>
259
+ </html>