entari-plugin-hyw 2.2.5__py3-none-any.whl → 3.5.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.
- entari_plugin_hyw/__init__.py +371 -315
- entari_plugin_hyw/assets/card-dist/index.html +396 -0
- entari_plugin_hyw/assets/card-dist/logos/anthropic.svg +1 -0
- entari_plugin_hyw/assets/card-dist/logos/cerebras.svg +9 -0
- entari_plugin_hyw/assets/card-dist/logos/deepseek.png +0 -0
- entari_plugin_hyw/assets/card-dist/logos/gemini.svg +1 -0
- entari_plugin_hyw/assets/card-dist/logos/google.svg +1 -0
- entari_plugin_hyw/assets/card-dist/logos/grok.png +0 -0
- entari_plugin_hyw/assets/card-dist/logos/huggingface.png +0 -0
- entari_plugin_hyw/assets/card-dist/logos/microsoft.svg +15 -0
- entari_plugin_hyw/assets/card-dist/logos/minimax.png +0 -0
- entari_plugin_hyw/assets/card-dist/logos/mistral.png +0 -0
- entari_plugin_hyw/assets/card-dist/logos/nvida.png +0 -0
- entari_plugin_hyw/assets/card-dist/logos/openai.svg +1 -0
- entari_plugin_hyw/assets/card-dist/logos/openrouter.png +0 -0
- entari_plugin_hyw/assets/card-dist/logos/perplexity.svg +24 -0
- entari_plugin_hyw/assets/card-dist/logos/qwen.png +0 -0
- entari_plugin_hyw/assets/card-dist/logos/xai.png +0 -0
- entari_plugin_hyw/assets/card-dist/logos/xiaomi.png +0 -0
- entari_plugin_hyw/assets/card-dist/logos/zai.png +0 -0
- entari_plugin_hyw/assets/card-dist/vite.svg +1 -0
- entari_plugin_hyw/assets/icon/anthropic.svg +1 -0
- entari_plugin_hyw/assets/icon/cerebras.svg +9 -0
- entari_plugin_hyw/assets/icon/deepseek.png +0 -0
- entari_plugin_hyw/assets/icon/gemini.svg +1 -0
- entari_plugin_hyw/assets/icon/google.svg +1 -0
- entari_plugin_hyw/assets/icon/grok.png +0 -0
- entari_plugin_hyw/assets/icon/huggingface.png +0 -0
- entari_plugin_hyw/assets/icon/microsoft.svg +15 -0
- entari_plugin_hyw/assets/icon/minimax.png +0 -0
- entari_plugin_hyw/assets/icon/mistral.png +0 -0
- entari_plugin_hyw/assets/icon/nvida.png +0 -0
- entari_plugin_hyw/assets/icon/openai.svg +1 -0
- entari_plugin_hyw/assets/icon/openrouter.png +0 -0
- entari_plugin_hyw/assets/icon/perplexity.svg +24 -0
- entari_plugin_hyw/assets/icon/qwen.png +0 -0
- entari_plugin_hyw/assets/icon/xai.png +0 -0
- entari_plugin_hyw/assets/icon/xiaomi.png +0 -0
- entari_plugin_hyw/assets/icon/zai.png +0 -0
- entari_plugin_hyw/card-ui/.gitignore +24 -0
- entari_plugin_hyw/card-ui/README.md +5 -0
- entari_plugin_hyw/card-ui/index.html +16 -0
- entari_plugin_hyw/card-ui/package-lock.json +2342 -0
- entari_plugin_hyw/card-ui/package.json +31 -0
- entari_plugin_hyw/card-ui/public/logos/anthropic.svg +1 -0
- entari_plugin_hyw/card-ui/public/logos/cerebras.svg +9 -0
- entari_plugin_hyw/card-ui/public/logos/deepseek.png +0 -0
- entari_plugin_hyw/card-ui/public/logos/gemini.svg +1 -0
- entari_plugin_hyw/card-ui/public/logos/google.svg +1 -0
- entari_plugin_hyw/card-ui/public/logos/grok.png +0 -0
- entari_plugin_hyw/card-ui/public/logos/huggingface.png +0 -0
- entari_plugin_hyw/card-ui/public/logos/microsoft.svg +15 -0
- entari_plugin_hyw/card-ui/public/logos/minimax.png +0 -0
- entari_plugin_hyw/card-ui/public/logos/mistral.png +0 -0
- entari_plugin_hyw/card-ui/public/logos/nvida.png +0 -0
- entari_plugin_hyw/card-ui/public/logos/openai.svg +1 -0
- entari_plugin_hyw/card-ui/public/logos/openrouter.png +0 -0
- entari_plugin_hyw/card-ui/public/logos/perplexity.svg +24 -0
- entari_plugin_hyw/card-ui/public/logos/qwen.png +0 -0
- entari_plugin_hyw/card-ui/public/logos/xai.png +0 -0
- entari_plugin_hyw/card-ui/public/logos/xiaomi.png +0 -0
- entari_plugin_hyw/card-ui/public/logos/zai.png +0 -0
- entari_plugin_hyw/card-ui/public/vite.svg +1 -0
- entari_plugin_hyw/card-ui/src/App.vue +412 -0
- entari_plugin_hyw/card-ui/src/assets/vue.svg +1 -0
- entari_plugin_hyw/card-ui/src/components/HelloWorld.vue +41 -0
- entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +386 -0
- entari_plugin_hyw/card-ui/src/components/SectionCard.vue +41 -0
- entari_plugin_hyw/card-ui/src/components/StageCard.vue +237 -0
- entari_plugin_hyw/card-ui/src/main.ts +5 -0
- entari_plugin_hyw/card-ui/src/style.css +29 -0
- entari_plugin_hyw/card-ui/src/test_regex.js +103 -0
- entari_plugin_hyw/card-ui/src/types.ts +52 -0
- entari_plugin_hyw/card-ui/tsconfig.app.json +16 -0
- entari_plugin_hyw/card-ui/tsconfig.json +7 -0
- entari_plugin_hyw/card-ui/tsconfig.node.json +26 -0
- entari_plugin_hyw/card-ui/vite.config.ts +16 -0
- entari_plugin_hyw/history.py +170 -0
- entari_plugin_hyw/image_cache.py +274 -0
- entari_plugin_hyw/misc.py +128 -0
- entari_plugin_hyw/pipeline.py +1338 -0
- entari_plugin_hyw/prompts.py +108 -0
- entari_plugin_hyw/render_vue.py +314 -0
- entari_plugin_hyw/search.py +696 -0
- entari_plugin_hyw-3.5.0rc6.dist-info/METADATA +116 -0
- entari_plugin_hyw-3.5.0rc6.dist-info/RECORD +88 -0
- entari_plugin_hyw/hyw_core.py +0 -555
- entari_plugin_hyw-2.2.5.dist-info/METADATA +0 -135
- entari_plugin_hyw-2.2.5.dist-info/RECORD +0 -6
- {entari_plugin_hyw-2.2.5.dist-info → entari_plugin_hyw-3.5.0rc6.dist-info}/WHEEL +0 -0
- {entari_plugin_hyw-2.2.5.dist-info → entari_plugin_hyw-3.5.0rc6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { marked, type Tokens } from 'marked'
|
|
5
|
+
import katex from 'katex'
|
|
6
|
+
import 'katex/dist/katex.min.css'
|
|
7
|
+
import hljs from 'highlight.js/lib/core'
|
|
8
|
+
// Import only common languages to reduce bundle size
|
|
9
|
+
import python from 'highlight.js/lib/languages/python'
|
|
10
|
+
import javascript from 'highlight.js/lib/languages/javascript'
|
|
11
|
+
import typescript from 'highlight.js/lib/languages/typescript'
|
|
12
|
+
import json from 'highlight.js/lib/languages/json'
|
|
13
|
+
import bash from 'highlight.js/lib/languages/bash'
|
|
14
|
+
import css from 'highlight.js/lib/languages/css'
|
|
15
|
+
import xml from 'highlight.js/lib/languages/xml'
|
|
16
|
+
import java from 'highlight.js/lib/languages/java'
|
|
17
|
+
import cpp from 'highlight.js/lib/languages/cpp'
|
|
18
|
+
import go from 'highlight.js/lib/languages/go'
|
|
19
|
+
import rust from 'highlight.js/lib/languages/rust'
|
|
20
|
+
import sql from 'highlight.js/lib/languages/sql'
|
|
21
|
+
import markdown from 'highlight.js/lib/languages/markdown'
|
|
22
|
+
import shell from 'highlight.js/lib/languages/shell'
|
|
23
|
+
import yaml from 'highlight.js/lib/languages/yaml'
|
|
24
|
+
import properties from 'highlight.js/lib/languages/properties'
|
|
25
|
+
|
|
26
|
+
hljs.registerLanguage('python', python)
|
|
27
|
+
hljs.registerLanguage('javascript', javascript)
|
|
28
|
+
hljs.registerLanguage('js', javascript)
|
|
29
|
+
hljs.registerLanguage('typescript', typescript)
|
|
30
|
+
hljs.registerLanguage('ts', typescript)
|
|
31
|
+
hljs.registerLanguage('json', json)
|
|
32
|
+
hljs.registerLanguage('bash', bash)
|
|
33
|
+
hljs.registerLanguage('sh', bash)
|
|
34
|
+
hljs.registerLanguage('shell', shell)
|
|
35
|
+
hljs.registerLanguage('zsh', bash)
|
|
36
|
+
hljs.registerLanguage('css', css)
|
|
37
|
+
hljs.registerLanguage('html', xml)
|
|
38
|
+
hljs.registerLanguage('xml', xml)
|
|
39
|
+
hljs.registerLanguage('java', java)
|
|
40
|
+
hljs.registerLanguage('cpp', cpp)
|
|
41
|
+
hljs.registerLanguage('c', cpp)
|
|
42
|
+
hljs.registerLanguage('go', go)
|
|
43
|
+
hljs.registerLanguage('rust', rust)
|
|
44
|
+
hljs.registerLanguage('sql', sql)
|
|
45
|
+
hljs.registerLanguage('markdown', markdown)
|
|
46
|
+
hljs.registerLanguage('md', markdown)
|
|
47
|
+
hljs.registerLanguage('yaml', yaml)
|
|
48
|
+
hljs.registerLanguage('yml', yaml)
|
|
49
|
+
hljs.registerLanguage('properties', properties)
|
|
50
|
+
hljs.registerLanguage('ini', properties)
|
|
51
|
+
hljs.registerLanguage('conf', properties)
|
|
52
|
+
|
|
53
|
+
import 'highlight.js/styles/github.css'
|
|
54
|
+
|
|
55
|
+
const props = defineProps<{
|
|
56
|
+
markdown: string
|
|
57
|
+
numSearchRefs?: number
|
|
58
|
+
numPageRefs?: number
|
|
59
|
+
bare?: boolean // When true, tables and code blocks render without window decoration
|
|
60
|
+
}>()
|
|
61
|
+
|
|
62
|
+
// Configure marked with syntax highlighting
|
|
63
|
+
marked.setOptions({
|
|
64
|
+
breaks: true,
|
|
65
|
+
gfm: true,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// Custom renderer for code blocks with technical layout
|
|
69
|
+
const renderer = new marked.Renderer()
|
|
70
|
+
renderer.code = ({ text, lang }: Tokens.Code): string => {
|
|
71
|
+
const language = lang || 'text'
|
|
72
|
+
let highlighted = ''
|
|
73
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
74
|
+
try {
|
|
75
|
+
highlighted = hljs.highlight(text, { language: lang }).value
|
|
76
|
+
} catch {
|
|
77
|
+
highlighted = hljs.highlightAuto(text).value
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
highlighted = hljs.highlightAuto(text).value
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Add line numbers to code
|
|
84
|
+
const addLineNumbers = (code: string): string => {
|
|
85
|
+
const lines = code.split('\n')
|
|
86
|
+
return lines.map((line, i) =>
|
|
87
|
+
`<span class="code-line"><span class="line-number">${i + 1}</span><span class="line-content">${line}</span></span>`
|
|
88
|
+
).join('')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const highlightedWithLines = addLineNumbers(highlighted)
|
|
92
|
+
|
|
93
|
+
// Bare mode: just the code, no window decoration
|
|
94
|
+
if (props.bare) {
|
|
95
|
+
return `<pre class="!mt-0 !mb-0 !rounded-none !bg-gray-50 !p-0 border-b border-gray-100 code-with-lines"><code class="hljs language-${language} text-[17.5px] leading-snug font-mono">${highlightedWithLines}</code></pre>`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Dynamic Icon mapping
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
return `
|
|
102
|
+
<div class="my-6 space-y-1 group">
|
|
103
|
+
<div class="h-4 w-24 ml-auto" style="background-color: var(--theme-color);"></div>
|
|
104
|
+
<div class="">
|
|
105
|
+
<pre class="!mt-0 !mb-0 !rounded-none !bg-white !p-0 code-with-lines"><code class="hljs language-${language} text-[17.5px] leading-snug font-mono">${highlightedWithLines}</code></pre>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
`
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
marked.use({ renderer })
|
|
112
|
+
|
|
113
|
+
// Render LaTeX math with KaTeX
|
|
114
|
+
function renderMath(tex: string, displayMode: boolean): string {
|
|
115
|
+
try {
|
|
116
|
+
return katex.renderToString(tex, {
|
|
117
|
+
displayMode,
|
|
118
|
+
throwOnError: false,
|
|
119
|
+
strict: false,
|
|
120
|
+
})
|
|
121
|
+
} catch {
|
|
122
|
+
return `<code>${tex}</code>`
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Process markdown and convert citations to badges
|
|
127
|
+
const processedHtml = computed(() => {
|
|
128
|
+
let md = props.markdown || ''
|
|
129
|
+
|
|
130
|
+
// Remove References section at end
|
|
131
|
+
md = md.replace(/(?:^|\n)\s*(?:#{1,3}|\*\*)\s*(?:References|Citations|Sources)[\s\S]*$/i, '')
|
|
132
|
+
|
|
133
|
+
// Protect math blocks from markdown parsing by replacing with placeholders
|
|
134
|
+
const mathBlocks: { placeholder: string; html: string }[] = []
|
|
135
|
+
let mathIndex = 0
|
|
136
|
+
|
|
137
|
+
// Block math: $$...$$ or \[...\]
|
|
138
|
+
md = md.replace(/\$\$([\s\S]+?)\$\$|\\\[([\s\S]+?)\\\]/g, (_, tex1, tex2) => {
|
|
139
|
+
const tex = tex1 || tex2
|
|
140
|
+
const placeholder = `%%MATH_BLOCK_${mathIndex++}%%`
|
|
141
|
+
mathBlocks.push({ placeholder, html: `<div class="my-4 overflow-x-auto">${renderMath(tex.trim(), true)}</div>` })
|
|
142
|
+
return placeholder
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Inline math: $...$ or \(...\) (but not $$)
|
|
146
|
+
md = md.replace(/\$([^\$\n]+?)\$|\\\((.+?)\\\)/g, (_, tex1, tex2) => {
|
|
147
|
+
const tex = tex1 || tex2
|
|
148
|
+
const placeholder = `%%MATH_INLINE_${mathIndex++}%%`
|
|
149
|
+
mathBlocks.push({ placeholder, html: renderMath(tex.trim(), false) })
|
|
150
|
+
return placeholder
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Convert markdown to HTML
|
|
154
|
+
let html = marked.parse(md) as string
|
|
155
|
+
|
|
156
|
+
// Restore math blocks
|
|
157
|
+
for (const { placeholder, html: mathHtml } of mathBlocks) {
|
|
158
|
+
html = html.replace(placeholder, mathHtml)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Render <summary> tags as technical highlight blocks
|
|
162
|
+
html = html.replace(/<summary>([\s\S]*?)<\/summary>/g, (_, content) => {
|
|
163
|
+
return `
|
|
164
|
+
<div class="my-8 group shadow-sm shadow-black/10">
|
|
165
|
+
<div class="h-4 w-full" style="background-color: var(--theme-color);"></div>
|
|
166
|
+
<div class="p-6 text-[19px] leading-relaxed font-medium bg-white" style="color: var(--text-body)">
|
|
167
|
+
${content}
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
`
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// Wrap tables in crisp technical borders
|
|
174
|
+
html = html.replace(/<table[^>]*>([\s\S]*?)<\/table>/g, (_, content) => {
|
|
175
|
+
// Parse table content to simple structure
|
|
176
|
+
const rows = content.match(/<tr[^>]*>[\s\S]*?<\/tr>/g) || []
|
|
177
|
+
|
|
178
|
+
// Extract headers
|
|
179
|
+
const headerRow = rows[0] || ''
|
|
180
|
+
const headers = (headerRow.match(/<th[^>]*>([\s\S]*?)<\/th>/g) || []).map((h: string) => {
|
|
181
|
+
const alignMatch = h.match(/align="([^"]*)"/)
|
|
182
|
+
const align = alignMatch ? alignMatch[1] : 'left'
|
|
183
|
+
const text = h.replace(/<[^>]+>/g, '')
|
|
184
|
+
return { text, align }
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// Extract body rows
|
|
188
|
+
const bodyRows = rows.slice(1).map((row: string) => {
|
|
189
|
+
return (row.match(/<td[^>]*>([\s\S]*?)<\/td>/g) || []).map((c: string, i: number) => {
|
|
190
|
+
const alignMatch = c.match(/align="([^"]*)"/)
|
|
191
|
+
const align = alignMatch ? alignMatch[1] : (headers[i]?.align || 'left')
|
|
192
|
+
const innerHtml = c.replace(/^<td[^>]*>|<\/td>$/g, '')
|
|
193
|
+
return { html: innerHtml, align }
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
const containerClass = "w-full bg-white text-[16.5px] select-text";
|
|
198
|
+
|
|
199
|
+
let gridHtml = `<div class="${containerClass}">`
|
|
200
|
+
|
|
201
|
+
const allRows: any[] = [headers.map((h: any) => ({ html: h.text, align: h.align })), ...bodyRows];
|
|
202
|
+
|
|
203
|
+
allRows.forEach((row: any[], rowIndex: number) => {
|
|
204
|
+
const isHeader = rowIndex === 0;
|
|
205
|
+
const rowBg = isHeader
|
|
206
|
+
? 'bg-white text-gray-800 font-black uppercase tracking-tight'
|
|
207
|
+
: (rowIndex % 2 === 0 ? 'bg-white' : 'bg-gray-50/30');
|
|
208
|
+
const borderB = rowIndex < allRows.length - 1 ? 'border-b border-gray-200' : '';
|
|
209
|
+
|
|
210
|
+
gridHtml += `<div class="flex w-full ${rowBg} ${borderB}">`;
|
|
211
|
+
|
|
212
|
+
row.forEach((cell: any, colIndex: number) => {
|
|
213
|
+
const justify = cell.align === 'center' ? 'justify-center text-center' : (cell.align === 'right' ? 'justify-end text-right' : 'justify-start');
|
|
214
|
+
const borderClass = colIndex === row.length - 1 ? '' : 'border-r border-gray-100';
|
|
215
|
+
|
|
216
|
+
gridHtml += `<div class="flex-1 py-3.5 px-4 min-w-0 break-words flex items-center leading-tight ${justify} ${borderClass}">
|
|
217
|
+
<span>${cell.html}</span>
|
|
218
|
+
</div>`;
|
|
219
|
+
});
|
|
220
|
+
gridHtml += `</div>`;
|
|
221
|
+
});
|
|
222
|
+
gridHtml += `</div>`;
|
|
223
|
+
|
|
224
|
+
if (props.bare) {
|
|
225
|
+
return `<div class="border-b border-gray-200">${gridHtml}</div>`
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return `
|
|
229
|
+
<div class="my-6 group">
|
|
230
|
+
<div class="bg-white p-0 border-t border-gray-100">
|
|
231
|
+
${gridHtml}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
`
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// Convert [N] citations to small square badges with shadow
|
|
238
|
+
html = html.replace(/(\s*)\[(\d+)\]/g, (_, _space, n) => {
|
|
239
|
+
const num = parseInt(n)
|
|
240
|
+
return `<sup class="inline-flex items-center justify-center w-[15px] h-[15px] text-[10px] font-bold cursor-default select-none ml-0.5 mr-0 align-middle" style="background-color: var(--theme-color); color: var(--header-text-color); box-shadow: 0 1px 2px 0 rgba(0,0,0,0.15)">${num}</sup>`
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// Style <u> underline tags with theme-colored solid underline
|
|
244
|
+
html = html.replace(/<u>([^<]*)<\/u>/g, (_, content) => {
|
|
245
|
+
return `<span class="underline decoration-[3px] underline-offset-[6px]" style="text-decoration-color: var(--theme-color)">${content}</span>`
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
return html
|
|
249
|
+
})
|
|
250
|
+
</script>
|
|
251
|
+
|
|
252
|
+
<template>
|
|
253
|
+
<div ref="contentRef"
|
|
254
|
+
class="prose prose-slate max-w-none prose-lg
|
|
255
|
+
prose-headings:font-bold prose-headings:mb-3 prose-headings:mt-8 prose-headings:tracking-tight
|
|
256
|
+
prose-p:leading-7 prose-p:my-4 prose-p:text-[20px] prose-li:text-[20px]
|
|
257
|
+
prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline
|
|
258
|
+
prose-code:bg-gray-100 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-none prose-code:text-[0.85em] prose-code:font-mono
|
|
259
|
+
prose-pre:bg-gray-50 prose-pre:border prose-pre:border-gray-200 prose-pre:rounded-none prose-pre:p-0
|
|
260
|
+
prose-img:rounded-none prose-img:my-6 prose-img:max-h-[400px] prose-img:w-auto prose-img:object-contain prose-img:border prose-img:border-gray-200
|
|
261
|
+
prose-ol:list-decimal prose-ol:pl-7 prose-ol:list-outside prose-ol:my-5
|
|
262
|
+
prose-li:my-2.5 prose-li:leading-7
|
|
263
|
+
[&>*:first-child]:!mt-0"
|
|
264
|
+
style="--prose-headings: var(--text-primary); --prose-body: var(--text-body); --prose-bold: var(--text-primary); --prose-code: var(--text-body)"
|
|
265
|
+
v-html="processedHtml">
|
|
266
|
+
</div>
|
|
267
|
+
</template>
|
|
268
|
+
|
|
269
|
+
<style>
|
|
270
|
+
/* Highlight.js theme - minimal */
|
|
271
|
+
.hljs {
|
|
272
|
+
background: transparent !important;
|
|
273
|
+
padding: 0 !important;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Custom List Styling - Premium technical bullet */
|
|
277
|
+
.prose ul {
|
|
278
|
+
list-style: none !important;
|
|
279
|
+
padding-left: 0.25rem !important;
|
|
280
|
+
margin-top: 1rem !important;
|
|
281
|
+
margin-bottom: 1rem !important;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.prose ul > li {
|
|
285
|
+
position: relative !important;
|
|
286
|
+
padding-left: 1.5rem !important;
|
|
287
|
+
margin-top: 0.75rem !important;
|
|
288
|
+
margin-bottom: 0.75rem !important;
|
|
289
|
+
line-height: 1.6 !important;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.prose ul > li::before {
|
|
293
|
+
content: "" !important;
|
|
294
|
+
position: absolute !important;
|
|
295
|
+
left: 0 !important;
|
|
296
|
+
top: 0.6em !important;
|
|
297
|
+
width: 8px !important;
|
|
298
|
+
height: 8px !important;
|
|
299
|
+
background-color: var(--theme-color, #ef4444) !important; /* Theme color */
|
|
300
|
+
border-radius: 0 !important;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* Nested list styling */
|
|
304
|
+
.prose ul ul {
|
|
305
|
+
margin-top: 0.25rem !important;
|
|
306
|
+
margin-bottom: 0.25rem !important;
|
|
307
|
+
padding-left: 1rem !important;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.prose ul ul > li {
|
|
311
|
+
padding-left: 1.25rem !important;
|
|
312
|
+
margin-top: 0.25rem !important;
|
|
313
|
+
margin-bottom: 0.25rem !important;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.prose ul ul > li::before {
|
|
317
|
+
width: 6px !important;
|
|
318
|
+
height: 6px !important;
|
|
319
|
+
background-color: var(--theme-color, #ef4444) !important; /* Theme color - same as parent, slightly smaller */
|
|
320
|
+
top: 0.65em !important;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/* Custom Blockquote Styling - Dual Red Lines */
|
|
324
|
+
.prose blockquote {
|
|
325
|
+
border-left: none !important;
|
|
326
|
+
padding-left: 1rem !important;
|
|
327
|
+
margin-left: 0 !important;
|
|
328
|
+
position: relative !important;
|
|
329
|
+
font-style: italic !important;
|
|
330
|
+
color: var(--text-body, #3a3a3c) !important; /* Premium reading color */
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.prose blockquote::before {
|
|
334
|
+
content: "" !important;
|
|
335
|
+
position: absolute !important;
|
|
336
|
+
left: 0 !important;
|
|
337
|
+
top: 0 !important;
|
|
338
|
+
bottom: 0 !important;
|
|
339
|
+
width: 5px !important;
|
|
340
|
+
background-color: var(--theme-color, #ef4444) !important; /* Theme color - thick line */
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
/* Ensure images don't have artifacts */
|
|
346
|
+
.prose img {
|
|
347
|
+
display: block;
|
|
348
|
+
margin-left: 0;
|
|
349
|
+
margin-right: auto;
|
|
350
|
+
}
|
|
351
|
+
.prose pre {
|
|
352
|
+
border: none !important;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* Code line numbers - Modern minimalist style */
|
|
356
|
+
.code-with-lines code {
|
|
357
|
+
display: block;
|
|
358
|
+
padding: 1.25em 0;
|
|
359
|
+
background: white;
|
|
360
|
+
}
|
|
361
|
+
.code-with-lines .code-line {
|
|
362
|
+
display: flex;
|
|
363
|
+
align-items: stretch;
|
|
364
|
+
}
|
|
365
|
+
.code-with-lines .line-number {
|
|
366
|
+
flex-shrink: 0;
|
|
367
|
+
width: 36px;
|
|
368
|
+
padding: 0.1em 8px 0.1em 4px;
|
|
369
|
+
text-align: right;
|
|
370
|
+
color: var(--text-muted, #9ca3af);
|
|
371
|
+
background: white;
|
|
372
|
+
border-right: 1px solid #e5e7eb;
|
|
373
|
+
user-select: none;
|
|
374
|
+
font-size: 11px;
|
|
375
|
+
display: flex;
|
|
376
|
+
align-items: flex-start;
|
|
377
|
+
justify-content: flex-end;
|
|
378
|
+
}
|
|
379
|
+
.code-with-lines .line-content {
|
|
380
|
+
flex: 1;
|
|
381
|
+
padding: 0.1em 1.25em 0.1em 1em;
|
|
382
|
+
white-space: pre-wrap;
|
|
383
|
+
word-break: break-all;
|
|
384
|
+
background: white;
|
|
385
|
+
}
|
|
386
|
+
</style>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import MarkdownContent from './MarkdownContent.vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
title: string
|
|
6
|
+
content: string
|
|
7
|
+
level: number
|
|
8
|
+
numSearchRefs?: number
|
|
9
|
+
numPageRefs?: number
|
|
10
|
+
index: number
|
|
11
|
+
}>()
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div class="mb-5">
|
|
16
|
+
<!-- Normal Text Section: macOS Window Style -->
|
|
17
|
+
<div class="rounded-none overflow-hidden border border-gray-300 bg-white/70 backdrop-blur-xl">
|
|
18
|
+
<!-- Section Window Header with traffic lights + title on right -->
|
|
19
|
+
<div class="flex items-center justify-between px-3 py-2 bg-gray-100/80 backdrop-blur-lg border-b border-gray-200/40">
|
|
20
|
+
<div class="flex items-center gap-1.5">
|
|
21
|
+
<!-- macOS Traffic Lights -->
|
|
22
|
+
<div class="flex gap-1.5 mr-2">
|
|
23
|
+
<div class="w-2.5 h-2.5 rounded-full bg-[#ff5f56] shadow-md"></div>
|
|
24
|
+
<div class="w-2.5 h-2.5 rounded-full bg-[#ffbd2e] shadow-md"></div>
|
|
25
|
+
<div class="w-2.5 h-2.5 rounded-full bg-[#27c93f] shadow-md"></div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
<span class="text-[11px] font-mono text-gray-700/70 uppercase font-bold tracking-wider">{{ title }}</span>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Section Content -->
|
|
32
|
+
<div class="p-5 bg-white/40 backdrop-blur-sm">
|
|
33
|
+
<MarkdownContent
|
|
34
|
+
:markdown="content"
|
|
35
|
+
:num-search-refs="numSearchRefs"
|
|
36
|
+
:num-page-refs="numPageRefs"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import { Icon } from '@iconify/vue'
|
|
4
|
+
import type { Stage } from '../types'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
stage: Stage
|
|
8
|
+
isFirst?: boolean
|
|
9
|
+
isLast?: boolean
|
|
10
|
+
prevStageName?: string
|
|
11
|
+
refOffset?: number
|
|
12
|
+
}>()
|
|
13
|
+
|
|
14
|
+
const failedImages = ref<Record<string, boolean>>({})
|
|
15
|
+
const imageHeights = ref<Record<string, number>>({})
|
|
16
|
+
|
|
17
|
+
function handleImageError(url: string) {
|
|
18
|
+
failedImages.value[url] = true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function handleImageLoad(url: string, event: Event) {
|
|
22
|
+
const img = event.target as HTMLImageElement
|
|
23
|
+
if (img.naturalWidth && img.naturalHeight) {
|
|
24
|
+
// Store aspect ratio as height per unit width
|
|
25
|
+
imageHeights.value[url] = img.naturalHeight / img.naturalWidth
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Compute two columns for masonry layout
|
|
30
|
+
const imageColumns = computed(() => {
|
|
31
|
+
const images = props.stage.image_references || []
|
|
32
|
+
const leftColumn: typeof images = []
|
|
33
|
+
const rightColumn: typeof images = []
|
|
34
|
+
let leftHeight = 0
|
|
35
|
+
let rightHeight = 0
|
|
36
|
+
|
|
37
|
+
for (const img of images) {
|
|
38
|
+
if (failedImages.value[img.url]) continue
|
|
39
|
+
|
|
40
|
+
// Get aspect ratio (default to 1 if not loaded yet)
|
|
41
|
+
const aspectRatio = imageHeights.value[img.url] || 1
|
|
42
|
+
|
|
43
|
+
// Add to shorter column
|
|
44
|
+
if (leftHeight <= rightHeight) {
|
|
45
|
+
leftColumn.push(img)
|
|
46
|
+
leftHeight += aspectRatio
|
|
47
|
+
} else {
|
|
48
|
+
rightColumn.push(img)
|
|
49
|
+
rightHeight += aspectRatio
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { leftColumn, rightColumn }
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
function getDomain(url: string): string {
|
|
61
|
+
try {
|
|
62
|
+
const urlObj = new URL(url)
|
|
63
|
+
const hostname = urlObj.hostname.replace('www.', '')
|
|
64
|
+
const pathname = urlObj.pathname === '/' ? '' : urlObj.pathname
|
|
65
|
+
return hostname + pathname
|
|
66
|
+
} catch {
|
|
67
|
+
return url
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getFavicon(url: string): string {
|
|
72
|
+
const domain = getDomain(url)
|
|
73
|
+
return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function formatTime(seconds: number): string {
|
|
77
|
+
return `${seconds.toFixed(2)}s`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatCost(dollars: number): string {
|
|
81
|
+
return dollars > 0 ? `$${dollars.toFixed(6)}` : '$0'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getModelShort(model: string): string {
|
|
85
|
+
const short = model.includes('/') ? model.split('/').pop() || model : model
|
|
86
|
+
return short.length > 25 ? short.slice(0, 23) + '…' : short
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getStageTheme(name?: string) {
|
|
90
|
+
if (!name) return themes['default']
|
|
91
|
+
const key = name.toLowerCase()
|
|
92
|
+
|
|
93
|
+
if (key.includes('search')) return themes['search']
|
|
94
|
+
if (key.includes('crawl') || key.includes('page')) return themes['crawler']
|
|
95
|
+
if (key.includes('agent')) return themes['agent']
|
|
96
|
+
if (key.includes('instruct')) return themes['instruct']
|
|
97
|
+
if (key.includes('vision')) return themes['vision']
|
|
98
|
+
|
|
99
|
+
return themes['default']
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const themes: Record<string, any> = {
|
|
103
|
+
'search': { color: 'text-blue-600', bg: 'bg-blue-50', iconBg: 'bg-blue-100/50', icon: 'mdi:magnify' },
|
|
104
|
+
'crawler': { color: 'text-orange-600', bg: 'bg-orange-50', iconBg: 'bg-orange-100/50', icon: 'mdi:web' },
|
|
105
|
+
'agent': { color: 'text-purple-600', bg: 'bg-purple-50', iconBg: 'bg-white/80', icon: 'mdi:robot' },
|
|
106
|
+
'instruct': { color: 'text-red-600', bg: 'bg-red-50', iconBg: 'bg-white/80', icon: 'mdi:lightning-bolt' },
|
|
107
|
+
'vision': { color: 'text-purple-600', bg: 'bg-purple-50', iconBg: 'bg-white/80', icon: 'mdi:eye' },
|
|
108
|
+
'default': { color: 'text-gray-600', bg: 'bg-gray-50', iconBg: 'bg-gray-100/50', icon: 'mdi:circle' }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getIcon(name: string): string {
|
|
112
|
+
const key = name.toLowerCase()
|
|
113
|
+
if (key.includes('search')) return 'mdi:magnify'
|
|
114
|
+
if (key.includes('crawl') || key.includes('page')) return 'mdi:web'
|
|
115
|
+
if (key.includes('agent')) return 'mdi:robot'
|
|
116
|
+
if (key.includes('instruct')) return 'mdi:lightning-bolt'
|
|
117
|
+
if (key.includes('vision')) return 'mdi:eye'
|
|
118
|
+
return 'mdi:circle'
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getModelLogo(model: string): string | undefined {
|
|
122
|
+
if (!model) return undefined
|
|
123
|
+
const m = model.toLowerCase()
|
|
124
|
+
if (m.includes('openai') || m.includes('gpt')) return 'logos/openai.svg'
|
|
125
|
+
if (m.includes('claude') || m.includes('anthropic')) return 'logos/anthropic.svg'
|
|
126
|
+
if (m.includes('gemini') || m.includes('google')) return 'logos/google.svg'
|
|
127
|
+
if (m.includes('deepseek')) return 'logos/deepseek.png'
|
|
128
|
+
if (m.includes('huggingface')) return 'logos/huggingface.png'
|
|
129
|
+
if (m.includes('mistral')) return 'logos/mistral.png'
|
|
130
|
+
if (m.includes('perplexity')) return 'logos/perplexity.svg'
|
|
131
|
+
if (m.includes('cerebras')) return 'logos/cerebras.svg'
|
|
132
|
+
if (m.includes('grok')) return 'logos/grok.png'
|
|
133
|
+
if (m.includes('qwen')) return 'logos/qwen.png'
|
|
134
|
+
if (m.includes('minimax')) return 'logos/minimax.png'
|
|
135
|
+
if (m.includes('nvidia') || m.includes('nvida')) return 'logos/nvida.png'
|
|
136
|
+
if (m.includes('azure') || m.includes('microsoft')) return 'logos/microsoft.svg'
|
|
137
|
+
if (m.includes('xai')) return 'logos/xai.png'
|
|
138
|
+
if (m.includes('xiaomi')) return 'logos/xiaomi.png'
|
|
139
|
+
if (m.includes('zai')) return 'logos/zai.png'
|
|
140
|
+
return undefined
|
|
141
|
+
}
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<template>
|
|
145
|
+
<div class="relative">
|
|
146
|
+
<!-- Content -->
|
|
147
|
+
<div class="flex-1 min-w-0 pl-2">
|
|
148
|
+
<div class="rounded-none overflow-hidden bg-white">
|
|
149
|
+
|
|
150
|
+
<!-- Header -->
|
|
151
|
+
<div :class="['bg-white px-4 py-2.5 flex items-center justify-between gap-3']">
|
|
152
|
+
<div class="flex items-center gap-3">
|
|
153
|
+
<div :class="['w-8 h-8 flex items-center justify-center shrink-0 overflow-hidden border border-gray-100', getStageTheme(stage.name).iconBg, getStageTheme(stage.name).color]">
|
|
154
|
+
<img v-if="getModelLogo(stage.model)" :src="getModelLogo(stage.model)" class="w-5 h-5 object-contain" />
|
|
155
|
+
<Icon v-else :icon="getIcon(stage.name)" class="text-lg" />
|
|
156
|
+
</div>
|
|
157
|
+
<div class="flex flex-col">
|
|
158
|
+
<span class="font-black text-[18px] text-gray-800 uppercase tracking-tight">{{ stage.name }}</span>
|
|
159
|
+
<span class="text-[15.5px] font-mono tabular-nums tracking-tighter" style="color: var(--text-muted)">{{ getModelShort(stage.model) }}</span>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
<div v-if="stage.time > 0 || stage.cost > 0" class="text-[15.5px] font-mono flex items-center justify-end gap-2 leading-tight min-w-[120px]" style="color: var(--text-muted)">
|
|
163
|
+
<span v-if="stage.cost > 0">{{ formatCost(stage.cost) }}</span>
|
|
164
|
+
<span v-if="stage.time > 0 && stage.cost > 0" class="text-gray-300">·</span>
|
|
165
|
+
<span v-if="stage.time > 0">{{ formatTime(stage.time) }}</span>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
<div v-if="stage.references?.length || stage.image_references?.length || stage.crawled_pages?.length" class="bg-white pl-11 relative">
|
|
171
|
+
<div v-if="stage.references?.length" class="divide-y divide-gray-50 relative z-10">
|
|
172
|
+
<a v-for="(ref, idx) in stage.references" :key="idx"
|
|
173
|
+
:href="ref.url" target="_blank"
|
|
174
|
+
class="flex items-start gap-3 pr-3 py-3 hover:bg-gray-50 transition-colors group">
|
|
175
|
+
<!-- Favicon - Aligned with Title -->
|
|
176
|
+
<img :src="getFavicon(ref.url)" class="w-4 h-4 rounded-none shrink-0 object-contain mt-[4px]">
|
|
177
|
+
|
|
178
|
+
<!-- Content: Title and Domain -->
|
|
179
|
+
<div class="flex-1 min-w-0 flex flex-col">
|
|
180
|
+
<div class="flex items-center gap-2">
|
|
181
|
+
<span class="flex-1 text-[18px] font-bold text-gray-700 truncate leading-tight tracking-tight">{{ ref.title }}</span>
|
|
182
|
+
<!-- Square Badge with Shadow -->
|
|
183
|
+
<span class="shrink-0 w-[18px] h-[18px] text-[11px] font-bold flex items-center justify-center" style="background-color: var(--theme-color); color: var(--header-text-color); box-shadow: 0 1px 3px 0 rgba(0,0,0,0.15)">{{ (refOffset || 0) + idx + 1 }}</span>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="text-[15.5px] font-mono truncate mt-0.5 tracking-tighter" style="color: var(--text-muted)">{{ getDomain(ref.url) }}</div>
|
|
186
|
+
</div>
|
|
187
|
+
</a>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- Image Search Results - True Masonry Layout -->
|
|
191
|
+
<div v-if="stage.image_references?.length" class="pr-3 py-3 relative z-10">
|
|
192
|
+
<div class="flex gap-2">
|
|
193
|
+
<!-- Left Column -->
|
|
194
|
+
<div class="flex-1 flex flex-col gap-2">
|
|
195
|
+
<a v-for="(img, idx) in imageColumns.leftColumn" :key="`left-${img.url}-${idx}`"
|
|
196
|
+
:href="img.url" target="_blank"
|
|
197
|
+
class="relative overflow-hidden transition-all hover:opacity-90 group block">
|
|
198
|
+
<img :src="img.thumbnail || img.url"
|
|
199
|
+
@load="handleImageLoad(img.url, $event)"
|
|
200
|
+
@error="handleImageError(img.url)"
|
|
201
|
+
class="w-full h-auto block group-hover:scale-[1.02] transition-transform">
|
|
202
|
+
</a>
|
|
203
|
+
</div>
|
|
204
|
+
<!-- Right Column -->
|
|
205
|
+
<div class="flex-1 flex flex-col gap-2">
|
|
206
|
+
<a v-for="(img, idx) in imageColumns.rightColumn" :key="`right-${img.url}-${idx}`"
|
|
207
|
+
:href="img.url" target="_blank"
|
|
208
|
+
class="relative overflow-hidden transition-all hover:opacity-90 group block">
|
|
209
|
+
<img :src="img.thumbnail || img.url"
|
|
210
|
+
@load="handleImageLoad(img.url, $event)"
|
|
211
|
+
@error="handleImageError(img.url)"
|
|
212
|
+
class="w-full h-auto block group-hover:scale-[1.02] transition-transform">
|
|
213
|
+
</a>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<div v-if="stage.crawled_pages?.length" class="divide-y divide-gray-50 relative z-10">
|
|
219
|
+
<a v-for="(page, idx) in stage.crawled_pages" :key="idx"
|
|
220
|
+
:href="page.url" target="_blank"
|
|
221
|
+
class="flex items-start gap-3 pr-3 py-3 hover:bg-gray-50 transition-colors group">
|
|
222
|
+
<img :src="getFavicon(page.url)" class="w-4 h-4 rounded-none shrink-0 object-contain mt-[4px]">
|
|
223
|
+
<div class="flex-1 min-w-0 flex flex-col">
|
|
224
|
+
<div class="flex items-center gap-2">
|
|
225
|
+
<span class="flex-1 text-[18px] font-bold text-gray-700 truncate leading-tight tracking-tight">{{ page.title }}</span>
|
|
226
|
+
<!-- Square Badge with Shadow -->
|
|
227
|
+
<span class="shrink-0 w-[18px] h-[18px] text-[11px] font-bold flex items-center justify-center" style="background-color: var(--theme-color); color: var(--header-text-color); box-shadow: 0 1px 3px 0 rgba(0,0,0,0.15)">{{ (refOffset || 0) + (stage.references?.length || 0) + idx + 1 }}</span>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="text-[15.5px] font-mono truncate mt-0.5 tracking-tighter" style="color: var(--text-muted)">{{ getDomain(page.url) }}</div>
|
|
230
|
+
</div>
|
|
231
|
+
</a>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</template>
|