entari-plugin-hyw 4.0.0rc4__py3-none-any.whl → 4.0.0rc6__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 +216 -75
- entari_plugin_hyw/assets/card-dist/index.html +70 -79
- entari_plugin_hyw/browser/__init__.py +10 -0
- entari_plugin_hyw/browser/engines/base.py +13 -0
- entari_plugin_hyw/browser/engines/bing.py +95 -0
- entari_plugin_hyw/browser/engines/duckduckgo.py +137 -0
- entari_plugin_hyw/browser/engines/google.py +155 -0
- entari_plugin_hyw/browser/landing.html +172 -0
- entari_plugin_hyw/browser/manager.py +153 -0
- entari_plugin_hyw/browser/service.py +304 -0
- entari_plugin_hyw/card-ui/src/App.vue +526 -182
- entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +7 -11
- entari_plugin_hyw/card-ui/src/components/StageCard.vue +33 -30
- entari_plugin_hyw/card-ui/src/types.ts +9 -0
- entari_plugin_hyw/definitions.py +155 -0
- entari_plugin_hyw/history.py +111 -33
- entari_plugin_hyw/misc.py +34 -0
- entari_plugin_hyw/modular_pipeline.py +384 -0
- entari_plugin_hyw/render_vue.py +326 -239
- entari_plugin_hyw/search.py +95 -708
- entari_plugin_hyw/stage_base.py +92 -0
- entari_plugin_hyw/stage_instruct.py +345 -0
- entari_plugin_hyw/stage_instruct_deepsearch.py +104 -0
- entari_plugin_hyw/stage_summary.py +164 -0
- {entari_plugin_hyw-4.0.0rc4.dist-info → entari_plugin_hyw-4.0.0rc6.dist-info}/METADATA +4 -4
- {entari_plugin_hyw-4.0.0rc4.dist-info → entari_plugin_hyw-4.0.0rc6.dist-info}/RECORD +28 -16
- entari_plugin_hyw/pipeline.py +0 -1219
- entari_plugin_hyw/prompts.py +0 -47
- {entari_plugin_hyw-4.0.0rc4.dist-info → entari_plugin_hyw-4.0.0rc6.dist-info}/WHEEL +0 -0
- {entari_plugin_hyw-4.0.0rc4.dist-info → entari_plugin_hyw-4.0.0rc6.dist-info}/top_level.txt +0 -0
|
@@ -2,20 +2,38 @@
|
|
|
2
2
|
import { ref, computed, onMounted } from 'vue'
|
|
3
3
|
import { Icon } from '@iconify/vue'
|
|
4
4
|
|
|
5
|
-
import type { RenderData } from './types'
|
|
6
|
-
import StageCard from './components/StageCard.vue'
|
|
5
|
+
import type { RenderData, Reference } from './types'
|
|
7
6
|
import MarkdownContent from './components/MarkdownContent.vue'
|
|
8
7
|
|
|
8
|
+
// Import icons for Flow area
|
|
9
|
+
import iconOpenai from './assets/icon/openai.svg'
|
|
10
|
+
import iconGemini from './assets/icon/gemini.svg'
|
|
11
|
+
import iconAnthropic from './assets/icon/anthropic.svg'
|
|
12
|
+
import iconDeepseek from './assets/icon/deepseek.png'
|
|
13
|
+
import iconQwen from './assets/icon/qwen.png'
|
|
14
|
+
import iconMistral from './assets/icon/mistral.png'
|
|
15
|
+
import iconGrok from './assets/icon/grok.png'
|
|
16
|
+
import iconHuggingface from './assets/icon/huggingface.png'
|
|
17
|
+
import iconCerebras from './assets/icon/cerebras.svg'
|
|
18
|
+
import iconMinimax from './assets/icon/minimax.png'
|
|
19
|
+
import iconPerplexity from './assets/icon/perplexity.svg'
|
|
20
|
+
import iconNvidia from './assets/icon/nvida.png'
|
|
21
|
+
import iconMicrosoft from './assets/icon/microsoft.svg'
|
|
22
|
+
import iconXiaomi from './assets/icon/xiaomi.png'
|
|
23
|
+
import iconOpenrouter from './assets/icon/openrouter.png'
|
|
24
|
+
|
|
9
25
|
// Get icon for card type
|
|
10
26
|
const getCardIcon = (contentType?: string): string => {
|
|
11
27
|
switch (contentType) {
|
|
12
|
-
case 'summary': return 'mdi:
|
|
28
|
+
case 'summary': return 'mdi:text-box-outline'
|
|
13
29
|
case 'code': return 'mdi:code-braces'
|
|
14
30
|
case 'table': return 'mdi:table'
|
|
15
31
|
default: return 'mdi:card-outline'
|
|
16
32
|
}
|
|
17
33
|
}
|
|
18
34
|
|
|
35
|
+
|
|
36
|
+
|
|
19
37
|
// Get display label for card
|
|
20
38
|
const getCardLabel = (contentType?: string, language?: string): string => {
|
|
21
39
|
switch (contentType) {
|
|
@@ -43,34 +61,85 @@ window.updateRenderData = (newData: RenderData) => {
|
|
|
43
61
|
const numSearchRefs = computed(() => data.value?.references?.length || 0)
|
|
44
62
|
const numPageRefs = computed(() => data.value?.page_references?.length || 0)
|
|
45
63
|
|
|
46
|
-
// Calculate the reference offset for each stage (for unified badge numbering)
|
|
47
|
-
const getRefOffset = (stageIndex: number): number => {
|
|
48
|
-
if (!data.value?.stages) return 0
|
|
49
|
-
let offset = 0
|
|
50
|
-
for (let i = 0; i < stageIndex; i++) {
|
|
51
|
-
const stage = data.value.stages[i]
|
|
52
|
-
if (stage) {
|
|
53
|
-
offset += (stage.references?.length || 0) + (stage.crawled_pages?.length || 0)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return offset
|
|
57
|
-
}
|
|
58
64
|
|
|
59
65
|
// Helper: Strips content before the first H1 heading (e.g., AI "thought" prefixes)
|
|
60
66
|
const stripPrefixBeforeH1 = (text: string): string => {
|
|
61
67
|
// Find the first line starting with "# " (H1)
|
|
62
68
|
const h1Match = text.match(/^#\s+/m)
|
|
69
|
+
const summaryMatch = text.match(/<summary>/)
|
|
70
|
+
|
|
71
|
+
let startIndex = -1
|
|
72
|
+
|
|
63
73
|
if (h1Match && h1Match.index !== undefined) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
startIndex = h1Match.index
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (summaryMatch && summaryMatch.index !== undefined) {
|
|
78
|
+
// If summary is found and is BEFORE the H1 (or no H1 found), start from summary
|
|
79
|
+
if (startIndex === -1 || summaryMatch.index < startIndex) {
|
|
80
|
+
startIndex = summaryMatch.index
|
|
81
|
+
}
|
|
67
82
|
}
|
|
83
|
+
|
|
84
|
+
if (startIndex !== -1) {
|
|
85
|
+
return text.substring(startIndex)
|
|
86
|
+
}
|
|
87
|
+
|
|
68
88
|
// If no H1 found, return text as-is (fallback)
|
|
69
89
|
return text
|
|
70
90
|
}
|
|
71
91
|
|
|
92
|
+
// Reorder citations and return cleaned markdown + reordered refs
|
|
93
|
+
const reorderedData = computed(() => {
|
|
94
|
+
const originalMd = stripPrefixBeforeH1(data.value?.markdown || '')
|
|
95
|
+
if (!originalMd) return { markdown: '', references: [] }
|
|
96
|
+
|
|
97
|
+
const searchRefs = (data.value?.references || []).map((r, i) => ({...r, type: 'search', _orig: i + 1}))
|
|
98
|
+
const pageRefs = (data.value?.page_references || []).map((r, i) => ({...r, type: 'page', _orig: (data.value?.references?.length || 0) + i + 1}))
|
|
99
|
+
const allRefs = [...searchRefs, ...pageRefs]
|
|
100
|
+
|
|
101
|
+
const citationRegex = /\[(\d+)\]/g
|
|
102
|
+
const usageOrder: number[] = []
|
|
103
|
+
let match
|
|
104
|
+
// Scan for usage order
|
|
105
|
+
while ((match = citationRegex.exec(originalMd)) !== null) {
|
|
106
|
+
const id = parseInt(match[1]!)
|
|
107
|
+
if (!usageOrder.includes(id)) usageOrder.push(id)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const idMap = new Map()
|
|
111
|
+
const newReferences: any[] = []
|
|
112
|
+
|
|
113
|
+
// 1. Used refs
|
|
114
|
+
usageOrder.forEach((oldId, idx) => {
|
|
115
|
+
const newId = idx + 1
|
|
116
|
+
idMap.set(oldId, newId)
|
|
117
|
+
const sourceRef = allRefs[oldId - 1]
|
|
118
|
+
if (sourceRef) newReferences.push({ ...sourceRef, original_idx: newId })
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// 2. Unused refs
|
|
122
|
+
allRefs.forEach((ref, idx) => {
|
|
123
|
+
const oldId = idx + 1
|
|
124
|
+
if (!idMap.has(oldId)) {
|
|
125
|
+
const newId = newReferences.length + 1
|
|
126
|
+
idMap.set(oldId, newId)
|
|
127
|
+
newReferences.push({ ...ref, original_idx: newId })
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const newMd = originalMd.replace(citationRegex, (m, n) => {
|
|
132
|
+
const newId = idMap.get(parseInt(n))
|
|
133
|
+
return newId ? `[${newId}]` : m
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
return { markdown: newMd, references: newReferences }
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const referencesList = computed(() => reorderedData.value.references)
|
|
140
|
+
|
|
72
141
|
const mainTitle = computed(() => {
|
|
73
|
-
const md =
|
|
142
|
+
const md = reorderedData.value.markdown || ''
|
|
74
143
|
const match = md.match(/^#\s+(.+)$/m)
|
|
75
144
|
return match && match[1] ? match[1].trim() : ''
|
|
76
145
|
})
|
|
@@ -82,6 +151,141 @@ const processedTitle = computed(() => {
|
|
|
82
151
|
})
|
|
83
152
|
})
|
|
84
153
|
|
|
154
|
+
function getDomain(url: string): string {
|
|
155
|
+
try {
|
|
156
|
+
const urlObj = new URL(url)
|
|
157
|
+
const hostname = urlObj.hostname.replace('www.', '')
|
|
158
|
+
let pathname = urlObj.pathname === '/' ? '' : decodeURIComponent(urlObj.pathname)
|
|
159
|
+
|
|
160
|
+
// Truncate if too long
|
|
161
|
+
const maxLen = 40
|
|
162
|
+
let result = hostname + pathname
|
|
163
|
+
if (result.length > maxLen) {
|
|
164
|
+
result = result.slice(0, maxLen - 3) + '...'
|
|
165
|
+
}
|
|
166
|
+
return result
|
|
167
|
+
} catch {
|
|
168
|
+
return url.length > 40 ? url.slice(0, 37) + '...' : url
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getFavicon(url: string): string {
|
|
173
|
+
const domain = getDomain(url)
|
|
174
|
+
return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Robustly formats an image source.
|
|
179
|
+
* Handles:
|
|
180
|
+
* 1. Absolute URLs (http, https, //)
|
|
181
|
+
* 2. Data URIs (data:image/...)
|
|
182
|
+
* 3. Raw Base64 strings (fallbacks to data URI)
|
|
183
|
+
*/
|
|
184
|
+
function getImageUrl(src: string): string {
|
|
185
|
+
if (!src) return ''
|
|
186
|
+
|
|
187
|
+
// 1. Data URI
|
|
188
|
+
if (src.startsWith('data:')) return src
|
|
189
|
+
|
|
190
|
+
// 2. Protocol-relative or Absolute URL
|
|
191
|
+
if (src.startsWith('//') || src.startsWith('http:') || src.startsWith('https:')) {
|
|
192
|
+
return src
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 3. Assume raw base64 (remove potential whitespace)
|
|
196
|
+
const cleanBase64 = src.trim()
|
|
197
|
+
if (cleanBase64.length > 0) {
|
|
198
|
+
return `data:image/jpeg;base64,${cleanBase64}`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return src
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function isValidImage(src: string): boolean {
|
|
205
|
+
if (!src) return false
|
|
206
|
+
if (src.startsWith('http') || src.startsWith('//')) return true
|
|
207
|
+
if (src.length < 20) return false // Too short for meaningful data
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Get all instruct/analysis stages for aggregation
|
|
212
|
+
const instructStages = computed(() => data.value?.stages?.filter(s =>
|
|
213
|
+
s.name?.toLowerCase() === 'instruct' ||
|
|
214
|
+
s.name?.toLowerCase().startsWith('analysis') ||
|
|
215
|
+
s.provider?.toLowerCase() === 'instruct'
|
|
216
|
+
) || [])
|
|
217
|
+
|
|
218
|
+
// Aggregated instruct data (sum of all rounds)
|
|
219
|
+
const instructStage = computed(() => {
|
|
220
|
+
const stages = instructStages.value
|
|
221
|
+
if (!stages.length) return null
|
|
222
|
+
|
|
223
|
+
// Get first stage as base for model/icon info
|
|
224
|
+
const first = stages[0]
|
|
225
|
+
|
|
226
|
+
// Sum time, usage, and cost from all rounds
|
|
227
|
+
const totalTime = stages.reduce((sum, s) => sum + (s.time || 0), 0)
|
|
228
|
+
const totalInputTokens = stages.reduce((sum, s) => sum + (s.usage?.input_tokens || 0), 0)
|
|
229
|
+
const totalOutputTokens = stages.reduce((sum, s) => sum + (s.usage?.output_tokens || 0), 0)
|
|
230
|
+
const totalCost = stages.reduce((sum, s) => sum + (s.cost || 0), 0)
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
...first,
|
|
234
|
+
time: totalTime,
|
|
235
|
+
usage: { input_tokens: totalInputTokens, output_tokens: totalOutputTokens },
|
|
236
|
+
cost: totalCost
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
const summaryStage = computed(() => data.value?.stages?.find(s => s.name?.toLowerCase() === 'summary' || s.name?.toLowerCase() === 'agent'))
|
|
241
|
+
// searchStage removed - no longer needed for display
|
|
242
|
+
|
|
243
|
+
// Collect all extracted images from references
|
|
244
|
+
const galleryImages = computed(() => {
|
|
245
|
+
const refs = (data.value?.references || []) as Reference[]
|
|
246
|
+
const images: string[] = []
|
|
247
|
+
const seenHashes = new Set<string>()
|
|
248
|
+
|
|
249
|
+
// Strategy: Balanced picking
|
|
250
|
+
// 1. First Pass: Try to pick 1-2 from each
|
|
251
|
+
for (const ref of refs) {
|
|
252
|
+
if (ref.images && Array.isArray(ref.images)) {
|
|
253
|
+
let count = 0
|
|
254
|
+
for (const b64 of ref.images) {
|
|
255
|
+
if (!isValidImage(b64)) continue
|
|
256
|
+
const hash = `${b64.substring(0, 100)}_${b64.length}`
|
|
257
|
+
if (!seenHashes.has(hash)) {
|
|
258
|
+
seenHashes.add(hash)
|
|
259
|
+
images.push(b64)
|
|
260
|
+
count++
|
|
261
|
+
if (count >= 2) break
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 2. Second Pass: If still too few, pick more from anyone
|
|
268
|
+
if (images.length < 8) {
|
|
269
|
+
for (const ref of refs) {
|
|
270
|
+
if (ref.images && Array.isArray(ref.images)) {
|
|
271
|
+
for (const b64 of ref.images) {
|
|
272
|
+
if (!isValidImage(b64)) continue
|
|
273
|
+
const hash = `${b64.substring(0, 100)}_${b64.length}`
|
|
274
|
+
if (!seenHashes.has(hash)) {
|
|
275
|
+
seenHashes.add(hash)
|
|
276
|
+
images.push(b64)
|
|
277
|
+
if (images.length >= 12) break
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (images.length >= 12) break
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log(`[Gallery] Selected ${images.length} images. First 100 chars of #1:`, images[0]?.substring(0, 100))
|
|
286
|
+
return images.slice(0, 12)
|
|
287
|
+
})
|
|
288
|
+
|
|
85
289
|
|
|
86
290
|
|
|
87
291
|
|
|
@@ -123,24 +327,47 @@ const headerTextColor = computed(() => {
|
|
|
123
327
|
return luminance > 0.4 ? '#1f2937' : '#ffffff' // gray-800 or white
|
|
124
328
|
})
|
|
125
329
|
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
const getIconPath = (stage?: any): string => {
|
|
333
|
+
if (!stage) return iconOpenai
|
|
334
|
+
const model = (stage.model || '').toLowerCase()
|
|
335
|
+
const provider = (stage.provider || '').toLowerCase()
|
|
336
|
+
|
|
337
|
+
// Match to imported icons
|
|
338
|
+
if (model.includes('gpt') || model.includes('o1') || provider.includes('openai')) return iconOpenai
|
|
339
|
+
if (model.includes('gemini') || provider.includes('google')) return iconGemini
|
|
340
|
+
if (model.includes('claude') || provider.includes('anthropic')) return iconAnthropic
|
|
341
|
+
if (model.includes('deepseek') || provider.includes('deepseek')) return iconDeepseek
|
|
342
|
+
if (model.includes('qwen') || provider.includes('qwen') || provider.includes('alibaba')) return iconQwen
|
|
343
|
+
if (model.includes('mistral') || provider.includes('mistral')) return iconMistral
|
|
344
|
+
if (model.includes('grok') || provider.includes('xai')) return iconGrok
|
|
345
|
+
if (model.includes('huggingface')) return iconHuggingface
|
|
346
|
+
if (model.includes('cerebras')) return iconCerebras
|
|
347
|
+
if (model.includes('minimax')) return iconMinimax
|
|
348
|
+
if (model.includes('perplexity')) return iconPerplexity
|
|
349
|
+
if (model.includes('nvidia')) return iconNvidia
|
|
350
|
+
if (model.includes('phi') || provider.includes('microsoft')) return iconMicrosoft
|
|
351
|
+
if (model.includes('xiaomi') || model.includes('mimo')) return iconXiaomi
|
|
352
|
+
|
|
353
|
+
return iconOpenrouter // Default fallback
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
|
|
126
357
|
const themeStyle = computed(() => ({
|
|
127
358
|
'--theme-color': themeColor.value,
|
|
128
359
|
'--header-text-color': headerTextColor.value,
|
|
129
360
|
'--text-primary': '#2c2c2e', // Warm dark gray for headings (Apple HIG inspired)
|
|
130
361
|
'--text-body': '#3a3a3c', // Softer reading color for body text
|
|
131
|
-
'--text-muted': '#
|
|
362
|
+
'--text-muted': '#86868b', // Lighter muted secondary text (updated from #636366)
|
|
132
363
|
'--border-color': '#e5e7eb', // gray-200, for borders
|
|
133
364
|
'--bg-subtle': '#f9fafb' // gray-50, for subtle backgrounds
|
|
134
365
|
}))
|
|
135
366
|
|
|
136
367
|
|
|
137
368
|
const parsedSections = computed(() => {
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
140
|
-
|
|
141
|
-
// Robustness: Strip any content (AI thoughts, system role prefixes) before the first H1 heading
|
|
142
|
-
// User request: "Match the first big header, ignore what comes before it" ("匹配第一个大标题 无视前面的")
|
|
143
|
-
const md = stripPrefixBeforeH1(rawMd)
|
|
369
|
+
const md = reorderedData.value.markdown || ''
|
|
370
|
+
if (!md) return []
|
|
144
371
|
|
|
145
372
|
let content = md.replace(/^#\s+.+$/m, '')
|
|
146
373
|
content = content.replace(/(?:^|\n)\s*(?:#{1,3}|\*\*)\s*(?:References|Citations|Sources)[\s\S]*$/i, '')
|
|
@@ -215,114 +442,70 @@ onMounted(() => {
|
|
|
215
442
|
} else {
|
|
216
443
|
// Demo data for development preview
|
|
217
444
|
data.value = {
|
|
218
|
-
markdown: `#
|
|
445
|
+
markdown: `# Entari Headless Browser System
|
|
446
|
+
This interface demonstrates the capabilities of the Entari Headless Browser system. It generates high-resolution, pixel-perfect captures of AI interactions [1].
|
|
219
447
|
|
|
220
448
|
<summary>
|
|
221
|
-
|
|
449
|
+
The system renders Markdown, Code, Tables, and complex UI layouts using a headless browser, optimized for archiving and sharing AI logic flows. This summary block highlights key information [2].
|
|
222
450
|
</summary>
|
|
223
451
|
|
|
224
|
-
##
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
## 安装与运行建议
|
|
246
|
-
由于其高度集成的特性,官方强烈建议使用 **Prism Launcher** 进行安装和管理 [5]。在运行环境方面,虽然基于旧版 MC,但通过社区努力,目前推荐使用 **Java 17-25** 版本以获得最佳的内存管理和性能优化,确保大型自动化工厂运行流畅 [5]。
|
|
247
|
-
|
|
248
|
-
\`\`\`bash
|
|
249
|
-
curl -s https://raw.githubusercontent.com/GTNewHorizons/GT-New-Horizons-Modpack/master/README.md
|
|
250
|
-
java -version
|
|
251
|
-
java -Xmx1024M -Xms1024M -jar prism-launcher.jar
|
|
252
|
-
\`\`\``,
|
|
253
|
-
total_time: 8.5,
|
|
452
|
+
## Component Showcase
|
|
453
|
+
|
|
454
|
+
### Code Highlighting
|
|
455
|
+
\`\`\`python
|
|
456
|
+
class EntariBrowser:
|
|
457
|
+
def capture(self, url: str) -> bytes:
|
|
458
|
+
"""Captures a screenshot of the given URL."""
|
|
459
|
+
return self.driver.get_screenshot_as_png()
|
|
460
|
+
\`\`\`
|
|
461
|
+
|
|
462
|
+
### Data Tables
|
|
463
|
+
| Feature | Status | Priority |
|
|
464
|
+
| :--- | :--- | :--- |
|
|
465
|
+
| Markdown | ✅ Supported | High |
|
|
466
|
+
| Syntax Highlight | ✅ Supported | Medium |
|
|
467
|
+
| Tables | ✅ Supported | Low |
|
|
468
|
+
|
|
469
|
+
## Citation Handling
|
|
470
|
+
The system automatically handles citations like [1] and [2], reordering them dynamically to match the flow.`,
|
|
471
|
+
total_time: 1.5,
|
|
254
472
|
stages: [
|
|
255
473
|
{
|
|
256
474
|
name: 'instruct',
|
|
257
|
-
model: '
|
|
258
|
-
provider: '
|
|
259
|
-
time:
|
|
260
|
-
cost: 0.
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
name: 'search',
|
|
264
|
-
model: '',
|
|
265
|
-
provider: '',
|
|
266
|
-
time: 0.5,
|
|
267
|
-
cost: 0.0,
|
|
268
|
-
references: [
|
|
269
|
-
{ title: 'GTNH 2025 Server Information', url: 'https://stonelegion.com/mc-gtnh-2026/' },
|
|
270
|
-
{ title: 'GT New Horizons Wiki', url: 'https://gtnh.miraheze.org/wiki/Main_Page' },
|
|
271
|
-
{ title: 'GT New Horizons - GitHub', url: 'https://github.com/GTNewHorizons/GT-New-Horizons-Modpack' },
|
|
272
|
-
{ title: 'GT New Horizons - CurseForge', url: 'https://www.curseforge.com/minecraft/modpacks/gt-new-horizons' },
|
|
273
|
-
{ title: 'Installing and Migrating - GTNH', url: 'https://gtnh.miraheze.org/wiki/Installing_and_Migrating' },
|
|
274
|
-
{ title: 'Modlist - GT New Horizons', url: 'https://wiki.gtnewhorizons.com/wiki/Modlist' },
|
|
275
|
-
{ title: 'GregTech: New Horizons - Home', url: 'https://www.gtnewhorizons.com/' },
|
|
276
|
-
{ title: 'GT New Horizons - FTB Wiki', url: 'https://ftb.fandom.com/wiki/GT_New_Horizons' }
|
|
277
|
-
],
|
|
278
|
-
image_references: [
|
|
279
|
-
{ title: 'GTNH Live Lets Play', url: 'https://i.ytimg.com/vi/5T-oSWAgaMM/maxresdefault.jpg', thumbnail: 'https://tse4.mm.bing.net/th/id/OIP.b_56VnY4nyrzeqp1JetmFQHaEK?pid=Api' },
|
|
280
|
-
{ title: 'GTNH Modpack Cover', url: 'https://i.mcmod.cn/modpack/cover/20240113/1705139595_29797_dSkE.jpg', thumbnail: 'https://tse1.mm.bing.net/th/id/OIP.KNKaZX1d_4Ueq6vpl1qJNAHaEo?pid=Api' },
|
|
281
|
-
{ title: 'GTNH Steam Age', url: 'https://i.ytimg.com/vi/8IPwXxqB71w/maxresdefault.jpg', thumbnail: 'https://tse4.mm.bing.net/th/id/OIP.P-KrnI4GBH21yPgwpNPSzAHaEK?pid=Api' },
|
|
282
|
-
{ title: 'GTNH MCMod Cover', url: 'https://i.mcmod.cn/post/cover/20230201/1675241030_2_VqDc.jpg', thumbnail: 'https://tse2.mm.bing.net/th/id/OIP.GvYz7YWrg-fnpAHjOiW3OAHaEo?pid=Api' },
|
|
283
|
-
{ title: 'GTNH Tectech Tutorial', url: 'http://i0.hdslb.com/bfs/archive/1ed1e53341fd44018138f2823b2fe6c499fb9c9c.jpg', thumbnail: 'https://tse4.mm.bing.net/th/id/OIP.0Wg7xFHTjhxIV9hKuUo4xwHaEo?pid=Api' }
|
|
284
|
-
]
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
name: 'crawler',
|
|
288
|
-
model: '',
|
|
289
|
-
provider: '',
|
|
290
|
-
time: 2.5,
|
|
291
|
-
cost: 0.0,
|
|
292
|
-
crawled_pages: [
|
|
293
|
-
{ title: 'GregTech: New Horizons Official Wiki', url: 'https://gtnh.miraheze.org/wiki/Main_Page' },
|
|
294
|
-
{ title: 'GT New Horizons Modpack Download', url: 'https://www.curseforge.com/minecraft/modpacks/gt-new-horizons' },
|
|
295
|
-
{ title: 'Installing and Migrating Guide', url: 'https://gtnh.miraheze.org/wiki/Installing_and_Migrating' }
|
|
296
|
-
]
|
|
475
|
+
model: 'entari/demo-v1',
|
|
476
|
+
provider: 'Entari',
|
|
477
|
+
time: 0.8,
|
|
478
|
+
cost: 0.001,
|
|
297
479
|
},
|
|
298
480
|
{
|
|
299
|
-
name: '
|
|
300
|
-
model: '
|
|
301
|
-
provider: '
|
|
302
|
-
time:
|
|
303
|
-
cost: 0.
|
|
481
|
+
name: 'summary',
|
|
482
|
+
model: 'entari/summary-v1',
|
|
483
|
+
provider: 'Entari',
|
|
484
|
+
time: 0.7,
|
|
485
|
+
cost: 0.0005,
|
|
304
486
|
}
|
|
305
487
|
],
|
|
306
488
|
references: [
|
|
307
|
-
{
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
{
|
|
313
|
-
|
|
314
|
-
|
|
489
|
+
{
|
|
490
|
+
title: 'Entari Project Documentation',
|
|
491
|
+
url: 'https://github.com/entari/docs',
|
|
492
|
+
snippet: 'Official documentation for Entari framework...'
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
title: 'Headless Browser Concepts',
|
|
496
|
+
url: 'https://en.wikipedia.org/wiki/Headless_browser',
|
|
497
|
+
snippet: 'A **headless browser** is a web browser without a graphical user interface.'
|
|
498
|
+
}
|
|
315
499
|
],
|
|
316
500
|
page_references: [
|
|
317
|
-
{
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
{ title: 'GTNH Modpack Cover', url: 'https://i.mcmod.cn/modpack/cover/20240113/1705139595_29797_dSkE.jpg', thumbnail: 'https://tse1.mm.bing.net/th/id/OIP.KNKaZX1d_4Ueq6vpl1qJNAHaEo?pid=Api' },
|
|
323
|
-
{ title: 'GTNH Steam Age', url: 'https://i.ytimg.com/vi/8IPwXxqB71w/maxresdefault.jpg', thumbnail: 'https://tse4.mm.bing.net/th/id/OIP.P-KrnI4GBH21yPgwpNPSzAHaEK?pid=Api' }
|
|
501
|
+
{
|
|
502
|
+
title: 'Vue.js Framework',
|
|
503
|
+
url: 'https://vuejs.org/',
|
|
504
|
+
snippet: 'The Progressive JavaScript Framework. Approachable, Performant, and Versatile.'
|
|
505
|
+
}
|
|
324
506
|
],
|
|
325
|
-
|
|
507
|
+
image_references: [],
|
|
508
|
+
stats: { total_time: 1.5 },
|
|
326
509
|
theme_color: '#ef4444'
|
|
327
510
|
}
|
|
328
511
|
}
|
|
@@ -330,83 +513,244 @@ java -Xmx1024M -Xms1024M -jar prism-launcher.jar
|
|
|
330
513
|
</script>
|
|
331
514
|
|
|
332
515
|
<template>
|
|
333
|
-
<div class="
|
|
334
|
-
<!--
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
<!-- Content Sections -->
|
|
344
|
-
<template v-for="(section, idx) in parsedSections" :key="idx">
|
|
516
|
+
<div id="app-wrapper" class="min-h-screen w-full flex justify-center bg-[#f2f2f2]" :style="themeStyle">
|
|
517
|
+
<!--
|
|
518
|
+
Container Scaling:
|
|
519
|
+
Width: 560px
|
|
520
|
+
Zoom: 1.5
|
|
521
|
+
Resulting Visual Width: 840px
|
|
522
|
+
-->
|
|
523
|
+
<div class="origin-top my-10" :style="{ zoom: 1.5 }">
|
|
524
|
+
<div id="main-container" class="w-[560px] min-h-[500px] px-8 py-10 space-y-6 bg-[#f2f2f2]" data-theme="light">
|
|
345
525
|
|
|
346
|
-
<!--
|
|
347
|
-
<
|
|
348
|
-
<
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
526
|
+
<!-- Title -->
|
|
527
|
+
<header v-if="mainTitle" class="mb-6">
|
|
528
|
+
<h1 class="text-[32px] font-black leading-tight tracking-tighter uppercase tabular-nums" style="color: var(--text-primary)" v-html="processedTitle"></h1>
|
|
529
|
+
</header>
|
|
530
|
+
|
|
531
|
+
<!-- Content Sections -->
|
|
532
|
+
<template v-for="(section, idx) in parsedSections" :key="idx">
|
|
533
|
+
|
|
534
|
+
<!-- Standard Markdown -->
|
|
535
|
+
<div v-if="section.type === 'markdown'">
|
|
536
|
+
<MarkdownContent
|
|
537
|
+
:markdown="section.content"
|
|
538
|
+
:num-search-refs="numSearchRefs"
|
|
539
|
+
:num-page-refs="numPageRefs"
|
|
540
|
+
class="prose-h2:text-[22px] prose-h2:font-black prose-h2:uppercase prose-h2:tracking-tight prose-h2:mb-4 prose-h2:text-gray-800"
|
|
541
|
+
/>
|
|
542
|
+
</div>
|
|
355
543
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
544
|
+
<!-- Special Card (Table/Code/Summary) -->
|
|
545
|
+
<div v-else-if="section.type === 'card'" class="relative">
|
|
546
|
+
<!-- Corner Rectangle Badge with Icon and Label -->
|
|
547
|
+
<div
|
|
548
|
+
class="absolute -top-2 -left-2 h-7 px-2.5 z-10 flex items-center justify-center gap-1.5"
|
|
549
|
+
:style="{ backgroundColor: themeColor, color: headerTextColor, boxShadow: '0 2px 4px 0 rgba(0,0,0,0.15)' }"
|
|
550
|
+
>
|
|
551
|
+
<Icon :icon="getCardIcon(section.contentType)" class="text-[14px]" />
|
|
552
|
+
<span class="text-[12px] font-bold uppercase tracking-wide">{{ getCardLabel(section.contentType, section.language) }}</span>
|
|
553
|
+
</div>
|
|
554
|
+
<div
|
|
555
|
+
class="shadow-sm shadow-black/5 bg-white"
|
|
556
|
+
:class="[
|
|
557
|
+
section.contentType === 'summary' ? 'pt-8 px-5 pb-4 text-base leading-relaxed text-justify break-words' : '',
|
|
558
|
+
section.contentType === 'code' ? 'pt-7 pb-2' : '',
|
|
559
|
+
section.contentType === 'table' ? 'pt-5' : ''
|
|
560
|
+
]"
|
|
561
|
+
>
|
|
562
|
+
<MarkdownContent
|
|
563
|
+
:markdown="section.content"
|
|
564
|
+
:bare="true"
|
|
565
|
+
:num-search-refs="numSearchRefs"
|
|
566
|
+
:num-page-refs="numPageRefs"
|
|
567
|
+
/>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
|
|
571
|
+
</template>
|
|
572
|
+
|
|
573
|
+
<!-- Sources Section (Bibliography) - Styled as Card -->
|
|
574
|
+
<div v-if="referencesList.length" class="relative group/sources">
|
|
575
|
+
<!-- Corner Rectangle Badge -->
|
|
359
576
|
<div
|
|
360
|
-
class="absolute -top-2 -left-2 h-7 px-2.5 z-10 flex items-center gap-1.5"
|
|
577
|
+
class="absolute -top-2 -left-2 h-7 px-2.5 z-10 flex items-center justify-center gap-1.5"
|
|
361
578
|
:style="{ backgroundColor: themeColor, color: headerTextColor, boxShadow: '0 2px 4px 0 rgba(0,0,0,0.15)' }"
|
|
362
579
|
>
|
|
363
|
-
<Icon
|
|
364
|
-
<span class="text-
|
|
580
|
+
<Icon icon="mdi:book-open-page-variant-outline" class="text-[14px]" />
|
|
581
|
+
<span class="text-[12px] font-bold uppercase tracking-wide">Sources</span>
|
|
365
582
|
</div>
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
583
|
+
|
|
584
|
+
<div class="shadow-sm shadow-black/5 bg-white pt-10 px-5 pb-6 space-y-6">
|
|
585
|
+
<div v-for="ref in referencesList" :key="ref.url" class="group/item flex items-start gap-3 pl-0.5">
|
|
586
|
+
<!-- Number -->
|
|
587
|
+
<div class="shrink-0 w-5 h-5 text-[14px] font-bold flex items-center justify-center pt-0.5"
|
|
588
|
+
:style="{ color: themeColor }">
|
|
589
|
+
{{ ref.original_idx }}
|
|
590
|
+
</div>
|
|
591
|
+
|
|
592
|
+
<!-- Content -->
|
|
593
|
+
<div class="flex-1 min-w-0">
|
|
594
|
+
<!-- Title -->
|
|
595
|
+
<a :href="ref.url" target="_blank" class="block mb-0.5">
|
|
596
|
+
<div class="text-[16px] font-bold leading-tight group-hover/item:text-[var(--theme-color)] transition-colors" style="color: var(--text-primary)">
|
|
597
|
+
{{ ref.title }}
|
|
598
|
+
</div>
|
|
599
|
+
</a>
|
|
600
|
+
|
|
601
|
+
<!-- Domain & Favicon -->
|
|
602
|
+
<div class="flex items-center gap-2.5 text-[10px] font-mono mb-2" style="color: var(--text-muted)">
|
|
603
|
+
<img :src="getFavicon(ref.url)" class="w-3 h-3 object-contain rounded-sm">
|
|
604
|
+
<span>{{ getDomain(ref.url) }}</span>
|
|
605
|
+
</div>
|
|
606
|
+
|
|
607
|
+
<!-- Snippet / Screenshot (Condition: Must have snippet or raw screenshot) -->
|
|
608
|
+
<div v-if="ref.raw_screenshot_b64 || ref.snippet"
|
|
609
|
+
class="mt-1.5 pl-3 py-0.5"
|
|
610
|
+
:class="[(ref.is_fetched || ref.type === 'page') ? 'border-l-[3px]' : 'border-l-2 border-transparent']"
|
|
611
|
+
:style="(ref.is_fetched || ref.type === 'page') ? { borderColor: themeColor } : {}"
|
|
612
|
+
>
|
|
613
|
+
<!-- Real page screenshot if available -->
|
|
614
|
+
<img v-if="ref.raw_screenshot_b64"
|
|
615
|
+
:src="getImageUrl(ref.raw_screenshot_b64)"
|
|
616
|
+
class="max-w-full h-auto rounded-sm border border-gray-200 shadow-sm"
|
|
617
|
+
alt="Page preview"
|
|
618
|
+
/>
|
|
619
|
+
<!-- Fallback to markdown snippet -->
|
|
620
|
+
<MarkdownContent v-else
|
|
621
|
+
:markdown="ref.snippet"
|
|
622
|
+
:bare="true"
|
|
623
|
+
:compact="true"
|
|
624
|
+
/>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
</div>
|
|
380
628
|
</div>
|
|
381
629
|
</div>
|
|
382
630
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
631
|
+
<!-- Gallery Section (Extracted Images) - Masonry Layout -->
|
|
632
|
+
<div v-if="galleryImages.length" class="relative group/gallery mb-8">
|
|
633
|
+
<!-- Corner Badge -->
|
|
634
|
+
<div
|
|
635
|
+
class="absolute -top-2 -left-2 h-7 px-2.5 z-10 flex items-center justify-center gap-1.5"
|
|
636
|
+
:style="{ backgroundColor: themeColor, color: headerTextColor, boxShadow: '0 2px 4px 0 rgba(0,0,0,0.15)' }"
|
|
637
|
+
>
|
|
638
|
+
<Icon icon="mdi:image-multiple-outline" class="text-[14px]" />
|
|
639
|
+
<span class="text-[12px] font-bold uppercase tracking-wide">Gallery</span>
|
|
640
|
+
</div>
|
|
641
|
+
|
|
642
|
+
<div class="shadow-sm shadow-black/5 bg-white pt-10 px-6 pb-6">
|
|
643
|
+
<!-- Masonry Layout: 2 Columns -->
|
|
644
|
+
<div class="columns-2 gap-4 space-y-4">
|
|
645
|
+
<div v-for="(img, idx) in galleryImages" :key="idx" class="break-inside-avoid relative rounded-sm overflow-hidden border border-gray-100 bg-gray-50">
|
|
646
|
+
<img
|
|
647
|
+
:src="getImageUrl(img)"
|
|
648
|
+
class="w-full h-auto block object-cover transform hover:scale-105 transition-transform duration-500"
|
|
649
|
+
loading="lazy"
|
|
650
|
+
/>
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
394
654
|
</div>
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
655
|
+
|
|
656
|
+
<!-- Flow: Unified Stage Info Area -->
|
|
657
|
+
<div v-if="instructStage || summaryStage" class="relative group/flow">
|
|
658
|
+
<!-- Corner Badge -->
|
|
659
|
+
<div
|
|
660
|
+
class="absolute -top-2 -left-2 h-7 px-2.5 z-10 flex items-center justify-center gap-1.5"
|
|
661
|
+
:style="{ backgroundColor: themeColor, color: headerTextColor, boxShadow: '0 2px 4px 0 rgba(0,0,0,0.15)' }"
|
|
662
|
+
>
|
|
663
|
+
<Icon icon="mdi:sitemap-outline" class="text-[14px]" />
|
|
664
|
+
<span class="text-[12px] font-bold uppercase tracking-wide">Flow</span>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<!-- Flow Content (Timeline Style) -->
|
|
668
|
+
<div class="shadow-sm shadow-black/5 bg-white pt-8 px-6 pb-8">
|
|
669
|
+
<div class="space-y-8 relative">
|
|
670
|
+
|
|
671
|
+
<!-- Instruct Stage -->
|
|
672
|
+
<div v-if="instructStage" class="relative flex items-start gap-4 z-10 w-full">
|
|
673
|
+
<!-- Node: Brand Logo -->
|
|
674
|
+
<div class="shrink-0 w-6 h-6 flex items-center justify-center bg-white">
|
|
675
|
+
<img :src="getIconPath(instructStage)" class="w-5 h-5 object-contain" alt="" />
|
|
676
|
+
</div>
|
|
677
|
+
<!-- Content -->
|
|
678
|
+
<div class="flex-1 min-w-0 pt-1">
|
|
679
|
+
<div class="text-[17px] font-bold uppercase tracking-tight mb-1.5 leading-none" style="color: var(--text-primary)">Instruct</div>
|
|
680
|
+
<div class="flex items-center justify-between gap-x-4 text-[13px] font-mono leading-tight w-full" style="color: var(--text-muted)">
|
|
681
|
+
<span class="truncate max-w-[180px]" :title="instructStage.model">{{ instructStage.model }}</span>
|
|
682
|
+
|
|
683
|
+
<div class="flex items-center gap-4 shrink-0">
|
|
684
|
+
<div class="flex items-center gap-1.5 opacity-80">
|
|
685
|
+
<Icon icon="mdi:clock-outline" class="text-[13px]" />
|
|
686
|
+
<span>{{ (instructStage.time || 0).toFixed(2) }}s</span>
|
|
687
|
+
</div>
|
|
688
|
+
<template v-if="instructStage.cost">
|
|
689
|
+
<div class="flex items-center gap-0.5 opacity-80">
|
|
690
|
+
<span>${{ instructStage.cost.toFixed(5) }}</span>
|
|
691
|
+
</div>
|
|
692
|
+
</template>
|
|
693
|
+
</div>
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
</div>
|
|
697
|
+
|
|
698
|
+
<!-- Summary Stage -->
|
|
699
|
+
<div v-if="summaryStage" class="relative flex items-start gap-4 z-10 w-full">
|
|
700
|
+
<!-- Node: Brand Logo -->
|
|
701
|
+
<div class="shrink-0 w-6 h-6 flex items-center justify-center bg-white">
|
|
702
|
+
<img :src="getIconPath(summaryStage)" class="w-5 h-5 object-contain" alt="" />
|
|
703
|
+
</div>
|
|
704
|
+
<!-- Content -->
|
|
705
|
+
<div class="flex-1 min-w-0 pt-1">
|
|
706
|
+
<div class="text-[17px] font-bold uppercase tracking-tight mb-1.5 leading-none" style="color: var(--text-primary)">Summary</div>
|
|
707
|
+
<div class="flex items-center justify-between gap-x-4 text-[13px] font-mono leading-tight w-full" style="color: var(--text-muted)">
|
|
708
|
+
<span class="truncate max-w-[180px]" :title="summaryStage.model">{{ summaryStage.model }}</span>
|
|
709
|
+
|
|
710
|
+
<div class="flex items-center gap-4 shrink-0">
|
|
711
|
+
<div class="flex items-center gap-1.5 opacity-80">
|
|
712
|
+
<Icon icon="mdi:clock-outline" class="text-[13px]" />
|
|
713
|
+
<span>{{ summaryStage.time?.toFixed(2) }}s</span>
|
|
714
|
+
</div>
|
|
715
|
+
<template v-if="summaryStage.cost">
|
|
716
|
+
<div class="flex items-center gap-0.5 opacity-80">
|
|
717
|
+
<span>${{ summaryStage.cost.toFixed(5) }}</span>
|
|
718
|
+
</div>
|
|
719
|
+
</template>
|
|
720
|
+
</div>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
|
|
725
|
+
</div>
|
|
726
|
+
</div>
|
|
405
727
|
</div>
|
|
406
|
-
</div>
|
|
407
728
|
|
|
729
|
+
</div>
|
|
408
730
|
</div>
|
|
409
731
|
</div>
|
|
410
732
|
</template>
|
|
411
733
|
|
|
734
|
+
<style>
|
|
735
|
+
/* Global background fix to prevent white bottom strip */
|
|
736
|
+
:root, html, body {
|
|
737
|
+
background-color: #f2f2f2 !important;
|
|
738
|
+
margin: 0;
|
|
739
|
+
padding: 0;
|
|
740
|
+
overflow: hidden !important; /* Force hide scrollbars on root */
|
|
741
|
+
scrollbar-width: none; /* Firefox */
|
|
742
|
+
-ms-overflow-style: none; /* IE and Edge */
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/* Hide scrollbars for all elements */
|
|
746
|
+
*::-webkit-scrollbar {
|
|
747
|
+
display: none !important;
|
|
748
|
+
width: 0 !important;
|
|
749
|
+
height: 0 !important;
|
|
750
|
+
}
|
|
412
751
|
|
|
752
|
+
* {
|
|
753
|
+
scrollbar-width: none !important;
|
|
754
|
+
-ms-overflow-style: none !important;
|
|
755
|
+
}
|
|
756
|
+
</style>
|