entari-plugin-hyw 3.4.2__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.
- entari_plugin_hyw/__init__.py +14 -89
- entari_plugin_hyw/assets/card-dist/index.html +135 -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/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 +216 -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 +330 -0
- entari_plugin_hyw/card-ui/src/components/SectionCard.vue +41 -0
- entari_plugin_hyw/card-ui/src/components/StageCard.vue +163 -0
- entari_plugin_hyw/card-ui/src/main.ts +5 -0
- entari_plugin_hyw/card-ui/src/style.css +8 -0
- entari_plugin_hyw/card-ui/src/types.ts +51 -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/core/config.py +0 -3
- entari_plugin_hyw/core/pipeline.py +136 -61
- entari_plugin_hyw/core/render_vue.py +255 -0
- entari_plugin_hyw/test_output/render_0.jpg +0 -0
- entari_plugin_hyw/test_output/render_1.jpg +0 -0
- entari_plugin_hyw/test_output/render_2.jpg +0 -0
- entari_plugin_hyw/test_output/render_3.jpg +0 -0
- entari_plugin_hyw/test_output/render_4.jpg +0 -0
- entari_plugin_hyw/tests/ui_test_output.jpg +0 -0
- entari_plugin_hyw/tests/verify_ui.py +139 -0
- entari_plugin_hyw/utils/misc.py +0 -3
- entari_plugin_hyw/utils/prompts.py +65 -63
- {entari_plugin_hyw-3.4.2.dist-info → entari_plugin_hyw-3.5.0rc1.dist-info}/METADATA +5 -2
- entari_plugin_hyw-3.5.0rc1.dist-info/RECORD +99 -0
- entari_plugin_hyw/assets/libs/highlight.css +0 -10
- entari_plugin_hyw/assets/libs/highlight.js +0 -1213
- entari_plugin_hyw/assets/libs/katex-auto-render.js +0 -1
- entari_plugin_hyw/assets/libs/katex.css +0 -1
- entari_plugin_hyw/assets/libs/katex.js +0 -1
- entari_plugin_hyw/assets/libs/tailwind.css +0 -1
- entari_plugin_hyw/assets/package-lock.json +0 -953
- entari_plugin_hyw/assets/package.json +0 -16
- entari_plugin_hyw/assets/tailwind.config.js +0 -12
- entari_plugin_hyw/assets/tailwind.input.css +0 -235
- entari_plugin_hyw/assets/template.html +0 -157
- entari_plugin_hyw/assets/template.html.bak +0 -157
- entari_plugin_hyw/assets/template.j2 +0 -400
- entari_plugin_hyw/core/render.py +0 -630
- entari_plugin_hyw/utils/prompts_cn.py +0 -119
- entari_plugin_hyw-3.4.2.dist-info/RECORD +0 -49
- {entari_plugin_hyw-3.4.2.dist-info → entari_plugin_hyw-3.5.0rc1.dist-info}/WHEEL +0 -0
- {entari_plugin_hyw-3.4.2.dist-info → entari_plugin_hyw-3.5.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { marked, type Tokens } from 'marked'
|
|
4
|
+
import hljs from 'highlight.js/lib/core'
|
|
5
|
+
// Import only common languages to reduce bundle size
|
|
6
|
+
import python from 'highlight.js/lib/languages/python'
|
|
7
|
+
import javascript from 'highlight.js/lib/languages/javascript'
|
|
8
|
+
import typescript from 'highlight.js/lib/languages/typescript'
|
|
9
|
+
import json from 'highlight.js/lib/languages/json'
|
|
10
|
+
import bash from 'highlight.js/lib/languages/bash'
|
|
11
|
+
import css from 'highlight.js/lib/languages/css'
|
|
12
|
+
import xml from 'highlight.js/lib/languages/xml'
|
|
13
|
+
import java from 'highlight.js/lib/languages/java'
|
|
14
|
+
import cpp from 'highlight.js/lib/languages/cpp'
|
|
15
|
+
import go from 'highlight.js/lib/languages/go'
|
|
16
|
+
import rust from 'highlight.js/lib/languages/rust'
|
|
17
|
+
import sql from 'highlight.js/lib/languages/sql'
|
|
18
|
+
import markdown from 'highlight.js/lib/languages/markdown'
|
|
19
|
+
import shell from 'highlight.js/lib/languages/shell'
|
|
20
|
+
import yaml from 'highlight.js/lib/languages/yaml'
|
|
21
|
+
import properties from 'highlight.js/lib/languages/properties'
|
|
22
|
+
|
|
23
|
+
hljs.registerLanguage('python', python)
|
|
24
|
+
hljs.registerLanguage('javascript', javascript)
|
|
25
|
+
hljs.registerLanguage('js', javascript)
|
|
26
|
+
hljs.registerLanguage('typescript', typescript)
|
|
27
|
+
hljs.registerLanguage('ts', typescript)
|
|
28
|
+
hljs.registerLanguage('json', json)
|
|
29
|
+
hljs.registerLanguage('bash', bash)
|
|
30
|
+
hljs.registerLanguage('sh', bash)
|
|
31
|
+
hljs.registerLanguage('shell', shell)
|
|
32
|
+
hljs.registerLanguage('zsh', bash)
|
|
33
|
+
hljs.registerLanguage('css', css)
|
|
34
|
+
hljs.registerLanguage('html', xml)
|
|
35
|
+
hljs.registerLanguage('xml', xml)
|
|
36
|
+
hljs.registerLanguage('java', java)
|
|
37
|
+
hljs.registerLanguage('cpp', cpp)
|
|
38
|
+
hljs.registerLanguage('c', cpp)
|
|
39
|
+
hljs.registerLanguage('go', go)
|
|
40
|
+
hljs.registerLanguage('rust', rust)
|
|
41
|
+
hljs.registerLanguage('sql', sql)
|
|
42
|
+
hljs.registerLanguage('markdown', markdown)
|
|
43
|
+
hljs.registerLanguage('md', markdown)
|
|
44
|
+
hljs.registerLanguage('yaml', yaml)
|
|
45
|
+
hljs.registerLanguage('yml', yaml)
|
|
46
|
+
hljs.registerLanguage('properties', properties)
|
|
47
|
+
hljs.registerLanguage('ini', properties)
|
|
48
|
+
hljs.registerLanguage('conf', properties)
|
|
49
|
+
|
|
50
|
+
import 'highlight.js/styles/github.css'
|
|
51
|
+
|
|
52
|
+
const props = defineProps<{
|
|
53
|
+
markdown: string
|
|
54
|
+
numSearchRefs?: number
|
|
55
|
+
numPageRefs?: number
|
|
56
|
+
bare?: boolean // When true, tables and code blocks render without window decoration
|
|
57
|
+
}>()
|
|
58
|
+
|
|
59
|
+
// Configure marked with syntax highlighting
|
|
60
|
+
marked.setOptions({
|
|
61
|
+
breaks: true,
|
|
62
|
+
gfm: true,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Custom renderer for code blocks with technical layout
|
|
66
|
+
const renderer = new marked.Renderer()
|
|
67
|
+
renderer.code = ({ text, lang }: Tokens.Code): string => {
|
|
68
|
+
const language = lang || 'text'
|
|
69
|
+
let highlighted = ''
|
|
70
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
71
|
+
try {
|
|
72
|
+
highlighted = hljs.highlight(text, { language: lang }).value
|
|
73
|
+
} catch {
|
|
74
|
+
highlighted = hljs.highlightAuto(text).value
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
highlighted = hljs.highlightAuto(text).value
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Bare mode: just the code, no window decoration
|
|
81
|
+
if (props.bare) {
|
|
82
|
+
return `<pre class="!mt-0 !mb-0 !rounded-none !bg-gray-50 !p-4 overflow-x-auto border-b border-gray-100"><code class="hljs language-${language} text-[13px] leading-relaxed font-mono">${highlighted}</code></pre>`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Dynamic Icon mapping
|
|
86
|
+
const getLangIcon = (l: string) => {
|
|
87
|
+
const map: Record<string, { icon: string, color: string }> = {
|
|
88
|
+
'python': { icon: 'mdi:language-python', color: 'text-blue-500' },
|
|
89
|
+
'javascript': { icon: 'mdi:language-javascript', color: 'text-yellow-500' },
|
|
90
|
+
'js': { icon: 'mdi:language-javascript', color: 'text-yellow-500' },
|
|
91
|
+
'typescript': { icon: 'mdi:language-typescript', color: 'text-blue-600' },
|
|
92
|
+
'ts': { icon: 'mdi:language-typescript', color: 'text-blue-600' },
|
|
93
|
+
'bash': { icon: 'mdi:terminal', color: 'text-green-500' },
|
|
94
|
+
'sh': { icon: 'mdi:terminal', color: 'text-green-500' },
|
|
95
|
+
'shell': { icon: 'mdi:terminal', color: 'text-green-500' },
|
|
96
|
+
'json': { icon: 'mdi:code-json', color: 'text-yellow-600' },
|
|
97
|
+
'html': { icon: 'mdi:language-html5', color: 'text-orange-500' },
|
|
98
|
+
'css': { icon: 'mdi:language-css3', color: 'text-blue-500' },
|
|
99
|
+
'yaml': { icon: 'mdi:file-cog', color: 'text-purple-500' },
|
|
100
|
+
'sql': { icon: 'mdi:database', color: 'text-red-500' }
|
|
101
|
+
}
|
|
102
|
+
return map[l] || { icon: 'mdi:code-braces', color: 'text-red-500' }
|
|
103
|
+
}
|
|
104
|
+
const langInfo = getLangIcon(language)
|
|
105
|
+
|
|
106
|
+
return `
|
|
107
|
+
<div class="my-6 space-y-1 group">
|
|
108
|
+
<div class="flex items-center justify-between px-3 py-1.5 bg-gray-50 ">
|
|
109
|
+
<div class="flex items-center gap-2">
|
|
110
|
+
<Icon icon="${langInfo.icon}" class="${langInfo.color} text-sm" />
|
|
111
|
+
<span class="text-[10px] font-black text-gray-700 uppercase tracking-widest">${language}</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="text-gray-500 text-[9px] font-mono tracking-tighter tabular-nums">
|
|
114
|
+
Source Code
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="">
|
|
118
|
+
<pre class="!mt-0 !mb-0 !rounded-none !bg-white !p-4 overflow-x-auto"><code class="hljs language-${language} text-[13px] leading-relaxed font-mono">${highlighted}</code></pre>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
`
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
marked.use({ renderer })
|
|
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
|
+
// Convert markdown to HTML
|
|
134
|
+
let html = marked.parse(md) as string
|
|
135
|
+
|
|
136
|
+
// Render <summary> tags as technical highlight blocks
|
|
137
|
+
html = html.replace(/<summary>([\s\S]*?)<\/summary>/g, (_, content) => {
|
|
138
|
+
return `
|
|
139
|
+
<div class="my-8 group">
|
|
140
|
+
<div class="flex items-center justify-between px-3 py-1.5 bg-gray-50 ">
|
|
141
|
+
<div class="flex items-center gap-2">
|
|
142
|
+
<Icon icon="mdi:lightning-bolt" class="text-red-500 text-sm" />
|
|
143
|
+
<span class="text-[10px] font-black text-gray-700 uppercase tracking-widest">Summary</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="text-gray-500 text-[9px] font-mono tracking-tighter tabular-nums">
|
|
146
|
+
Insight
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="p-5 text-[15px] leading-relaxed text-gray-800 font-medium bg-white ">
|
|
150
|
+
${content}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
`
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// Wrap tables in crisp technical borders
|
|
157
|
+
html = html.replace(/<table[^>]*>([\s\S]*?)<\/table>/g, (_, content) => {
|
|
158
|
+
// Parse table content to simple structure
|
|
159
|
+
const rows = content.match(/<tr[^>]*>[\s\S]*?<\/tr>/g) || []
|
|
160
|
+
|
|
161
|
+
// Extract headers
|
|
162
|
+
const headerRow = rows[0] || ''
|
|
163
|
+
const headers = (headerRow.match(/<th[^>]*>([\s\S]*?)<\/th>/g) || []).map((h: string) => {
|
|
164
|
+
const alignMatch = h.match(/align="([^"]*)"/)
|
|
165
|
+
const align = alignMatch ? alignMatch[1] : 'left'
|
|
166
|
+
const text = h.replace(/<[^>]+>/g, '')
|
|
167
|
+
return { text, align }
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// Extract body rows
|
|
171
|
+
const bodyRows = rows.slice(1).map((row: string) => {
|
|
172
|
+
return (row.match(/<td[^>]*>([\s\S]*?)<\/td>/g) || []).map((c: string, i: number) => {
|
|
173
|
+
const alignMatch = c.match(/align="([^"]*)"/)
|
|
174
|
+
const align = alignMatch ? alignMatch[1] : (headers[i]?.align || 'left')
|
|
175
|
+
const innerHtml = c.replace(/^<td[^>]*>|<\/td>$/g, '')
|
|
176
|
+
return { html: innerHtml, align }
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const containerClass = "w-full bg-white text-[12px] select-text";
|
|
181
|
+
|
|
182
|
+
let gridHtml = `<div class="${containerClass}">`
|
|
183
|
+
|
|
184
|
+
const allRows: any[] = [headers.map((h: any) => ({ html: h.text, align: h.align })), ...bodyRows];
|
|
185
|
+
|
|
186
|
+
allRows.forEach((row: any[], rowIndex: number) => {
|
|
187
|
+
const isHeader = rowIndex === 0;
|
|
188
|
+
const rowBg = isHeader
|
|
189
|
+
? 'bg-white text-gray-900 font-black uppercase tracking-tight'
|
|
190
|
+
: (rowIndex % 2 === 0 ? 'bg-white' : 'bg-gray-50/30');
|
|
191
|
+
const borderB = rowIndex < allRows.length - 1 ? 'border-b border-gray-200' : '';
|
|
192
|
+
|
|
193
|
+
gridHtml += `<div class="flex w-full ${rowBg} ${borderB}">`;
|
|
194
|
+
|
|
195
|
+
row.forEach((cell: any, colIndex: number) => {
|
|
196
|
+
const justify = cell.align === 'center' ? 'justify-center text-center' : (cell.align === 'right' ? 'justify-end text-right' : 'justify-start');
|
|
197
|
+
const borderClass = colIndex === row.length - 1 ? '' : 'border-r border-gray-100';
|
|
198
|
+
|
|
199
|
+
gridHtml += `<div class="flex-1 py-2.5 px-3 min-w-0 break-words flex items-center leading-tight ${justify} ${borderClass}">
|
|
200
|
+
<span>${cell.html}</span>
|
|
201
|
+
</div>`;
|
|
202
|
+
});
|
|
203
|
+
gridHtml += `</div>`;
|
|
204
|
+
});
|
|
205
|
+
gridHtml += `</div>`;
|
|
206
|
+
|
|
207
|
+
if (props.bare) {
|
|
208
|
+
return `<div class="overflow-x-auto border-b border-gray-200">${gridHtml}</div>`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return `
|
|
212
|
+
<div class="my-6 group">
|
|
213
|
+
<div class="overflow-x-auto bg-white p-0 border-t border-gray-100">
|
|
214
|
+
${gridHtml}
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
`
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
// Convert [N] citations to rectangular sharp badges
|
|
221
|
+
html = html.replace(/\[(\d+)\]/g, (_, n) => {
|
|
222
|
+
const num = parseInt(n)
|
|
223
|
+
return `<span class="relative -top-1.5 text-[10px] font-bold text-blue-600 mx-0.5 cursor-default select-none transition-all">${num}</span>`
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
return html
|
|
227
|
+
})
|
|
228
|
+
</script>
|
|
229
|
+
|
|
230
|
+
<template>
|
|
231
|
+
<div ref="contentRef"
|
|
232
|
+
class="prose prose-slate max-w-none
|
|
233
|
+
prose-headings:text-gray-900 prose-headings:font-black prose-headings:mb-2 prose-headings:mt-6 prose-headings:tracking-tight
|
|
234
|
+
prose-p:text-gray-800 prose-p:leading-relaxed prose-p:my-3
|
|
235
|
+
prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline
|
|
236
|
+
prose-code:bg-gray-100 prose-code:px-1 prose-code:py-0.5 prose-code:rounded-none prose-code:text-[0.9em] prose-code:font-mono prose-code:text-gray-900
|
|
237
|
+
prose-pre:bg-gray-50 prose-pre:border prose-pre:border-gray-200 prose-pre:rounded-none prose-pre:p-0
|
|
238
|
+
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 prose-img:
|
|
239
|
+
prose-ol:list-decimal prose-ol:pl-4 prose-ol:list-outside
|
|
240
|
+
[&>*:first-child]:!mt-0"
|
|
241
|
+
v-html="processedHtml">
|
|
242
|
+
</div>
|
|
243
|
+
</template>
|
|
244
|
+
|
|
245
|
+
<style>
|
|
246
|
+
/* Highlight.js theme - minimal */
|
|
247
|
+
.hljs {
|
|
248
|
+
background: transparent !important;
|
|
249
|
+
padding: 0 !important;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Custom List Styling - Premium technical bullet */
|
|
253
|
+
.prose ul {
|
|
254
|
+
list-style: none !important;
|
|
255
|
+
padding-left: 0.25rem !important;
|
|
256
|
+
margin-top: 0.75rem !important;
|
|
257
|
+
margin-bottom: 0.75rem !important;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.prose ul > li {
|
|
261
|
+
position: relative !important;
|
|
262
|
+
padding-left: 1.5rem !important;
|
|
263
|
+
margin-top: 0.5rem !important;
|
|
264
|
+
margin-bottom: 0.5rem !important;
|
|
265
|
+
line-height: 1.6 !important;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.prose ul > li::before {
|
|
269
|
+
content: "" !important;
|
|
270
|
+
position: absolute !important;
|
|
271
|
+
left: 0 !important;
|
|
272
|
+
top: 0.6em !important;
|
|
273
|
+
width: 6px !important;
|
|
274
|
+
height: 6px !important;
|
|
275
|
+
background-color: #ef4444 !important; /* Red-500 */
|
|
276
|
+
border-radius: 0 !important;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Nested list styling */
|
|
280
|
+
.prose ul ul {
|
|
281
|
+
margin-top: 0.25rem !important;
|
|
282
|
+
margin-bottom: 0.25rem !important;
|
|
283
|
+
padding-left: 1rem !important;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.prose ul ul > li {
|
|
287
|
+
padding-left: 1.25rem !important;
|
|
288
|
+
margin-top: 0.25rem !important;
|
|
289
|
+
margin-bottom: 0.25rem !important;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.prose ul ul > li::before {
|
|
293
|
+
width: 5px !important;
|
|
294
|
+
height: 5px !important;
|
|
295
|
+
background-color: #ef4444 !important; /* Red-500 - same as parent, slightly smaller */
|
|
296
|
+
top: 0.65em !important;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* Custom Blockquote Styling - Dual Red Lines */
|
|
300
|
+
.prose blockquote {
|
|
301
|
+
border-left: none !important;
|
|
302
|
+
padding-left: 1rem !important;
|
|
303
|
+
margin-left: 0 !important;
|
|
304
|
+
position: relative !important;
|
|
305
|
+
font-style: italic !important;
|
|
306
|
+
color: #1f2937 !important; /* gray-800 */
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.prose blockquote::before {
|
|
310
|
+
content: "" !important;
|
|
311
|
+
position: absolute !important;
|
|
312
|
+
left: 0 !important;
|
|
313
|
+
top: 0 !important;
|
|
314
|
+
bottom: 0 !important;
|
|
315
|
+
width: 3px !important;
|
|
316
|
+
background-color: #ef4444 !important; /* Red-500 - thick line */
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
/* Ensure images don't have artifacts */
|
|
322
|
+
.prose img {
|
|
323
|
+
display: block;
|
|
324
|
+
margin-left: auto;
|
|
325
|
+
margin-right: auto;
|
|
326
|
+
}
|
|
327
|
+
.prose pre {
|
|
328
|
+
border: none !important;
|
|
329
|
+
}
|
|
330
|
+
</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,163 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { Icon } from '@iconify/vue'
|
|
4
|
+
import type { Stage } from '../types'
|
|
5
|
+
|
|
6
|
+
const failedImages = ref<Record<string, boolean>>({})
|
|
7
|
+
|
|
8
|
+
function handleImageError(url: string) {
|
|
9
|
+
failedImages.value[url] = true
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const props = defineProps<{
|
|
13
|
+
stage: Stage
|
|
14
|
+
isFirst?: boolean
|
|
15
|
+
isLast?: boolean
|
|
16
|
+
prevStageName?: string
|
|
17
|
+
}>()
|
|
18
|
+
|
|
19
|
+
function getDomain(url: string): string {
|
|
20
|
+
try {
|
|
21
|
+
return new URL(url).hostname.replace('www.', '')
|
|
22
|
+
} catch {
|
|
23
|
+
return url
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getFavicon(url: string): string {
|
|
28
|
+
const domain = getDomain(url)
|
|
29
|
+
return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatTime(seconds: number): string {
|
|
33
|
+
return `${seconds.toFixed(2)}s`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatCost(dollars: number): string {
|
|
37
|
+
return dollars > 0 ? `$${dollars.toFixed(6)}` : '$0'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getModelShort(model: string): string {
|
|
41
|
+
const short = model.includes('/') ? model.split('/').pop() || model : model
|
|
42
|
+
return short.length > 25 ? short.slice(0, 23) + '…' : short
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getStageTheme(name?: string) {
|
|
46
|
+
if (!name) return themes['default']
|
|
47
|
+
const key = name.toLowerCase()
|
|
48
|
+
|
|
49
|
+
if (key.includes('search')) return themes['search']
|
|
50
|
+
if (key.includes('crawl') || key.includes('page')) return themes['crawler']
|
|
51
|
+
if (key.includes('agent')) return themes['agent']
|
|
52
|
+
if (key.includes('instruct')) return themes['instruct']
|
|
53
|
+
if (key.includes('vision')) return themes['vision']
|
|
54
|
+
|
|
55
|
+
return themes['default']
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const themes: Record<string, any> = {
|
|
59
|
+
'search': { color: 'text-blue-600', bg: 'bg-blue-50', line: 'bg-red-300', iconBg: 'bg-blue-100/50', dotBg: 'bg-red-500', icon: 'mdi:magnify' },
|
|
60
|
+
'crawler': { color: 'text-orange-600', bg: 'bg-orange-50', line: 'bg-red-300', iconBg: 'bg-orange-100/50', dotBg: 'bg-red-500', icon: 'mdi:web' },
|
|
61
|
+
'agent': { color: 'text-purple-600', bg: 'bg-purple-50', line: 'bg-red-300', iconBg: 'bg-white/80', dotBg: 'bg-red-500', icon: 'mdi:robot' },
|
|
62
|
+
'instruct': { color: 'text-red-600', bg: 'bg-red-50', line: 'bg-red-300', iconBg: 'bg-white/80', dotBg: 'bg-red-500', icon: 'mdi:lightning-bolt' },
|
|
63
|
+
'vision': { color: 'text-green-600', bg: 'bg-green-50', line: 'bg-red-300', iconBg: 'bg-green-100/50', dotBg: 'bg-red-500', icon: 'mdi:eye' },
|
|
64
|
+
'default': { color: 'text-gray-600', bg: 'bg-gray-50', line: 'bg-red-300', iconBg: 'bg-gray-100/50', dotBg: 'bg-red-500', icon: 'mdi:circle' }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getIcon(name: string): string {
|
|
68
|
+
const key = name.toLowerCase()
|
|
69
|
+
if (key.includes('search')) return 'mdi:magnify'
|
|
70
|
+
if (key.includes('crawl') || key.includes('page')) return 'mdi:web'
|
|
71
|
+
if (key.includes('agent')) return 'mdi:robot'
|
|
72
|
+
if (key.includes('instruct')) return 'mdi:lightning-bolt'
|
|
73
|
+
if (key.includes('vision')) return 'mdi:eye'
|
|
74
|
+
return 'mdi:circle'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getModelLogo(model: string): string | undefined {
|
|
78
|
+
if (!model) return undefined
|
|
79
|
+
const m = model.toLowerCase()
|
|
80
|
+
if (m.includes('openai') || m.includes('gpt')) return 'logos/openai.svg'
|
|
81
|
+
if (m.includes('claude') || m.includes('anthropic')) return 'logos/anthropic.svg'
|
|
82
|
+
if (m.includes('gemini') || m.includes('google')) return 'logos/google.svg'
|
|
83
|
+
if (m.includes('deepseek')) return 'logos/deepseek.png'
|
|
84
|
+
if (m.includes('huggingface')) return 'logos/huggingface.png'
|
|
85
|
+
if (m.includes('mistral')) return 'logos/mistral.png'
|
|
86
|
+
if (m.includes('perplexity')) return 'logos/perplexity.svg'
|
|
87
|
+
if (m.includes('cerebras')) return 'logos/cerebras.svg'
|
|
88
|
+
if (m.includes('grok')) return 'logos/grok.png'
|
|
89
|
+
if (m.includes('qwen')) return 'logos/qwen.png'
|
|
90
|
+
if (m.includes('minimax')) return 'logos/minimax.png'
|
|
91
|
+
if (m.includes('nvidia') || m.includes('nvida')) return 'logos/nvida.png'
|
|
92
|
+
if (m.includes('azure') || m.includes('microsoft')) return 'logos/microsoft.svg'
|
|
93
|
+
if (m.includes('xai')) return 'logos/xai.png'
|
|
94
|
+
if (m.includes('xiaomi')) return 'logos/xiaomi.png'
|
|
95
|
+
if (m.includes('zai')) return 'logos/zai.png'
|
|
96
|
+
return undefined
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<div class="relative">
|
|
102
|
+
<!-- Content -->
|
|
103
|
+
<div class="flex-1 min-w-0 pl-2">
|
|
104
|
+
<div class="rounded-none overflow-hidden bg-white">
|
|
105
|
+
|
|
106
|
+
<!-- Header -->
|
|
107
|
+
<div :class="['bg-white px-3 py-1.5 flex items-center justify-between gap-2']">
|
|
108
|
+
<div class="flex items-center gap-2">
|
|
109
|
+
<div :class="['w-6 h-6 flex items-center justify-center shrink-0 overflow-hidden border border-gray-100', getStageTheme(stage.name).iconBg, getStageTheme(stage.name).color]">
|
|
110
|
+
<img v-if="getModelLogo(stage.model)" :src="getModelLogo(stage.model)" class="w-4 h-4 object-contain" />
|
|
111
|
+
<Icon v-else :icon="getIcon(stage.name)" class="text-xs" />
|
|
112
|
+
</div>
|
|
113
|
+
<div class="flex flex-col">
|
|
114
|
+
<span class="font-black text-xs text-gray-900 uppercase tracking-tight">{{ stage.name }}</span>
|
|
115
|
+
<span class="text-[10px] font-mono text-gray-400 tabular-nums tracking-tighter">{{ getModelShort(stage.model) }}</span>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
<div v-if="stage.time > 0 || stage.cost > 0" class="text-[10px] text-gray-400 font-mono flex items-center justify-end gap-2 leading-tight min-w-[120px]">
|
|
119
|
+
<span v-if="stage.cost > 0">{{ formatCost(stage.cost) }}</span>
|
|
120
|
+
<span v-if="stage.time > 0 && stage.cost > 0" class="text-gray-300">·</span>
|
|
121
|
+
<span v-if="stage.time > 0">{{ formatTime(stage.time) }}</span>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
<div v-if="stage.references?.length || stage.image_references?.length" class="bg-white pl-8 relative">
|
|
127
|
+
<div v-if="stage.references?.length" class="divide-y divide-gray-50 relative z-10">
|
|
128
|
+
<a v-for="(ref, idx) in stage.references" :key="idx"
|
|
129
|
+
:href="ref.url" target="_blank"
|
|
130
|
+
class="flex items-start gap-2 pr-3 py-2 hover:bg-gray-50 transition-colors group">
|
|
131
|
+
<!-- Favicon - Aligned with Title -->
|
|
132
|
+
<img :src="getFavicon(ref.url)" class="w-3 h-3 rounded-none shrink-0 object-contain mt-[3px]">
|
|
133
|
+
|
|
134
|
+
<!-- Content: Title and Domain -->
|
|
135
|
+
<div class="flex-1 min-w-0 flex flex-col">
|
|
136
|
+
<div class="flex items-center gap-2">
|
|
137
|
+
<span class="flex-1 text-xs font-bold text-gray-800 truncate leading-tight tracking-tight">{{ ref.title }}</span>
|
|
138
|
+
<!-- Neutralized Citation Design -->
|
|
139
|
+
<span class="shrink-0 text-[10px] font-bold text-blue-600 flex items-center justify-center">{{ idx + 1 }}</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="text-[10px] font-mono text-gray-400 truncate mt-0.5 tracking-tighter">{{ getDomain(ref.url) }}</div>
|
|
142
|
+
</div>
|
|
143
|
+
</a>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<!-- Image Search Results -->
|
|
147
|
+
<div v-if="stage.image_references?.length" class="pr-2 py-2 relative z-10">
|
|
148
|
+
<div class="grid grid-cols-3 gap-1">
|
|
149
|
+
<a v-for="(img, idx) in stage.image_references" :key="idx"
|
|
150
|
+
v-show="!failedImages[img.url]"
|
|
151
|
+
:href="img.url" target="_blank"
|
|
152
|
+
class="relative aspect-square overflow-hidden bg-white border border-gray-200 transition-colors">
|
|
153
|
+
<img :src="img.thumbnail || img.url"
|
|
154
|
+
@error="handleImageError(img.url)"
|
|
155
|
+
class="w-full h-full object-cover">
|
|
156
|
+
</a>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</template>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Type definitions for render data
|
|
2
|
+
// Python only passes raw data, all processing happens in frontend
|
|
3
|
+
|
|
4
|
+
export interface Stage {
|
|
5
|
+
name: string
|
|
6
|
+
model: string
|
|
7
|
+
provider: string
|
|
8
|
+
icon_name?: string // Icon identifier (e.g., "google", "openai")
|
|
9
|
+
time: number // Time in seconds (raw number)
|
|
10
|
+
cost: number // Cost in dollars (raw number)
|
|
11
|
+
references?: Reference[]
|
|
12
|
+
image_references?: ImageReference[]
|
|
13
|
+
crawled_pages?: CrawledPage[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Reference {
|
|
17
|
+
title: string
|
|
18
|
+
url: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ImageReference {
|
|
22
|
+
title: string
|
|
23
|
+
url: string
|
|
24
|
+
thumbnail?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CrawledPage {
|
|
28
|
+
title: string
|
|
29
|
+
url: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Stats {
|
|
33
|
+
total_time?: number
|
|
34
|
+
vision_duration?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface Flags {
|
|
38
|
+
has_vision: boolean
|
|
39
|
+
has_search: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Raw data from Python - minimal processing
|
|
43
|
+
export interface RenderData {
|
|
44
|
+
markdown: string // Raw markdown content
|
|
45
|
+
stages: Stage[]
|
|
46
|
+
references: Reference[] // All references for citation
|
|
47
|
+
page_references: Reference[]
|
|
48
|
+
image_references: ImageReference[]
|
|
49
|
+
stats: Stats
|
|
50
|
+
total_time: number
|
|
51
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
5
|
+
"types": ["vite/client"],
|
|
6
|
+
|
|
7
|
+
/* Linting */
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noUnusedLocals": true,
|
|
10
|
+
"noUnusedParameters": true,
|
|
11
|
+
"erasableSyntaxOnly": true,
|
|
12
|
+
"noFallthroughCasesInSwitch": true,
|
|
13
|
+
"noUncheckedSideEffectImports": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["vite.config.ts"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
4
|
+
import { viteSingleFile } from 'vite-plugin-singlefile'
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [
|
|
8
|
+
vue(),
|
|
9
|
+
tailwindcss(),
|
|
10
|
+
viteSingleFile(),
|
|
11
|
+
],
|
|
12
|
+
build: {
|
|
13
|
+
outDir: '../assets/card-dist',
|
|
14
|
+
emptyOutDir: true,
|
|
15
|
+
},
|
|
16
|
+
})
|