entari-plugin-hyw 3.4.1__py3-none-any.whl → 3.5.0rc1__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.

Files changed (91) hide show
  1. entari_plugin_hyw/__init__.py +14 -89
  2. entari_plugin_hyw/assets/card-dist/index.html +135 -0
  3. entari_plugin_hyw/assets/card-dist/logos/anthropic.svg +1 -0
  4. entari_plugin_hyw/assets/card-dist/logos/cerebras.svg +9 -0
  5. entari_plugin_hyw/assets/card-dist/logos/deepseek.png +0 -0
  6. entari_plugin_hyw/assets/card-dist/logos/gemini.svg +1 -0
  7. entari_plugin_hyw/assets/card-dist/logos/google.svg +1 -0
  8. entari_plugin_hyw/assets/card-dist/logos/grok.png +0 -0
  9. entari_plugin_hyw/assets/card-dist/logos/huggingface.png +0 -0
  10. entari_plugin_hyw/assets/card-dist/logos/microsoft.svg +15 -0
  11. entari_plugin_hyw/assets/card-dist/logos/minimax.png +0 -0
  12. entari_plugin_hyw/assets/card-dist/logos/mistral.png +0 -0
  13. entari_plugin_hyw/assets/card-dist/logos/nvida.png +0 -0
  14. entari_plugin_hyw/assets/card-dist/logos/openai.svg +1 -0
  15. entari_plugin_hyw/assets/card-dist/logos/openrouter.png +0 -0
  16. entari_plugin_hyw/assets/card-dist/logos/perplexity.svg +24 -0
  17. entari_plugin_hyw/assets/card-dist/logos/qwen.png +0 -0
  18. entari_plugin_hyw/assets/card-dist/logos/xai.png +0 -0
  19. entari_plugin_hyw/assets/card-dist/logos/xiaomi.png +0 -0
  20. entari_plugin_hyw/assets/card-dist/logos/zai.png +0 -0
  21. entari_plugin_hyw/assets/card-dist/vite.svg +1 -0
  22. entari_plugin_hyw/assets/icon/cerebras.svg +9 -0
  23. entari_plugin_hyw/card-ui/.gitignore +24 -0
  24. entari_plugin_hyw/card-ui/README.md +5 -0
  25. entari_plugin_hyw/card-ui/index.html +16 -0
  26. entari_plugin_hyw/card-ui/package-lock.json +2342 -0
  27. entari_plugin_hyw/card-ui/package.json +31 -0
  28. entari_plugin_hyw/card-ui/public/logos/anthropic.svg +1 -0
  29. entari_plugin_hyw/card-ui/public/logos/cerebras.svg +9 -0
  30. entari_plugin_hyw/card-ui/public/logos/deepseek.png +0 -0
  31. entari_plugin_hyw/card-ui/public/logos/gemini.svg +1 -0
  32. entari_plugin_hyw/card-ui/public/logos/google.svg +1 -0
  33. entari_plugin_hyw/card-ui/public/logos/grok.png +0 -0
  34. entari_plugin_hyw/card-ui/public/logos/huggingface.png +0 -0
  35. entari_plugin_hyw/card-ui/public/logos/microsoft.svg +15 -0
  36. entari_plugin_hyw/card-ui/public/logos/minimax.png +0 -0
  37. entari_plugin_hyw/card-ui/public/logos/mistral.png +0 -0
  38. entari_plugin_hyw/card-ui/public/logos/nvida.png +0 -0
  39. entari_plugin_hyw/card-ui/public/logos/openai.svg +1 -0
  40. entari_plugin_hyw/card-ui/public/logos/openrouter.png +0 -0
  41. entari_plugin_hyw/card-ui/public/logos/perplexity.svg +24 -0
  42. entari_plugin_hyw/card-ui/public/logos/qwen.png +0 -0
  43. entari_plugin_hyw/card-ui/public/logos/xai.png +0 -0
  44. entari_plugin_hyw/card-ui/public/logos/xiaomi.png +0 -0
  45. entari_plugin_hyw/card-ui/public/logos/zai.png +0 -0
  46. entari_plugin_hyw/card-ui/public/vite.svg +1 -0
  47. entari_plugin_hyw/card-ui/src/App.vue +216 -0
  48. entari_plugin_hyw/card-ui/src/assets/vue.svg +1 -0
  49. entari_plugin_hyw/card-ui/src/components/HelloWorld.vue +41 -0
  50. entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +330 -0
  51. entari_plugin_hyw/card-ui/src/components/SectionCard.vue +41 -0
  52. entari_plugin_hyw/card-ui/src/components/StageCard.vue +163 -0
  53. entari_plugin_hyw/card-ui/src/main.ts +5 -0
  54. entari_plugin_hyw/card-ui/src/style.css +8 -0
  55. entari_plugin_hyw/card-ui/src/types.ts +51 -0
  56. entari_plugin_hyw/card-ui/tsconfig.app.json +16 -0
  57. entari_plugin_hyw/card-ui/tsconfig.json +7 -0
  58. entari_plugin_hyw/card-ui/tsconfig.node.json +26 -0
  59. entari_plugin_hyw/card-ui/vite.config.ts +16 -0
  60. entari_plugin_hyw/core/config.py +0 -3
  61. entari_plugin_hyw/core/pipeline.py +136 -61
  62. entari_plugin_hyw/core/render_vue.py +255 -0
  63. entari_plugin_hyw/test_output/render_0.jpg +0 -0
  64. entari_plugin_hyw/test_output/render_1.jpg +0 -0
  65. entari_plugin_hyw/test_output/render_2.jpg +0 -0
  66. entari_plugin_hyw/test_output/render_3.jpg +0 -0
  67. entari_plugin_hyw/test_output/render_4.jpg +0 -0
  68. entari_plugin_hyw/tests/ui_test_output.jpg +0 -0
  69. entari_plugin_hyw/tests/verify_ui.py +139 -0
  70. entari_plugin_hyw/utils/misc.py +0 -3
  71. entari_plugin_hyw/utils/prompts.py +65 -63
  72. {entari_plugin_hyw-3.4.1.dist-info → entari_plugin_hyw-3.5.0rc1.dist-info}/METADATA +5 -2
  73. entari_plugin_hyw-3.5.0rc1.dist-info/RECORD +99 -0
  74. entari_plugin_hyw/assets/libs/highlight.css +0 -10
  75. entari_plugin_hyw/assets/libs/highlight.js +0 -1213
  76. entari_plugin_hyw/assets/libs/katex-auto-render.js +0 -1
  77. entari_plugin_hyw/assets/libs/katex.css +0 -1
  78. entari_plugin_hyw/assets/libs/katex.js +0 -1
  79. entari_plugin_hyw/assets/libs/tailwind.css +0 -1
  80. entari_plugin_hyw/assets/package-lock.json +0 -953
  81. entari_plugin_hyw/assets/package.json +0 -16
  82. entari_plugin_hyw/assets/tailwind.config.js +0 -12
  83. entari_plugin_hyw/assets/tailwind.input.css +0 -235
  84. entari_plugin_hyw/assets/template.html +0 -157
  85. entari_plugin_hyw/assets/template.html.bak +0 -157
  86. entari_plugin_hyw/assets/template.j2 +0 -400
  87. entari_plugin_hyw/core/render.py +0 -629
  88. entari_plugin_hyw/utils/prompts_cn.py +0 -119
  89. entari_plugin_hyw-3.4.1.dist-info/RECORD +0 -48
  90. {entari_plugin_hyw-3.4.1.dist-info → entari_plugin_hyw-3.5.0rc1.dist-info}/WHEEL +0 -0
  91. {entari_plugin_hyw-3.4.1.dist-info → entari_plugin_hyw-3.5.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,400 +0,0 @@
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
- <style>
20
- /* Fallback style for broken images in markdown content */
21
- .img-error-fallback {
22
- display: flex;
23
- align-items: center;
24
- justify-content: center;
25
- gap: 8px;
26
- width: 100%;
27
- aspect-ratio: 16 / 9;
28
- margin-bottom: 8px;
29
- background: linear-gradient(135deg, #d3e4fd 0%, #b7d3fe 50%, #8bb9fc 100%);
30
- border-radius: 12px;
31
- color: white;
32
- font-size: 14px;
33
- font-weight: 500;
34
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.25);
35
- }
36
- .img-error-fallback i {
37
- font-size: 20px;
38
- }
39
- /* Dynamic image sizing based on aspect ratio */
40
- #markdown-content img {
41
- border-radius: 8px;
42
- margin-bottom: 8px;
43
- }
44
- #markdown-content img.img-horizontal {
45
- width: 100%;
46
- height: auto;
47
- }
48
- #markdown-content img.img-vertical {
49
- width: 60%;
50
- height: auto;
51
- }
52
- </style>
53
- </head>
54
-
55
- <body class="bg-[#f2f2f2] p-0 box-border m-0 font-sans text-gray-800">
56
- <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 pb-8">
57
-
58
- {# --- MACROS --- #}
59
-
60
- {% macro icon_container(icon_html, box_class="bg-gray-50 rounded-lg border border-gray-100", size_class="w-10 h-10") %}
61
- <div class="flex items-center justify-center {{ size_class }} {{ box_class }} shrink-0">
62
- {{ icon_html | safe }}
63
- </div>
64
- {% endmacro %}
65
-
66
- {% macro card_header(title, icon_html=None, subtitle_html=None, is_plain=False, icon_box_class=None) %}
67
- {% set container_class = "flex items-center gap-3" if is_plain else "flex items-center gap-3 pb-3 mb-3 border-gray-100 border-b" %}
68
- {% set default_box_class = icon_box_class if icon_box_class else "bg-gray-50 rounded-lg border border-gray-100" %}
69
- <div class="{{ container_class }}">
70
- {{ icon_container(icon_html, box_class=default_box_class) }}
71
- <div class="flex flex-col min-w-0">
72
- <div class="text-sm font-bold text-gray-900 uppercase tracking-wide whitespace-nowrap overflow-hidden text-ellipsis">{{ title }}</div>
73
- {% if subtitle_html and not is_plain %}
74
- {{ subtitle_html | safe }}
75
- {% endif %}
76
- </div>
77
- </div>
78
- {% endmacro %}
79
-
80
- {% 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") %}
81
- {% set tag = "a" if link_url else "div" %}
82
- {% set href_attr = 'href="' ~ link_url ~ '" target="_blank"' if link_url else "" %}
83
- {% set hover_class = "transition-colors hover:bg-gray-50" if link_url else "" %}
84
-
85
- {% set padding_class = "p-2.5" if is_compact else "px-4 py-3.5" %}
86
- {% set align_class = "items-center" if is_compact else "items-start" %}
87
- {% set icon_size = "w-6 h-6" if is_compact else "w-8 h-8" %}
88
-
89
- <{{ 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 }}">
90
- <div class="flex items-center justify-center {{ icon_size }} {{ icon_box_class }}">
91
- {{ icon_html | safe }}
92
- </div>
93
- <div class="flex flex-col flex-1 min-w-0 gap-0.5">
94
- <div class="flex items-center gap-2 leading-tight min-w-0">
95
- {{ title_html | safe }}
96
- </div>
97
- {% if subtitle_html %}
98
- <div>{{ subtitle_html | safe }}</div>
99
- {% endif %}
100
- </div>
101
- {% if right_content_html %}
102
- <div class="shrink-0 ml-2">{{ right_content_html | safe }}</div>
103
- {% endif %}
104
- </{{ tag }}>
105
- {% endmacro %}
106
-
107
-
108
- {# --- MAIN CONTENT --- #}
109
-
110
- <!-- Header: Feature Icons / Badges (Centered) -->
111
- <!-- Increased top padding from pt-5 to pt-7 for more whitespace -->
112
- <div class="pt-7 pb-5 px-6 flex justify-between items-center relative z-20">
113
- <!-- Total Time Badge -->
114
- <div class="flex items-center gap-1.5 px-3 py-1.5 bg-white/60 backdrop-blur rounded-lg shadow-sm border border-white/50">
115
- <i class="ri-time-line text-gray-700 font-bold text-[13px]"></i>
116
- <span class="text-[13px] font-bold text-gray-900 tracking-wide">{{ "%.1f"|format(total_time) }}s</span>
117
- </div>
118
- </div>
119
-
120
- <!-- Response Card (Content First) -->
121
- <div class="bg-[#f2f2f2] rounded-2xl px-5 pt-0 pb-2 overflow-hidden">
122
- <div id="markdown-content" class="markdown-body text-[15px] leading-relaxed text-gray-800 [&>*:first-child]:!mt-0">
123
- {{ content_html | safe }}
124
- </div>
125
- </div>
126
-
127
- <!-- Speculation Card (Optional) -->
128
- {% if suggestions %}
129
- <div class="flex flex-col gap-2 bg-[#f2f2f2] rounded-2xl px-5 py-2 overflow-hidden">
130
- {% set sug_icon %}
131
- <i class="ri-magic-line text-lg text-pink-600"></i>
132
- {% endset %}
133
- {{ card_header("SUGGESTIONS", icon_html=sug_icon, is_plain=True) }}
134
-
135
- <div class="grid grid-cols-2 gap-2.5">
136
- {% for sug in suggestions %}
137
- <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">
138
- <span class="text-gray-500 font-mono font-bold text-[13px] whitespace-nowrap">{{ loop.index }}</span>
139
- <span class="flex-1 text-[13px] text-gray-600 font-medium whitespace-nowrap overflow-hidden text-ellipsis">{{ sug }}</span>
140
- </div>
141
- {% endfor %}
142
- </div>
143
- </div>
144
- {% endif %}
145
-
146
- <!-- Pipeline & Children (Nested) -->
147
- <div class="bg-[#f2f2f2] rounded-2xl px-5 pt-2 pb-5 overflow-hidden flex flex-col gap-4">
148
-
149
- {% if stages %}
150
- {% for stage in stages %}
151
- <div>
152
- {# Stage Card #}
153
- {% set color_class = "bg-white text-gray-700 border-gray-200 shadow-sm" %}
154
-
155
- {% set icon_box_class = color_class + " rounded-md border shrink-0" %}
156
-
157
- {% set title_html %}
158
- <span class="text-[13px] font-bold uppercase text-gray-700 shrink-0">{{ stage.name }}</span>
159
- <span class="text-[13px] font-medium text-gray-700 truncate min-w-0" title="{{ stage.model }}">{{ stage.model_short }}</span>
160
- <span class="ml-auto text-[12px] text-gray-400 shrink-0 truncate max-w-[80px]">{{ stage.provider }}</span>
161
- {% endset %}
162
-
163
- {% set stats_html %}
164
- <div class="flex items-center gap-3 text-[12px] text-gray-500 font-mono mt-0.5">
165
- <span>{{ stage.time_str }}</span><span>{{ stage.cost_str }}</span>
166
- </div>
167
- {% endset %}
168
-
169
- {{ list_card(stage.icon_html, title_html, subtitle_html=stats_html, is_compact=True, icon_box_class=icon_box_class) }}
170
-
171
- {# Nested Children (Indent & Connect) #}
172
- {% if stage.references or stage.image_references or stage.flow_steps or stage.crawled_pages %}
173
- <div class="ml-4 pl-4 border-l-2 border-gray-200 mt-2 flex flex-col gap-2">
174
-
175
- {# References #}
176
- {% if stage.references %}
177
- <div class="text-[12px] uppercase font-bold text-blue-600 tracking-wider mb-1 mt-1">Search Results</div>
178
- {% for ref in stage.references %}
179
- {% set favicon_url = "https://www.google.com/s2/favicons?domain=" + ref.domain + "&sz=32" %}
180
-
181
- {% set ref_icon %}
182
- <img src="{{ favicon_url }}" class="w-3.5 h-3.5 rounded-sm opacity-80 decoration-0">
183
- {% endset %}
184
-
185
- {% set ref_icon_box = "bg-white rounded border border-gray-100 w-6 h-6 shrink-0" %}
186
-
187
- {% set title_html = '<div class="text-[13px] 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>' %}
188
- {% set subtitle_html = '<div class="text-[12px] text-gray-700 truncate">' + ref.domain + '</div>' %}
189
- {% set right_html = '<div class="flex items-center justify-center min-w-[16px] h-4 px-0.5 text-[10px] font-bold text-blue-600 bg-blue-50 border border-blue-200 rounded">' + (loop.index|string) + '</div>' %}
190
-
191
- {{ 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) }}
192
- {% endfor %}
193
- {% endif %}
194
-
195
- {# Image References #}
196
- {% if stage.image_references %}
197
- <div class="text-[12px] uppercase font-bold text-blue-600 tracking-wider mb-1 mt-2">Images</div>
198
- {% for img in stage.image_references %}
199
- {% set favicon_url = "https://www.google.com/s2/favicons?domain=" + img.domain + "&sz=32" %}
200
-
201
- {% set img_icon %}
202
- <img src="{{ favicon_url }}" class="w-3.5 h-3.5 rounded-sm opacity-80">
203
- {% endset %}
204
-
205
- {% set img_icon_box = "bg-white rounded border border-gray-100 w-6 h-6 shrink-0" %}
206
-
207
- {% set title_html = '<div class="text-[13px] font-medium text-gray-900 truncate">' + img.title + '</div>' %}
208
- {% set subtitle_html = '<div class="text-[12px] text-gray-500 truncate">' + img.domain + '</div>' %}
209
-
210
- {{ list_card(img_icon, title_html, subtitle_html=subtitle_html, link_url=img.url, is_compact=True, icon_box_class=img_icon_box) }}
211
- {% endfor %}
212
- {% endif %}
213
-
214
- {# Crawled Pages #}
215
- {% if stage.crawled_pages %}
216
- <div class="text-[12px] uppercase font-bold text-blue-600 tracking-wider mb-1 mt-1">Fetched Pages</div>
217
- {% for page in stage.crawled_pages %}
218
- {% set domain = page.url.split('/')[2] if page.url.split('/')|length > 2 else '' %}
219
- {% set favicon_url = "https://www.google.com/s2/favicons?domain=" + domain + "&sz=32" %}
220
-
221
- {% set page_icon %}
222
- <img src="{{ favicon_url }}" class="w-3.5 h-3.5 rounded-sm opacity-80">
223
- {% endset %}
224
-
225
- {% set page_icon_box = "bg-white rounded border border-gray-100 w-6 h-6 shrink-0" %}
226
-
227
- {% set title_html = '<div class="text-[13px] font-medium text-gray-900 truncate underline decoration-gray-300 decoration-1 underline-offset-2 hover:text-black hover:decoration-gray-500 transition-colors">' + page.title + '</div>' %}
228
- {% set subtitle_html = '<div class="text-[12px] text-gray-500 truncate">' + domain + '</div>' %}
229
- {% set right_html = '<div class="flex items-center justify-center min-w-[16px] h-4 px-0.5 text-[10px] font-bold text-orange-600 bg-orange-50 border border-orange-200 rounded">' + (loop.index|string) + '</div>' %}
230
-
231
- {{ list_card(page_icon, title_html, subtitle_html=subtitle_html, link_url=page.url, right_content_html=right_html, is_compact=True, icon_box_class=page_icon_box) }}
232
- {% endfor %}
233
- {% endif %}
234
-
235
- </div>
236
- {% endif %}
237
- </div>
238
- {% endfor %}
239
- {% endif %}
240
-
241
- </div>
242
-
243
- <script>window.REFERENCES = {{ references_json | safe }};</script>
244
-
245
- <script>
246
- document.addEventListener("DOMContentLoaded", () => {
247
- // Initialize Syntax Highlighting
248
- if (typeof hljs !== 'undefined') {
249
- hljs.highlightAll();
250
- }
251
-
252
- // Render Math (KaTeX)
253
- const contentDiv = document.getElementById("markdown-content");
254
- if(typeof renderMathInElement !== 'undefined') {
255
- renderMathInElement(contentDiv, {
256
- delimiters: [
257
- { left: "$$", right: "$$", display: true },
258
- { left: "$", right: "$", display: false },
259
- { left: "\\(", right: "\\)", display: false },
260
- { left: "\\[", right: "\\]", display: true }
261
- ],
262
- throwOnError: false
263
- });
264
- }
265
-
266
- // Process Citations - handles `search:id` (blue) and `page:id` (orange) formats
267
- function processCitations(rootNode) {
268
- // 1. Handle citations inside <code> tags (generated by markdown backticks)
269
- const codeElements = rootNode.querySelectorAll('code');
270
- codeElements.forEach(code => {
271
- const text = code.textContent.trim();
272
- const match = /^(search|page):(\d+)$/i.exec(text);
273
- if (match) {
274
- const type = match[1].toLowerCase();
275
- const id = match[2];
276
- const isPage = type === "page";
277
- const colorClass = isPage
278
- ? "text-orange-600 bg-orange-50 border-orange-200"
279
- : "text-blue-600 bg-blue-50 border-blue-200";
280
-
281
- const span = document.createElement("span");
282
- // Using same badge style
283
- span.innerHTML = `<span class="inline-flex items-center justify-center min-w-[14px] h-4 px-0.5 text-[9px] font-bold ${colorClass} border rounded align-top -top-0.5 relative mx-0.5 cursor-default" title="${type}:${id}">${id}</span>`;
284
-
285
- // Replace the <code> element with our badge
286
- if (code.parentNode) {
287
- code.parentNode.replaceChild(span.firstElementChild, code);
288
- }
289
- }
290
- });
291
-
292
- // 2. Handle citations in plain text (fallback for non-backticked ones)
293
- const walker = document.createTreeWalker(
294
- rootNode,
295
- NodeFilter.SHOW_TEXT,
296
- null,
297
- false
298
- );
299
-
300
- const nodesToReplace = [];
301
- let node;
302
- while (node = walker.nextNode()) {
303
- if (node.parentElement.tagName === "SCRIPT" || node.parentElement.tagName === "STYLE" || node.parentElement.tagName === "A" || node.parentElement.tagName === "CODE") continue;
304
- // Match search:id or page:id
305
- if (/(search|page):(\d+)/i.test(node.nodeValue)) {
306
- nodesToReplace.push(node);
307
- }
308
- }
309
-
310
- nodesToReplace.forEach(textNode => {
311
- const fragment = document.createDocumentFragment();
312
- let lastIndex = 0;
313
- const text = textNode.nodeValue;
314
- // Regex to capture:
315
- // 1. Optional brackets/parens: [(
316
- // 2. Type: search/page
317
- // 3. IDs: 1 or 1,2,3
318
- // 4. Closing: )]
319
- const regex = /[\[\(]?(search|page):\s*([\d,\s]+)[\]\)]?/gi;
320
- let match;
321
-
322
- while ((match = regex.exec(text)) !== null) {
323
- // Validate match: simple check to ensure it contains digits
324
- if (!/\d/.test(match[2])) continue;
325
-
326
- fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
327
-
328
- const fullMatch = match[0];
329
- const type = match[1].toLowerCase();
330
- const idString = match[2];
331
-
332
- // Parse IDs (split by comma or space)
333
- const ids = idString.split(/[,\s]+/).filter(s => s.trim().length > 0);
334
-
335
- // Check for standard format (allow plain or [brackets])
336
- // Standard: search:1, [search:1], page:1, [page:1]
337
- // Non-standard: (page:1), page:1,2, (page:1,2)
338
- const isStandard = /^[\[]?(search|page):\d+[\]]?$/i.test(fullMatch);
339
-
340
- if (!isStandard) {
341
- console.warn(`[Template] Detected non-standard citation format: "${fullMatch}". Rendered as: ${type}:${ids.join(',')}`);
342
- }
343
-
344
- ids.forEach(id => {
345
- const span = document.createElement("span");
346
- const isPage = type === "page";
347
- const colorClass = isPage
348
- ? "text-orange-600 bg-orange-50 border-orange-200"
349
- : "text-blue-600 bg-blue-50 border-blue-200";
350
-
351
- span.innerHTML = `<span class="inline-flex items-center justify-center min-w-[14px] h-4 px-0.5 text-[9px] font-bold ${colorClass} border rounded align-top -top-0.5 relative mx-0.5 cursor-default" title="${type}:${id}">${id}</span>`;
352
- fragment.appendChild(span.firstElementChild);
353
- });
354
-
355
- lastIndex = regex.lastIndex;
356
- }
357
-
358
- fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
359
- if (textNode.parentNode) {
360
- textNode.parentNode.replaceChild(fragment, textNode);
361
- }
362
- });
363
- }
364
-
365
- processCitations(contentDiv);
366
-
367
- // Handle broken images in markdown content
368
- const contentImages = contentDiv.querySelectorAll('img');
369
- contentImages.forEach(img => {
370
- // Apply sizing class based on aspect ratio
371
- const applySizeClass = function() {
372
- if (this.naturalWidth >= this.naturalHeight) {
373
- this.classList.add('img-horizontal');
374
- } else {
375
- this.classList.add('img-vertical');
376
- }
377
- };
378
-
379
- img.onerror = function() {
380
- const fallback = document.createElement('span');
381
- fallback.className = 'img-error-fallback';
382
- fallback.innerHTML = `<span style="font-size: 18px;">(。•́︿•̀。)</span><span>渲染失败</span>`;
383
- this.parentNode.replaceChild(fallback, this);
384
- };
385
-
386
- // Check if image already loaded
387
- if (img.complete) {
388
- if (img.naturalHeight === 0) {
389
- img.onerror();
390
- } else {
391
- applySizeClass.call(img);
392
- }
393
- } else {
394
- img.onload = applySizeClass;
395
- }
396
- });
397
- });
398
- </script>
399
- </body>
400
- </html>