entari-plugin-hyw 4.0.0rc5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of entari-plugin-hyw might be problematic. Click here for more details.

Files changed (99) hide show
  1. entari_plugin_hyw/__init__.py +532 -0
  2. entari_plugin_hyw/assets/card-dist/index.html +387 -0
  3. entari_plugin_hyw/assets/card-dist/logos/anthropic.svg +1 -0
  4. entari_plugin_hyw/assets/card-dist/logos/cerebras.svg +9 -0
  5. entari_plugin_hyw/assets/card-dist/logos/deepseek.png +0 -0
  6. entari_plugin_hyw/assets/card-dist/logos/gemini.svg +1 -0
  7. entari_plugin_hyw/assets/card-dist/logos/google.svg +1 -0
  8. entari_plugin_hyw/assets/card-dist/logos/grok.png +0 -0
  9. entari_plugin_hyw/assets/card-dist/logos/huggingface.png +0 -0
  10. entari_plugin_hyw/assets/card-dist/logos/microsoft.svg +15 -0
  11. entari_plugin_hyw/assets/card-dist/logos/minimax.png +0 -0
  12. entari_plugin_hyw/assets/card-dist/logos/mistral.png +0 -0
  13. entari_plugin_hyw/assets/card-dist/logos/nvida.png +0 -0
  14. entari_plugin_hyw/assets/card-dist/logos/openai.svg +1 -0
  15. entari_plugin_hyw/assets/card-dist/logos/openrouter.png +0 -0
  16. entari_plugin_hyw/assets/card-dist/logos/perplexity.svg +24 -0
  17. entari_plugin_hyw/assets/card-dist/logos/qwen.png +0 -0
  18. entari_plugin_hyw/assets/card-dist/logos/xai.png +0 -0
  19. entari_plugin_hyw/assets/card-dist/logos/xiaomi.png +0 -0
  20. entari_plugin_hyw/assets/card-dist/logos/zai.png +0 -0
  21. entari_plugin_hyw/assets/card-dist/vite.svg +1 -0
  22. entari_plugin_hyw/assets/icon/anthropic.svg +1 -0
  23. entari_plugin_hyw/assets/icon/cerebras.svg +9 -0
  24. entari_plugin_hyw/assets/icon/deepseek.png +0 -0
  25. entari_plugin_hyw/assets/icon/gemini.svg +1 -0
  26. entari_plugin_hyw/assets/icon/google.svg +1 -0
  27. entari_plugin_hyw/assets/icon/grok.png +0 -0
  28. entari_plugin_hyw/assets/icon/huggingface.png +0 -0
  29. entari_plugin_hyw/assets/icon/microsoft.svg +15 -0
  30. entari_plugin_hyw/assets/icon/minimax.png +0 -0
  31. entari_plugin_hyw/assets/icon/mistral.png +0 -0
  32. entari_plugin_hyw/assets/icon/nvida.png +0 -0
  33. entari_plugin_hyw/assets/icon/openai.svg +1 -0
  34. entari_plugin_hyw/assets/icon/openrouter.png +0 -0
  35. entari_plugin_hyw/assets/icon/perplexity.svg +24 -0
  36. entari_plugin_hyw/assets/icon/qwen.png +0 -0
  37. entari_plugin_hyw/assets/icon/xai.png +0 -0
  38. entari_plugin_hyw/assets/icon/xiaomi.png +0 -0
  39. entari_plugin_hyw/assets/icon/zai.png +0 -0
  40. entari_plugin_hyw/browser/__init__.py +10 -0
  41. entari_plugin_hyw/browser/engines/base.py +13 -0
  42. entari_plugin_hyw/browser/engines/bing.py +95 -0
  43. entari_plugin_hyw/browser/engines/searxng.py +137 -0
  44. entari_plugin_hyw/browser/landing.html +172 -0
  45. entari_plugin_hyw/browser/manager.py +153 -0
  46. entari_plugin_hyw/browser/service.py +275 -0
  47. entari_plugin_hyw/card-ui/.gitignore +24 -0
  48. entari_plugin_hyw/card-ui/README.md +5 -0
  49. entari_plugin_hyw/card-ui/index.html +16 -0
  50. entari_plugin_hyw/card-ui/package-lock.json +2342 -0
  51. entari_plugin_hyw/card-ui/package.json +31 -0
  52. entari_plugin_hyw/card-ui/public/logos/anthropic.svg +1 -0
  53. entari_plugin_hyw/card-ui/public/logos/cerebras.svg +9 -0
  54. entari_plugin_hyw/card-ui/public/logos/deepseek.png +0 -0
  55. entari_plugin_hyw/card-ui/public/logos/gemini.svg +1 -0
  56. entari_plugin_hyw/card-ui/public/logos/google.svg +1 -0
  57. entari_plugin_hyw/card-ui/public/logos/grok.png +0 -0
  58. entari_plugin_hyw/card-ui/public/logos/huggingface.png +0 -0
  59. entari_plugin_hyw/card-ui/public/logos/microsoft.svg +15 -0
  60. entari_plugin_hyw/card-ui/public/logos/minimax.png +0 -0
  61. entari_plugin_hyw/card-ui/public/logos/mistral.png +0 -0
  62. entari_plugin_hyw/card-ui/public/logos/nvida.png +0 -0
  63. entari_plugin_hyw/card-ui/public/logos/openai.svg +1 -0
  64. entari_plugin_hyw/card-ui/public/logos/openrouter.png +0 -0
  65. entari_plugin_hyw/card-ui/public/logos/perplexity.svg +24 -0
  66. entari_plugin_hyw/card-ui/public/logos/qwen.png +0 -0
  67. entari_plugin_hyw/card-ui/public/logos/xai.png +0 -0
  68. entari_plugin_hyw/card-ui/public/logos/xiaomi.png +0 -0
  69. entari_plugin_hyw/card-ui/public/logos/zai.png +0 -0
  70. entari_plugin_hyw/card-ui/public/vite.svg +1 -0
  71. entari_plugin_hyw/card-ui/src/App.vue +756 -0
  72. entari_plugin_hyw/card-ui/src/assets/vue.svg +1 -0
  73. entari_plugin_hyw/card-ui/src/components/HelloWorld.vue +41 -0
  74. entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +382 -0
  75. entari_plugin_hyw/card-ui/src/components/SectionCard.vue +41 -0
  76. entari_plugin_hyw/card-ui/src/components/StageCard.vue +240 -0
  77. entari_plugin_hyw/card-ui/src/main.ts +5 -0
  78. entari_plugin_hyw/card-ui/src/style.css +29 -0
  79. entari_plugin_hyw/card-ui/src/test_regex.js +103 -0
  80. entari_plugin_hyw/card-ui/src/types.ts +61 -0
  81. entari_plugin_hyw/card-ui/tsconfig.app.json +16 -0
  82. entari_plugin_hyw/card-ui/tsconfig.json +7 -0
  83. entari_plugin_hyw/card-ui/tsconfig.node.json +26 -0
  84. entari_plugin_hyw/card-ui/vite.config.ts +16 -0
  85. entari_plugin_hyw/definitions.py +130 -0
  86. entari_plugin_hyw/history.py +248 -0
  87. entari_plugin_hyw/image_cache.py +274 -0
  88. entari_plugin_hyw/misc.py +135 -0
  89. entari_plugin_hyw/modular_pipeline.py +351 -0
  90. entari_plugin_hyw/render_vue.py +401 -0
  91. entari_plugin_hyw/search.py +116 -0
  92. entari_plugin_hyw/stage_base.py +88 -0
  93. entari_plugin_hyw/stage_instruct.py +328 -0
  94. entari_plugin_hyw/stage_instruct_review.py +92 -0
  95. entari_plugin_hyw/stage_summary.py +164 -0
  96. entari_plugin_hyw-4.0.0rc5.dist-info/METADATA +116 -0
  97. entari_plugin_hyw-4.0.0rc5.dist-info/RECORD +99 -0
  98. entari_plugin_hyw-4.0.0rc5.dist-info/WHEEL +5 -0
  99. entari_plugin_hyw-4.0.0rc5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,240 @@
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
+ hideRefs?: boolean
13
+ }>()
14
+
15
+ const failedImages = ref<Record<string, boolean>>({})
16
+ const imageHeights = ref<Record<string, number>>({})
17
+
18
+ function handleImageError(url: string) {
19
+ failedImages.value[url] = true
20
+ }
21
+
22
+ function handleImageLoad(url: string, event: Event) {
23
+ const img = event.target as HTMLImageElement
24
+ if (img.naturalWidth && img.naturalHeight) {
25
+ // Store aspect ratio as height per unit width
26
+ imageHeights.value[url] = img.naturalHeight / img.naturalWidth
27
+ }
28
+ }
29
+
30
+ // Compute two columns for masonry layout
31
+ const imageColumns = computed(() => {
32
+ const images = props.stage.image_references || []
33
+ const leftColumn: typeof images = []
34
+ const rightColumn: typeof images = []
35
+ let leftHeight = 0
36
+ let rightHeight = 0
37
+
38
+ for (const img of images) {
39
+ if (failedImages.value[img.url]) continue
40
+
41
+ // Get aspect ratio (default to 1 if not loaded yet)
42
+ const aspectRatio = imageHeights.value[img.url] || 1
43
+
44
+ // Add to shorter column
45
+ if (leftHeight <= rightHeight) {
46
+ leftColumn.push(img)
47
+ leftHeight += aspectRatio
48
+ } else {
49
+ rightColumn.push(img)
50
+ rightHeight += aspectRatio
51
+ }
52
+ }
53
+
54
+ return { leftColumn, rightColumn }
55
+ })
56
+
57
+
58
+
59
+
60
+
61
+ function getDomain(url: string): string {
62
+ try {
63
+ const urlObj = new URL(url)
64
+ const hostname = urlObj.hostname.replace('www.', '')
65
+ const pathname = urlObj.pathname === '/' ? '' : urlObj.pathname
66
+ return hostname + pathname
67
+ } catch {
68
+ return url
69
+ }
70
+ }
71
+
72
+ function getFavicon(url: string): string {
73
+ const domain = getDomain(url)
74
+ return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`
75
+ }
76
+
77
+ function formatTime(seconds: number): string {
78
+ return `${seconds.toFixed(2)}s`
79
+ }
80
+
81
+ function formatCost(dollars: number): string {
82
+ return dollars > 0 ? `$${dollars.toFixed(6)}` : '$0'
83
+ }
84
+
85
+ function getModelShort(model: string): string {
86
+ const short = model.includes('/') ? model.split('/').pop() || model : model
87
+ return short.length > 25 ? short.slice(0, 23) + '…' : short
88
+ }
89
+
90
+ function getStageTheme(name?: string) {
91
+ if (!name) return themes['default']
92
+ const key = name.toLowerCase()
93
+
94
+ if (key.includes('search')) return themes['search']
95
+ if (key.includes('crawl') || key.includes('page')) return themes['crawler']
96
+ if (key.includes('agent')) return themes['agent']
97
+ if (key.includes('instruct')) return themes['instruct']
98
+ if (key.includes('vision')) return themes['vision']
99
+
100
+ return themes['default']
101
+ }
102
+
103
+ const themes: Record<string, any> = {
104
+ 'search': { color: 'text-blue-600', bg: 'bg-blue-50', iconBg: 'bg-blue-100/50', icon: 'mdi:magnify' },
105
+ 'crawler': { color: 'text-orange-600', bg: 'bg-orange-50', iconBg: 'bg-orange-100/50', icon: 'mdi:web' },
106
+ 'agent': { color: 'text-purple-600', bg: 'bg-purple-50', iconBg: 'bg-white/80', icon: 'mdi:robot' },
107
+ 'instruct': { color: 'text-red-600', bg: 'bg-red-50', iconBg: 'bg-white/80', icon: 'mdi:lightning-bolt' },
108
+ 'vision': { color: 'text-purple-600', bg: 'bg-purple-50', iconBg: 'bg-white/80', icon: 'mdi:eye' },
109
+ 'default': { color: 'text-gray-600', bg: 'bg-gray-50', iconBg: 'bg-gray-100/50', icon: 'mdi:circle' }
110
+ }
111
+
112
+ function getIcon(name: string): string {
113
+ const key = name.toLowerCase()
114
+ if (key.includes('search')) return 'mdi:magnify'
115
+ if (key.includes('crawl') || key.includes('page')) return 'mdi:web'
116
+ if (key.includes('agent')) return 'mdi:robot'
117
+ if (key.includes('instruct')) return 'mdi:lightning-bolt'
118
+ if (key.includes('vision')) return 'mdi:eye'
119
+ return 'mdi:circle'
120
+ }
121
+
122
+ function getModelLogo(model: string): string | undefined {
123
+ if (!model) return undefined
124
+ const m = model.toLowerCase()
125
+ if (m.includes('openai') || m.includes('gpt')) return 'logos/openai.svg'
126
+ if (m.includes('claude') || m.includes('anthropic')) return 'logos/anthropic.svg'
127
+ if (m.includes('gemini') || m.includes('google')) return 'logos/google.svg'
128
+ if (m.includes('deepseek')) return 'logos/deepseek.png'
129
+ if (m.includes('huggingface')) return 'logos/huggingface.png'
130
+ if (m.includes('mistral')) return 'logos/mistral.png'
131
+ if (m.includes('perplexity')) return 'logos/perplexity.svg'
132
+ if (m.includes('cerebras')) return 'logos/cerebras.svg'
133
+ if (m.includes('grok')) return 'logos/grok.png'
134
+ if (m.includes('qwen')) return 'logos/qwen.png'
135
+ if (m.includes('minimax')) return 'logos/minimax.png'
136
+ if (m.includes('nvidia') || m.includes('nvida')) return 'logos/nvida.png'
137
+ if (m.includes('azure') || m.includes('microsoft')) return 'logos/microsoft.svg'
138
+ if (m.includes('xai')) return 'logos/xai.png'
139
+ if (m.includes('xiaomi')) return 'logos/xiaomi.png'
140
+ if (m.includes('zai')) return 'logos/zai.png'
141
+ return undefined
142
+ }
143
+ </script>
144
+
145
+ <template>
146
+ <div class="relative">
147
+ <!-- Content -->
148
+ <div class="flex-1 min-w-0 pl-2">
149
+ <div class="rounded-none overflow-hidden bg-white">
150
+
151
+ <!-- Header -->
152
+ <div :class="['bg-white px-4 py-2.5 flex items-center justify-between gap-3']">
153
+ <div class="flex items-center gap-3">
154
+ <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]">
155
+ <img v-if="getModelLogo(stage.model)" :src="getModelLogo(stage.model)" class="w-5 h-5 object-contain" />
156
+ <Icon v-else :icon="getIcon(stage.name)" class="text-lg" />
157
+ </div>
158
+ <div class="flex flex-col">
159
+ <span class="font-black text-[18px] text-gray-800 uppercase tracking-tight">{{ stage.name }}</span>
160
+ <span class="text-[15.5px] font-mono tabular-nums tracking-tighter" style="color: var(--text-muted)">{{ getModelShort(stage.model) }}</span>
161
+ </div>
162
+ </div>
163
+ <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)">
164
+ <span v-if="stage.cost > 0">{{ formatCost(stage.cost) }}</span>
165
+ <span v-if="stage.time > 0 && stage.cost > 0" class="text-gray-300">·</span>
166
+ <span v-if="stage.time > 0">{{ formatTime(stage.time) }}</span>
167
+ </div>
168
+ </div>
169
+
170
+
171
+ <div v-if="stage.description" class="px-5 py-3 text-[14.5px] text-gray-600 bg-gray-50/40 border-y border-gray-100 italic leading-relaxed font-sans">
172
+ <span class="mr-1 text-[var(--theme-color)] not-italic opacity-50">✦</span> {{ stage.description }}
173
+ </div>
174
+
175
+ <div v-if="stage.references?.length || stage.image_references?.length || stage.crawled_pages?.length || stage.tasks?.length" class="bg-white pl-11 relative">
176
+
177
+ <!-- Tasks List -->
178
+ <div v-if="stage.tasks?.length" class="pr-3 py-3 relative z-10 border-b border-gray-50 last:border-0">
179
+ <div v-for="(task, idx) in stage.tasks" :key="idx" class="flex items-start gap-2.5 mb-2 last:mb-0">
180
+ <div class="w-4 h-4 rounded-full border border-[var(--theme-color)] flex items-center justify-center shrink-0 mt-0.5 bg-[var(--theme-color)]/10">
181
+ <Icon icon="mdi:check" class="text-[10px] text-[var(--theme-color)]" />
182
+ </div>
183
+ <span class="text-[15px] text-gray-700 font-medium leading-tight select-text">{{ task }}</span>
184
+ </div>
185
+ </div>
186
+
187
+ <!-- Image Search Results - True Masonry Layout -->
188
+ <div v-if="stage.image_references?.length && !hideRefs" class="pr-3 py-3 relative z-10">
189
+ <div class="flex gap-2">
190
+ <!-- Left Column -->
191
+ <div class="flex-1 flex flex-col gap-2">
192
+ <a v-for="(img, idx) in imageColumns.leftColumn" :key="`left-${img.url}-${idx}`"
193
+ :href="img.url" target="_blank"
194
+ class="relative overflow-hidden transition-all hover:opacity-90 group block">
195
+ <img :src="img.thumbnail || img.url"
196
+ @load="handleImageLoad(img.url, $event)"
197
+ @error="handleImageError(img.url)"
198
+ class="w-full h-auto block group-hover:scale-[1.02] transition-transform">
199
+ </a>
200
+ </div>
201
+ <!-- Right Column -->
202
+ <div class="flex-1 flex flex-col gap-2">
203
+ <a v-for="(img, idx) in imageColumns.rightColumn" :key="`right-${img.url}-${idx}`"
204
+ :href="img.url" target="_blank"
205
+ class="relative overflow-hidden transition-all hover:opacity-90 group block">
206
+ <img :src="img.thumbnail || img.url"
207
+ @load="handleImageLoad(img.url, $event)"
208
+ @error="handleImageError(img.url)"
209
+ class="w-full h-auto block group-hover:scale-[1.02] transition-transform">
210
+ </a>
211
+ </div>
212
+ </div>
213
+ </div>
214
+
215
+ <div v-if="stage.crawled_pages?.length && !hideRefs" class="divide-y divide-gray-50 relative z-10">
216
+ <div v-for="(page, idx) in stage.crawled_pages" :key="idx" class="pr-3 py-3 hover:bg-gray-50 transition-colors group">
217
+ <!-- Header Row: Title & Link -->
218
+ <a :href="page.url" target="_blank" class="flex items-start gap-3 block">
219
+ <img :src="getFavicon(page.url)" class="w-4 h-4 rounded-none shrink-0 object-contain mt-[4px]">
220
+ <div class="flex-1 min-w-0 flex flex-col">
221
+ <div class="flex items-center gap-2">
222
+ <span class="flex-1 text-[18px] font-bold text-gray-700 truncate leading-tight tracking-tight group-hover:text-[var(--theme-color)] transition-colors">{{ page.title }}</span>
223
+ <!-- Square Badge with Shadow -->
224
+ <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>
225
+ </div>
226
+ <div class="text-[15.5px] font-mono truncate mt-0.5 tracking-tighter" style="color: var(--text-muted)">{{ getDomain(page.url) }}</div>
227
+ </div>
228
+ </a>
229
+
230
+ <!-- Description Sub-container -->
231
+ <div v-if="page.description" class="ml-7 mt-2.5 p-3 bg-gray-100/80 text-[14px] text-gray-600 leading-snug font-sans border-l-[3px] border-[var(--theme-color)] rounded-r-sm">
232
+ {{ page.description }}
233
+ </div>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ </div>
239
+ </div>
240
+ </template>
@@ -0,0 +1,5 @@
1
+ import { createApp } from 'vue'
2
+ import './style.css'
3
+ import App from './App.vue'
4
+
5
+ createApp(App).mount('#app')
@@ -0,0 +1,29 @@
1
+ @import "tailwindcss";
2
+ @plugin "daisyui";
3
+ @plugin "@tailwindcss/typography";
4
+
5
+ /* Custom styles */
6
+ body {
7
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
8
+ }
9
+
10
+ /* Premium reading color scheme - applies to all prose elements */
11
+ .prose {
12
+ color: var(--text-body, #3a3a3c) !important;
13
+ }
14
+
15
+ .prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
16
+ color: var(--text-primary, #2c2c2e) !important;
17
+ }
18
+
19
+ .prose p, .prose li, .prose td, .prose th {
20
+ color: var(--text-body, #3a3a3c) !important;
21
+ }
22
+
23
+ .prose strong, .prose b {
24
+ color: var(--text-primary, #2c2c2e) !important;
25
+ }
26
+
27
+ .prose code {
28
+ color: var(--text-body, #3a3a3c) !important;
29
+ }
@@ -0,0 +1,103 @@
1
+
2
+ const stripPrefixBeforeH1 = (text) => {
3
+ const h1Match = text.match(/^#\s+/m)
4
+ if (h1Match && h1Match.index !== undefined) {
5
+ return text.substring(h1Match.index)
6
+ }
7
+ return text
8
+ }
9
+
10
+ const dedent = (text) => {
11
+ const lines = text.split('\n')
12
+ // Find minimum indentation of non-empty lines
13
+ let minIndent = Infinity
14
+ for (const line of lines) {
15
+ if (line.trim().length === 0) continue
16
+ const leadingSpace = line.match(/^\s*/)?.[0].length || 0
17
+ if (leadingSpace < minIndent) minIndent = leadingSpace
18
+ }
19
+
20
+ if (minIndent === Infinity || minIndent === 0) return text
21
+
22
+ return lines.map(line => {
23
+ if (line.trim().length === 0) return ''
24
+ return line.substring(minIndent)
25
+ }).join('\n')
26
+ }
27
+
28
+ const parse = (rawMd) => {
29
+ if (!rawMd) return []
30
+
31
+ const md = stripPrefixBeforeH1(rawMd)
32
+
33
+ let content = md.replace(/^#\s+.+$/m, '')
34
+ content = content.replace(/(?:^|\n)\s*(?:#{1,3}|\*\*)\s*(?:References|Citations|Sources)[\s\S]*$/i, '')
35
+ content = content.trim()
36
+
37
+ const sections = []
38
+
39
+ const combinedRegex = /(```[\s\S]*?```|((?:^|\n)\|[^\n]*\|(?:\n\|[^\n]*\|)*)|<summary>[\s\S]*?<\/summary>)/
40
+
41
+ let remaining = content
42
+
43
+ while (remaining) {
44
+ const match = remaining.match(combinedRegex)
45
+ if (!match) {
46
+ if (remaining.trim()) {
47
+ sections.push({ type: 'markdown', content: remaining.trim() })
48
+ }
49
+ break
50
+ }
51
+
52
+ const index = match.index
53
+ const matchedStr = match[0]
54
+ const preText = remaining.substring(0, index)
55
+
56
+ if (preText.trim()) {
57
+ sections.push({ type: 'markdown', content: preText.trim() })
58
+ }
59
+
60
+ const isCode = matchedStr.startsWith('```')
61
+ const isSummary = matchedStr.startsWith('<summary>')
62
+ const isTable = !isCode && !isSummary && matchedStr.trim().startsWith('|')
63
+
64
+ if (isCode || isTable || isSummary) {
65
+ let language = ''
66
+ let content = matchedStr.trim()
67
+
68
+ if (isCode) {
69
+ const match = matchedStr.match(/^```(\w+)/)
70
+ if (match && match[1]) language = match[1]
71
+ } else if (isSummary) {
72
+ content = content.replace(/^<summary>/, '').replace(/<\/summary>$/, '')
73
+ content = dedent(content)
74
+ }
75
+
76
+ sections.push({
77
+ type: 'card',
78
+ title: isCode ? 'Code' : (isSummary ? 'Summary' : 'Table'),
79
+ content: content,
80
+ contentType: isCode ? 'code' : (isSummary ? 'summary' : 'table'),
81
+ language: language
82
+ })
83
+ } else {
84
+ sections.push({ type: 'markdown', content: matchedStr })
85
+ }
86
+
87
+ remaining = remaining.substring(index + matchedStr.length)
88
+ }
89
+
90
+ return sections
91
+ }
92
+
93
+ const test1 = `
94
+ # Title
95
+
96
+ <summary>
97
+ Indented text.
98
+ It might become code block.
99
+ </summary>
100
+ `
101
+
102
+ console.log("\n--- Test 2 (After Fix) ---")
103
+ console.log(JSON.stringify(parse(test1), null, 2))
@@ -0,0 +1,61 @@
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
+ icon_config?: string // Config key for icon (e.g. "openai")
10
+ time: number // Time in seconds (raw number)
11
+ cost: number // Cost in dollars (raw number)
12
+ usage?: any // Token usage stats
13
+ references?: Reference[]
14
+ image_references?: ImageReference[]
15
+ crawled_pages?: CrawledPage[]
16
+ description?: string // Brief intro or thought for this stage
17
+ tasks?: string[]
18
+ }
19
+
20
+ export interface Reference {
21
+ title: string
22
+ url: string
23
+ is_fetched?: boolean
24
+ snippet?: string
25
+ type?: string
26
+ images?: string[] // Extracted images (base64)
27
+ }
28
+
29
+ export interface ImageReference {
30
+ title: string
31
+ url: string
32
+ thumbnail?: string
33
+ }
34
+
35
+ export interface CrawledPage {
36
+ title: string
37
+ url: string
38
+ description?: string
39
+ }
40
+
41
+ export interface Stats {
42
+ total_time?: number
43
+ vision_duration?: number
44
+ }
45
+
46
+ export interface Flags {
47
+ has_vision: boolean
48
+ has_search: boolean
49
+ }
50
+
51
+ // Raw data from Python - minimal processing
52
+ export interface RenderData {
53
+ markdown: string // Raw markdown content
54
+ stages: Stage[]
55
+ references: Reference[] // All references for citation
56
+ page_references: Reference[]
57
+ image_references: ImageReference[]
58
+ stats: Stats
59
+ total_time: number
60
+ theme_color?: string // Configurable theme color (hex)
61
+ }
@@ -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,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -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
+ })
@@ -0,0 +1,130 @@
1
+ """
2
+ Centralized Definitions
3
+
4
+ All global prompts and tool definitions for the pipeline stages.
5
+ """
6
+
7
+ from typing import Dict, Any
8
+
9
+ # =============================================================================
10
+ # PROMPTS
11
+ # =============================================================================
12
+
13
+ INSTRUCT_SP = """你是一个智能 INSTRUCT 助手。
14
+ 你的目标是为用户的查询制定调研计划并执行。
15
+ > 尽量完成所有搜索和获取, 需要的时候可以并发执行多个同种或不同种工具
16
+
17
+ - 用户消息包含典型名词、可能的专有名词组合: 调用 `web_search` 工具(最大3)。
18
+ - 提炼消息中出独立的关键词搜索关键词本身, 不添加任何其他辅助搜索词, 搜索效果最好
19
+ - 不混合搜索词, 每次只针对搜索一个关键词, 获取其目的方向的最新数据
20
+ - 用户消息中出现链接时, 调用 `crawl_page`(最大3) 获取网页全文
21
+ - 以下情况调用 `refuse_answer`。
22
+ - 明显的鉴证、国家领导人、历史敏感事件隐喻
23
+ - 过于露骨的r18+、r18g内容(但不包括 galgame等acg向, 除非其过于违规)
24
+
25
+
26
+ ## 重要规则 (CRITICAL RULES):
27
+ - 禁止输出任何文本回复:你必须且只能通过工具调用来行动。
28
+ - 禁止回答用户的问题:你的任务仅仅是收集信息,回答将由后续阶段完成。
29
+ - 禁止解释你的行为:不要输出思考过程或计划描述,直接调用工具。
30
+ - 如果没有工具调用,流程将自动结束。
31
+ """
32
+
33
+ INSTRUCT_REVIEW_SP = """你是一个智能 INSTRUCT 审查助手, 你需要对 INSTRUCT 的输出进行二次信息补充或直接通过(不进行操作)
34
+
35
+
36
+ - 你已经使用过 `web_search` 工具, 不推荐再次使用, 即便你微调搜索词也只能获取重复信息
37
+ - 推荐使用 `crawl_page` 工具查看官方网站、wiki网站(但不推荐维基百科)、权威网站
38
+ - crawl_page 永远不使用国内垃圾网站例如 csdn、知乎、等重复搬运二手信息的网站
39
+
40
+ ## 重要规则 (CRITICAL RULES):
41
+ - 禁止输出任何文本回复:你必须且只能通过工具调用来行动。
42
+ - 禁止回答用户的问题:你的任务仅仅是收集信息。
43
+ - 禁止解释你的行为:直接调用所需工具。
44
+ - 如果没有必要进一步操作,请不要输出任何内容(空回复),流程将自动进入下一阶段。
45
+ """
46
+
47
+
48
+ SUMMARY_REPORT_SP = """# 你是一个信息整合专家 (Summary Agent).
49
+ 你需要根据用户问题、搜索结果和网页详细内容,生成最终的回答.
50
+ 如果用户发送你好或空内容回应你好即可.
51
+
52
+ ## 过程要求
53
+ - 用户要求的回复语言(包裹在 language 标签内)
54
+ ```language
55
+ {language}
56
+ ```
57
+ - 字数控制在600字以内, 百科式风格, 语言严谨不啰嗦.
58
+ - 视觉信息: 输入中如果包含自动获取的网页截图,请分析图片中的信息作为参考.
59
+ - 注意分辨搜索内容是否和用户问题有直接关系, 避免盲目相信混为一谈.
60
+ - 正文格式:
61
+ - 先给出一个 `# `大标题约 8-10 个字, 不要有多余废话, 不要直接回答用户的提问.
62
+ - 然后紧接着给出一个 <summary>...</summary>, 除了给出一个约 100 字的纯文本简介, 介绍本次输出的长文的清晰、重点概括.
63
+ - 随后开始详细二级标题 + markdown 正文, 语言描绘格式丰富多样, 简洁准确可信.
64
+ - 请不要给出过长的代码、表格列数等, 只讲重点和准确的数据.
65
+ - 不支持渲染: 链接, 图片链接, mermaid
66
+ - 支持渲染: 公式, 代码高亮, 只在需要的时候给出.
67
+ - 图片链接、链接框架会自动渲染出, 你无需显式给出.
68
+ - 引用:
69
+ > 重要: 所有正文内容必须基于实际信息, 保证百分百真实度
70
+ - 信息来源已按获取顺序编号为 [1], [2], [3]... 但不超过 9 个引用.
71
+ - 优先引用优质 fetch 抓取的页面的资源, 但如果抓取到需要登录、需要验证码、需要跳转到其他网站等无法获取的资源, 则不引用此资源
72
+ - 正文中直接使用 [1] 格式引用, 只引用对回答有帮助的来源, 只使用官方性较强的 wiki、官方网站、资源站等等, 不使用第三方转载新闻网站.
73
+ - 无需给出参考文献列表, 系统会自动生成
74
+ """
75
+
76
+
77
+ # =============================================================================
78
+ # TOOL DEFINITIONS
79
+ # =============================================================================
80
+
81
+ def get_refuse_answer_tool() -> Dict[str, Any]:
82
+ """Tool for refusing to answer inappropriate content."""
83
+ return {
84
+ "type": "function",
85
+ "function": {
86
+ "name": "refuse_answer",
87
+ "description": "违规内容拒绝回答",
88
+ "parameters": {
89
+ "type": "object",
90
+ "properties": {
91
+ "reason": {"type": "string", "description": "拒绝回答的原因(展示给用户)"},
92
+ },
93
+ "required": ["reason"],
94
+ },
95
+ },
96
+ }
97
+
98
+
99
+ def get_web_search_tool() -> Dict[str, Any]:
100
+ """Tool for searching the web."""
101
+ return {
102
+ "type": "function",
103
+ "function": {
104
+ "name": "internal_web_search",
105
+ "description": "网络搜索",
106
+ "parameters": {
107
+ "type": "object",
108
+ "properties": {"query": {"type": "string"}},
109
+ "required": ["query"],
110
+ },
111
+ },
112
+ }
113
+
114
+
115
+ def get_crawl_page_tool() -> Dict[str, Any]:
116
+ """Tool for crawling a web page."""
117
+ return {
118
+ "type": "function",
119
+ "function": {
120
+ "name": "crawl_page",
121
+ "description": "抓取网页并返回 Markdown 文本 / 网页截图",
122
+ "parameters": {
123
+ "type": "object",
124
+ "properties": {
125
+ "url": {"type": "string"},
126
+ },
127
+ "required": ["url"],
128
+ },
129
+ },
130
+ }