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.

@@ -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-muted': '#4b5563', // gray-600, higher contrast for mobile
130
- '--border-color': '#e5e7eb', // gray-200, for borders
131
- '--bg-subtle': '#f9fafb' // gray-50, for subtle backgrounds
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="min-h-screen bg-[#f2f2f2] flex justify-center" :style="themeStyle">
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-28 space-y-8 !bg-[#f2f2f2]" data-theme="light">
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 text-gray-800 leading-tight tracking-tighter uppercase tabular-nums" v-html="processedTitle"></h1>
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 text-gray-700 font-medium bg-white">
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(/\[(\d+)\]/g, (_, n) => {
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-1 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>`
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:text-gray-800 prose-headings:font-bold prose-headings:mb-3 prose-headings:mt-8 prose-headings:tracking-tight
256
- prose-p:text-gray-800 prose-p:leading-7 prose-p:my-4 prose-p:text-[20px] prose-li:text-[20px] prose-li:text-gray-800
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 prose-code:text-gray-800
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: #1f2937 !important; /* gray-800 */
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="grid grid-cols-2 gap-2 items-start">
153
- <a v-for="(img, idx) in stage.image_references" :key="idx"
154
- v-show="!failedImages[img.url]"
155
- :href="img.url" target="_blank"
156
- class="relative overflow-hidden transition-all hover:opacity-90 group">
157
- <img :src="img.thumbnail || img.url"
158
- @error="handleImageError(img.url)"
159
- class="w-full h-auto block group-hover:scale-[1.02] transition-transform">
160
- </a>
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
  }
@@ -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
- async with httpx.AsyncClient(timeout=self.timeout, follow_redirects=True) as client:
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, wait_timeout: float = 3.0) -> str:
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 asyncio.wait_for(asyncio.shield(pending_task), timeout=wait_timeout)
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.TimeoutError:
185
- logger.debug(f"ImageCache: Timeout waiting for {url}")
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], wait_timeout: float = 3.0) -> Dict[str, 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.wait_for(
215
- asyncio.gather(*pending_tasks, return_exceptions=True),
216
- timeout=wait_timeout
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], wait_timeout: float = 3.0) -> Dict[str, 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, wait_timeout=wait_timeout)
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
+