entari-plugin-hyw 3.5.0rc3__py3-none-any.whl → 3.5.0rc4__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 +22 -14
- entari_plugin_hyw/assets/card-dist/index.html +46 -46
- entari_plugin_hyw/card-ui/src/App.vue +8 -6
- entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +8 -7
- entari_plugin_hyw/card-ui/src/components/StageCard.vue +71 -17
- entari_plugin_hyw/card-ui/src/style.css +21 -0
- entari_plugin_hyw/image_cache.py +15 -24
- entari_plugin_hyw/misc.py +38 -0
- entari_plugin_hyw/pipeline.py +140 -33
- entari_plugin_hyw/prompts.py +4 -0
- entari_plugin_hyw/search.py +2 -2
- {entari_plugin_hyw-3.5.0rc3.dist-info → entari_plugin_hyw-3.5.0rc4.dist-info}/METADATA +1 -1
- {entari_plugin_hyw-3.5.0rc3.dist-info → entari_plugin_hyw-3.5.0rc4.dist-info}/RECORD +15 -15
- {entari_plugin_hyw-3.5.0rc3.dist-info → entari_plugin_hyw-3.5.0rc4.dist-info}/WHEEL +0 -0
- {entari_plugin_hyw-3.5.0rc3.dist-info → entari_plugin_hyw-3.5.0rc4.dist-info}/top_level.txt +0 -0
|
@@ -126,9 +126,11 @@ const headerTextColor = computed(() => {
|
|
|
126
126
|
const themeStyle = computed(() => ({
|
|
127
127
|
'--theme-color': themeColor.value,
|
|
128
128
|
'--header-text-color': headerTextColor.value,
|
|
129
|
-
'--text-
|
|
130
|
-
'--
|
|
131
|
-
'--
|
|
129
|
+
'--text-primary': '#2c2c2e', // Warm dark gray for headings (Apple HIG inspired)
|
|
130
|
+
'--text-body': '#3a3a3c', // Softer reading color for body text
|
|
131
|
+
'--text-muted': '#636366', // Muted secondary text
|
|
132
|
+
'--border-color': '#e5e7eb', // gray-200, for borders
|
|
133
|
+
'--bg-subtle': '#f9fafb' // gray-50, for subtle backgrounds
|
|
132
134
|
}))
|
|
133
135
|
|
|
134
136
|
|
|
@@ -328,14 +330,14 @@ java -Xmx1024M -Xms1024M -jar prism-launcher.jar
|
|
|
328
330
|
</script>
|
|
329
331
|
|
|
330
332
|
<template>
|
|
331
|
-
<div class="
|
|
333
|
+
<div class="bg-[#f2f2f2] flex justify-center" :style="themeStyle">
|
|
332
334
|
<!-- Main container with explicit background for screenshot capture -->
|
|
333
|
-
<div id="main-container" class="w-[540px] pt-16 pb-
|
|
335
|
+
<div id="main-container" class="w-[540px] pt-16 pb-12 space-y-8 !bg-[#f2f2f2]" data-theme="light">
|
|
334
336
|
|
|
335
337
|
<!-- Title -->
|
|
336
338
|
<header v-if="mainTitle" class="px-6 mb-8">
|
|
337
339
|
<!-- Removed Time/Icon Badge as requested -->
|
|
338
|
-
<h1 class="text-4xl font-black
|
|
340
|
+
<h1 class="text-4xl font-black leading-tight tracking-tighter uppercase tabular-nums" style="color: var(--text-primary)" v-html="processedTitle"></h1>
|
|
339
341
|
</header>
|
|
340
342
|
|
|
341
343
|
<!-- Content Sections -->
|
|
@@ -163,7 +163,7 @@ const processedHtml = computed(() => {
|
|
|
163
163
|
return `
|
|
164
164
|
<div class="my-8 group shadow-sm shadow-black/10">
|
|
165
165
|
<div class="h-4 w-full" style="background-color: var(--theme-color);"></div>
|
|
166
|
-
<div class="p-6 text-[19px] leading-relaxed
|
|
166
|
+
<div class="p-6 text-[19px] leading-relaxed font-medium bg-white" style="color: var(--text-body)">
|
|
167
167
|
${content}
|
|
168
168
|
</div>
|
|
169
169
|
</div>
|
|
@@ -235,9 +235,9 @@ const processedHtml = computed(() => {
|
|
|
235
235
|
})
|
|
236
236
|
|
|
237
237
|
// Convert [N] citations to small square badges with shadow
|
|
238
|
-
html = html.replace(
|
|
238
|
+
html = html.replace(/(\s*)\[(\d+)\]/g, (_, _space, n) => {
|
|
239
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-
|
|
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
241
|
})
|
|
242
242
|
|
|
243
243
|
// Style <u> underline tags with theme-colored solid underline
|
|
@@ -252,15 +252,16 @@ const processedHtml = computed(() => {
|
|
|
252
252
|
<template>
|
|
253
253
|
<div ref="contentRef"
|
|
254
254
|
class="prose prose-slate max-w-none prose-lg
|
|
255
|
-
prose-headings:
|
|
256
|
-
prose-p:
|
|
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
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
|
|
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
259
|
prose-pre:bg-gray-50 prose-pre:border prose-pre:border-gray-200 prose-pre:rounded-none prose-pre:p-0
|
|
260
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
261
|
prose-ol:list-decimal prose-ol:pl-7 prose-ol:list-outside prose-ol:my-5
|
|
262
262
|
prose-li:my-2.5 prose-li:leading-7
|
|
263
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)"
|
|
264
265
|
v-html="processedHtml">
|
|
265
266
|
</div>
|
|
266
267
|
</template>
|
|
@@ -326,7 +327,7 @@ const processedHtml = computed(() => {
|
|
|
326
327
|
margin-left: 0 !important;
|
|
327
328
|
position: relative !important;
|
|
328
329
|
font-style: italic !important;
|
|
329
|
-
color: #
|
|
330
|
+
color: var(--text-body, #3a3a3c) !important; /* Premium reading color */
|
|
330
331
|
}
|
|
331
332
|
|
|
332
333
|
.prose blockquote::before {
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref } from 'vue'
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
3
|
import { Icon } from '@iconify/vue'
|
|
4
4
|
import type { Stage } from '../types'
|
|
5
5
|
|
|
6
|
-
const failedImages = ref<Record<string, boolean>>({})
|
|
7
|
-
|
|
8
|
-
function handleImageError(url: string) {
|
|
9
|
-
failedImages.value[url] = true
|
|
10
|
-
}
|
|
11
|
-
|
|
12
6
|
const props = defineProps<{
|
|
13
7
|
stage: Stage
|
|
14
8
|
isFirst?: boolean
|
|
@@ -17,6 +11,52 @@ const props = defineProps<{
|
|
|
17
11
|
refOffset?: number
|
|
18
12
|
}>()
|
|
19
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
|
+
|
|
20
60
|
function getDomain(url: string): string {
|
|
21
61
|
try {
|
|
22
62
|
const urlObj = new URL(url)
|
|
@@ -147,17 +187,31 @@ function getModelLogo(model: string): string | undefined {
|
|
|
147
187
|
</a>
|
|
148
188
|
</div>
|
|
149
189
|
|
|
150
|
-
<!-- Image Search Results -->
|
|
190
|
+
<!-- Image Search Results - True Masonry Layout -->
|
|
151
191
|
<div v-if="stage.image_references?.length" class="pr-3 py-3 relative z-10">
|
|
152
|
-
<div class="
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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>
|
|
161
215
|
</div>
|
|
162
216
|
</div>
|
|
163
217
|
|
|
@@ -5,4 +5,25 @@
|
|
|
5
5
|
/* Custom styles */
|
|
6
6
|
body {
|
|
7
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;
|
|
8
29
|
}
|
entari_plugin_hyw/image_cache.py
CHANGED
|
@@ -32,11 +32,9 @@ class ImageCache:
|
|
|
32
32
|
def __init__(
|
|
33
33
|
self,
|
|
34
34
|
max_size_kb: int = 500, # Max image size to cache (KB)
|
|
35
|
-
timeout: float = 5.0, # Download timeout per image
|
|
36
35
|
max_concurrent: int = 6, # Max concurrent downloads
|
|
37
36
|
):
|
|
38
37
|
self.max_size_bytes = max_size_kb * 1024
|
|
39
|
-
self.timeout = timeout
|
|
40
38
|
self.max_concurrent = max_concurrent
|
|
41
39
|
|
|
42
40
|
# Cache storage: url -> base64_data_url or None (if failed)
|
|
@@ -78,7 +76,8 @@ class ImageCache:
|
|
|
78
76
|
"""
|
|
79
77
|
async with self._semaphore:
|
|
80
78
|
try:
|
|
81
|
-
|
|
79
|
+
# No timeout - images download until agent ends
|
|
80
|
+
async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
|
|
82
81
|
resp = await client.get(url, headers={
|
|
83
82
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
|
|
84
83
|
})
|
|
@@ -141,8 +140,6 @@ class ImageCache:
|
|
|
141
140
|
logger.debug(f"ImageCache: Cached {url} ({len(content)} bytes)")
|
|
142
141
|
return data_url
|
|
143
142
|
|
|
144
|
-
except asyncio.TimeoutError:
|
|
145
|
-
logger.debug(f"ImageCache: Timeout downloading {url}")
|
|
146
143
|
except Exception as e:
|
|
147
144
|
logger.debug(f"ImageCache: Failed to download {url}: {e}")
|
|
148
145
|
|
|
@@ -151,14 +148,13 @@ class ImageCache:
|
|
|
151
148
|
self._pending.pop(url, None)
|
|
152
149
|
return None
|
|
153
150
|
|
|
154
|
-
async def get_cached(self, url: str, wait: bool = True
|
|
151
|
+
async def get_cached(self, url: str, wait: bool = True) -> str:
|
|
155
152
|
"""
|
|
156
153
|
Get cached image data URL, or original URL if not cached.
|
|
157
154
|
|
|
158
155
|
Args:
|
|
159
156
|
url: Original image URL
|
|
160
|
-
wait: If True, wait for pending download to complete
|
|
161
|
-
wait_timeout: Max time to wait for pending download
|
|
157
|
+
wait: If True, wait for pending download to complete (no timeout - waits until agent ends)
|
|
162
158
|
|
|
163
159
|
Returns:
|
|
164
160
|
Cached data URL or original URL
|
|
@@ -174,35 +170,34 @@ class ImageCache:
|
|
|
174
170
|
|
|
175
171
|
pending_task = self._pending.get(url)
|
|
176
172
|
|
|
177
|
-
# Wait for pending download if requested
|
|
173
|
+
# Wait for pending download if requested (no timeout - waits until cancelled)
|
|
178
174
|
if pending_task and wait:
|
|
179
175
|
try:
|
|
180
|
-
await
|
|
176
|
+
await pending_task
|
|
181
177
|
async with self._lock:
|
|
182
178
|
cached = self._cache.get(url)
|
|
183
179
|
return cached if cached else url
|
|
184
|
-
except asyncio.
|
|
185
|
-
logger.debug(f"ImageCache:
|
|
180
|
+
except asyncio.CancelledError:
|
|
181
|
+
logger.debug(f"ImageCache: Download cancelled for {url}")
|
|
186
182
|
return url
|
|
187
183
|
except Exception:
|
|
188
184
|
return url
|
|
189
185
|
|
|
190
186
|
return url
|
|
191
187
|
|
|
192
|
-
async def get_all_cached(self, urls: List[str]
|
|
188
|
+
async def get_all_cached(self, urls: List[str]) -> Dict[str, str]:
|
|
193
189
|
"""
|
|
194
190
|
Get cached URLs for multiple images.
|
|
195
191
|
|
|
196
192
|
Args:
|
|
197
193
|
urls: List of original URLs
|
|
198
|
-
wait_timeout: Max time to wait for all pending downloads
|
|
199
194
|
|
|
200
195
|
Returns:
|
|
201
196
|
Dict mapping original URL to cached data URL (or original if not cached)
|
|
202
197
|
"""
|
|
203
198
|
result = {}
|
|
204
199
|
|
|
205
|
-
# Wait for all pending downloads first
|
|
200
|
+
# Wait for all pending downloads first (no timeout - waits until cancelled)
|
|
206
201
|
pending_tasks = []
|
|
207
202
|
async with self._lock:
|
|
208
203
|
for url in urls:
|
|
@@ -211,12 +206,9 @@ class ImageCache:
|
|
|
211
206
|
|
|
212
207
|
if pending_tasks:
|
|
213
208
|
try:
|
|
214
|
-
await asyncio.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
)
|
|
218
|
-
except asyncio.TimeoutError:
|
|
219
|
-
logger.debug(f"ImageCache: Timeout waiting for batch download")
|
|
209
|
+
await asyncio.gather(*pending_tasks, return_exceptions=True)
|
|
210
|
+
except asyncio.CancelledError:
|
|
211
|
+
logger.debug(f"ImageCache: Batch download cancelled")
|
|
220
212
|
|
|
221
213
|
# Collect results
|
|
222
214
|
for url in urls:
|
|
@@ -268,16 +260,15 @@ async def prefetch_images(urls: List[str]) -> None:
|
|
|
268
260
|
cache.start_prefetch(urls)
|
|
269
261
|
|
|
270
262
|
|
|
271
|
-
async def get_cached_images(urls: List[str]
|
|
263
|
+
async def get_cached_images(urls: List[str]) -> Dict[str, str]:
|
|
272
264
|
"""
|
|
273
265
|
Convenience function to get cached images.
|
|
274
266
|
|
|
275
267
|
Args:
|
|
276
268
|
urls: List of original URLs
|
|
277
|
-
wait_timeout: Max time to wait
|
|
278
269
|
|
|
279
270
|
Returns:
|
|
280
271
|
Dict mapping original URL to cached data URL
|
|
281
272
|
"""
|
|
282
273
|
cache = get_image_cache()
|
|
283
|
-
return await cache.get_all_cached(urls
|
|
274
|
+
return await cache.get_all_cached(urls)
|
entari_plugin_hyw/misc.py
CHANGED
|
@@ -88,3 +88,41 @@ def resolve_model_name(name: str, models_config: List[Dict[str, Any]]) -> Tuple[
|
|
|
88
88
|
|
|
89
89
|
# Default: assume it's a valid ID passed directly
|
|
90
90
|
return name, None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Hardcoded markdown for refuse answer
|
|
94
|
+
REFUSE_ANSWER_MARKDOWN = """
|
|
95
|
+
<summary>
|
|
96
|
+
Instruct 专家分配此任务流程失败,请尝试提出其他问题~
|
|
97
|
+
</summary>
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def render_refuse_answer(
|
|
102
|
+
renderer,
|
|
103
|
+
output_path: str,
|
|
104
|
+
theme_color: str = "#ef4444",
|
|
105
|
+
) -> bool:
|
|
106
|
+
"""
|
|
107
|
+
Render a refuse-to-answer image using hardcoded markdown.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
renderer: ContentRenderer instance
|
|
111
|
+
output_path: Path to save the output image
|
|
112
|
+
theme_color: Theme color for the card
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
True if render succeeded, False otherwise
|
|
116
|
+
"""
|
|
117
|
+
return await renderer.render(
|
|
118
|
+
markdown_content=REFUSE_ANSWER_MARKDOWN,
|
|
119
|
+
output_path=output_path,
|
|
120
|
+
stats={},
|
|
121
|
+
references=[],
|
|
122
|
+
page_references=[],
|
|
123
|
+
image_references=[],
|
|
124
|
+
stages_used=[],
|
|
125
|
+
image_timeout=1000, # No images to load
|
|
126
|
+
theme_color=theme_color,
|
|
127
|
+
)
|
|
128
|
+
|