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.
- entari_plugin_hyw/__init__.py +126 -7
- entari_plugin_hyw/assets/libs/tailwind.css +1 -1
- entari_plugin_hyw/assets/package-lock.json +953 -0
- entari_plugin_hyw/assets/package.json +16 -0
- entari_plugin_hyw/assets/tailwind.config.js +1 -1
- entari_plugin_hyw/assets/tailwind.input.css +8 -8
- entari_plugin_hyw/assets/template.html +39 -43
- entari_plugin_hyw/assets/template.html.bak +157 -0
- entari_plugin_hyw/assets/template.j2 +259 -0
- entari_plugin_hyw/core/config.py +2 -4
- entari_plugin_hyw/core/pipeline.py +192 -23
- entari_plugin_hyw/core/render.py +235 -571
- entari_plugin_hyw/core/render.py.bak +926 -0
- entari_plugin_hyw/utils/prompts.py +6 -6
- {entari_plugin_hyw-3.2.104.dist-info → entari_plugin_hyw-3.2.106.dist-info}/METADATA +2 -1
- {entari_plugin_hyw-3.2.104.dist-info → entari_plugin_hyw-3.2.106.dist-info}/RECORD +18 -13
- {entari_plugin_hyw-3.2.104.dist-info → entari_plugin_hyw-3.2.106.dist-info}/WHEEL +0 -0
- {entari_plugin_hyw-3.2.104.dist-info → entari_plugin_hyw-3.2.106.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
}
|
|
@@ -51,7 +51,7 @@ pre::-webkit-scrollbar-thumb {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
.markdown-body a {
|
|
54
|
-
@apply text-
|
|
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-
|
|
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-
|
|
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:
|
|
162
|
+
vertical-align: baseline;
|
|
163
163
|
font-size: 0.75em;
|
|
164
164
|
font-weight: bold;
|
|
165
|
-
color: #
|
|
166
|
-
background-color: #
|
|
167
|
-
border: 1px solid #
|
|
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: #
|
|
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
|
-
<!--
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
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-
|
|
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(
|
|
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:
|
|
81
|
-
{ left:
|
|
82
|
-
{ left:
|
|
83
|
-
{ left:
|
|
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
|
-
//
|
|
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 ===
|
|
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(
|
|
126
|
-
const title = ref.title.replace(/"/g,
|
|
122
|
+
const span = document.createElement("span");
|
|
123
|
+
const title = ref.title.replace(/"/g, """);
|
|
127
124
|
const url = ref.url;
|
|
128
|
-
let domain =
|
|
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, """);
|
|
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, """);
|
|
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>
|