Spaces:
Running
Running
Upload app.py
Browse files
app.py
CHANGED
|
@@ -181,13 +181,13 @@ class SimpleBrowser:
|
|
| 181 |
# Prioritize link_map as it stores search result metadata
|
| 182 |
if cursor in self.link_map:
|
| 183 |
return self.link_map[cursor]
|
| 184 |
-
|
| 185 |
# Fallback to page_stack for opened pages
|
| 186 |
if 0 <= cursor < len(self.page_stack):
|
| 187 |
url = self.page_stack[cursor]
|
| 188 |
page = self.pages.get(url)
|
| 189 |
if page:
|
| 190 |
-
return {'url': url, 'title': page.get('title', '')}
|
| 191 |
return None
|
| 192 |
|
| 193 |
def _format_line_numbers(self, text: str, offset: int = 0) -> str:
|
|
@@ -213,8 +213,9 @@ class SimpleBrowser:
|
|
| 213 |
except:
|
| 214 |
domain = ''
|
| 215 |
|
| 216 |
-
|
| 217 |
-
link_map[i] = {'url': url, 'title': title}
|
|
|
|
| 218 |
link_text = f"【{i}†{title}†{domain}】" if domain else f"【{i}†{title}】"
|
| 219 |
lines.append(f"{link_text}")
|
| 220 |
lines.append(f" {snippet}")
|
|
@@ -485,7 +486,7 @@ def is_final_answer(text: str) -> bool:
|
|
| 485 |
# HTML Rendering Helpers (From app_local.py)
|
| 486 |
# ============================================================
|
| 487 |
def render_citations(text: str, browser: SimpleBrowser) -> str:
|
| 488 |
-
"""Convert citation markers to clickable HTML links."""
|
| 489 |
def replace_citation(m):
|
| 490 |
cursor_str = m.group(1)
|
| 491 |
# l1 = m.group(2)
|
|
@@ -494,15 +495,30 @@ def render_citations(text: str, browser: SimpleBrowser) -> str:
|
|
| 494 |
try:
|
| 495 |
cursor = int(cursor_str)
|
| 496 |
index = browser.get_citation_index(cursor)
|
| 497 |
-
|
| 498 |
# Check if we have URL info
|
| 499 |
info = browser.get_page_info(cursor)
|
| 500 |
if info and info.get('url'):
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
# Fallback if no URL
|
| 507 |
return f'<span class="citation-link">[{index}]</span>'
|
| 508 |
except Exception as e:
|
|
@@ -512,12 +528,13 @@ def render_citations(text: str, browser: SimpleBrowser) -> str:
|
|
| 512 |
|
| 513 |
# First pass: replace citations with linked citations
|
| 514 |
result = re.sub(r'[【\[](\d+)†.*?[】\]]', replace_citation, text)
|
| 515 |
-
|
| 516 |
# Second pass: Deduplicate adjacent identical citations
|
| 517 |
-
# Matches: <
|
| 518 |
-
# We
|
| 519 |
while True:
|
| 520 |
-
|
|
|
|
| 521 |
if new_result == result:
|
| 522 |
break
|
| 523 |
result = new_result
|
|
@@ -1479,7 +1496,95 @@ def create_interface():
|
|
| 1479 |
.answer-section a:hover {
|
| 1480 |
text-decoration: underline;
|
| 1481 |
}
|
| 1482 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1483 |
/* User Message Bubble - 淡蓝色背景,右对齐 */
|
| 1484 |
.user-message-bubble {
|
| 1485 |
background: linear-gradient(135deg, #e0f2fe 0%, #bae6fd 100%);
|
|
@@ -2066,6 +2171,39 @@ def create_interface():
|
|
| 2066 |
color: #f0f9ff !important;
|
| 2067 |
}
|
| 2068 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2069 |
/* 用户问题气泡深色模式 */
|
| 2070 |
.user-message-bubble {
|
| 2071 |
background: linear-gradient(135deg, #1e3a5f 0%, #1e40af 100%) !important;
|
|
@@ -2198,6 +2336,39 @@ def create_interface():
|
|
| 2198 |
color: #e0f2fe !important;
|
| 2199 |
}
|
| 2200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2201 |
.dark .search-result-card {
|
| 2202 |
background: #1f2937 !important;
|
| 2203 |
border-color: #374151 !important;
|
|
|
|
| 181 |
# Prioritize link_map as it stores search result metadata
|
| 182 |
if cursor in self.link_map:
|
| 183 |
return self.link_map[cursor]
|
| 184 |
+
|
| 185 |
# Fallback to page_stack for opened pages
|
| 186 |
if 0 <= cursor < len(self.page_stack):
|
| 187 |
url = self.page_stack[cursor]
|
| 188 |
page = self.pages.get(url)
|
| 189 |
if page:
|
| 190 |
+
return {'url': url, 'title': page.get('title', ''), 'snippet': ''}
|
| 191 |
return None
|
| 192 |
|
| 193 |
def _format_line_numbers(self, text: str, offset: int = 0) -> str:
|
|
|
|
| 213 |
except:
|
| 214 |
domain = ''
|
| 215 |
|
| 216 |
+
# Store snippet information as well
|
| 217 |
+
self.link_map[i] = {'url': url, 'title': title, 'snippet': snippet}
|
| 218 |
+
link_map[i] = {'url': url, 'title': title, 'snippet': snippet}
|
| 219 |
link_text = f"【{i}†{title}†{domain}】" if domain else f"【{i}†{title}】"
|
| 220 |
lines.append(f"{link_text}")
|
| 221 |
lines.append(f" {snippet}")
|
|
|
|
| 486 |
# HTML Rendering Helpers (From app_local.py)
|
| 487 |
# ============================================================
|
| 488 |
def render_citations(text: str, browser: SimpleBrowser) -> str:
|
| 489 |
+
"""Convert citation markers to clickable HTML links with tooltips."""
|
| 490 |
def replace_citation(m):
|
| 491 |
cursor_str = m.group(1)
|
| 492 |
# l1 = m.group(2)
|
|
|
|
| 495 |
try:
|
| 496 |
cursor = int(cursor_str)
|
| 497 |
index = browser.get_citation_index(cursor)
|
| 498 |
+
|
| 499 |
# Check if we have URL info
|
| 500 |
info = browser.get_page_info(cursor)
|
| 501 |
if info and info.get('url'):
|
| 502 |
+
url = info.get('url', '')
|
| 503 |
+
title = info.get('title', 'No Title')
|
| 504 |
+
snippet = info.get('snippet', '')
|
| 505 |
+
|
| 506 |
+
# Unescape HTML entities for display (they were escaped in _clean_links)
|
| 507 |
+
title_display = title
|
| 508 |
+
snippet_display = snippet if snippet else 'No description available'
|
| 509 |
+
|
| 510 |
+
# Truncate URL for display
|
| 511 |
+
url_display = url if len(url) <= 60 else url[:57] + '...'
|
| 512 |
+
|
| 513 |
+
# Create citation with tooltip
|
| 514 |
+
tooltip_html = f'''<span class="citation-tooltip">
|
| 515 |
+
<div class="citation-tooltip-title">{title_display}</div>
|
| 516 |
+
<div class="citation-tooltip-snippet">{snippet_display}</div>
|
| 517 |
+
<div class="citation-tooltip-url">{html.escape(url_display)}</div>
|
| 518 |
+
</span>'''
|
| 519 |
+
|
| 520 |
+
return f'<span class="citation-wrapper"><a href="{html.escape(url)}" target="_blank" class="citation-link">[{index}]</a>{tooltip_html}</span>'
|
| 521 |
+
|
| 522 |
# Fallback if no URL
|
| 523 |
return f'<span class="citation-link">[{index}]</span>'
|
| 524 |
except Exception as e:
|
|
|
|
| 528 |
|
| 529 |
# First pass: replace citations with linked citations
|
| 530 |
result = re.sub(r'[【\[](\d+)†.*?[】\]]', replace_citation, text)
|
| 531 |
+
|
| 532 |
# Second pass: Deduplicate adjacent identical citations
|
| 533 |
+
# Matches: <span class="citation-wrapper">...</span> followed by optional whitespace and same wrapper
|
| 534 |
+
# We need to be more careful here with the new structure
|
| 535 |
while True:
|
| 536 |
+
# Match citation wrapper and deduplicate
|
| 537 |
+
new_result = re.sub(r'(<span class="citation-wrapper">.*?</span>)(\s*)\1', r'\1', result)
|
| 538 |
if new_result == result:
|
| 539 |
break
|
| 540 |
result = new_result
|
|
|
|
| 1496 |
.answer-section a:hover {
|
| 1497 |
text-decoration: underline;
|
| 1498 |
}
|
| 1499 |
+
|
| 1500 |
+
/* Citation Tooltip - 小便签弹窗 */
|
| 1501 |
+
.citation-wrapper {
|
| 1502 |
+
position: relative;
|
| 1503 |
+
display: inline-block;
|
| 1504 |
+
}
|
| 1505 |
+
|
| 1506 |
+
.citation-link {
|
| 1507 |
+
color: #10a37f;
|
| 1508 |
+
text-decoration: none;
|
| 1509 |
+
font-weight: 600;
|
| 1510 |
+
padding: 2px 4px;
|
| 1511 |
+
border-radius: 4px;
|
| 1512 |
+
background: #e6f7f1;
|
| 1513 |
+
transition: all 0.2s ease;
|
| 1514 |
+
cursor: pointer;
|
| 1515 |
+
border: 1px solid #10a37f30;
|
| 1516 |
+
}
|
| 1517 |
+
|
| 1518 |
+
.citation-link:hover {
|
| 1519 |
+
background: #10a37f;
|
| 1520 |
+
color: white;
|
| 1521 |
+
text-decoration: none;
|
| 1522 |
+
}
|
| 1523 |
+
|
| 1524 |
+
.citation-tooltip {
|
| 1525 |
+
visibility: hidden;
|
| 1526 |
+
opacity: 0;
|
| 1527 |
+
position: absolute;
|
| 1528 |
+
bottom: 125%;
|
| 1529 |
+
left: 50%;
|
| 1530 |
+
transform: translateX(-50%);
|
| 1531 |
+
z-index: 1000;
|
| 1532 |
+
width: 280px;
|
| 1533 |
+
background: linear-gradient(135deg, #ffffff 0%, #f9fafb 100%);
|
| 1534 |
+
border: 2px solid #10a37f;
|
| 1535 |
+
border-radius: 12px;
|
| 1536 |
+
padding: 12px 14px;
|
| 1537 |
+
box-shadow: 0 8px 24px rgba(16, 163, 127, 0.25), 0 4px 8px rgba(0, 0, 0, 0.1);
|
| 1538 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 1539 |
+
pointer-events: none;
|
| 1540 |
+
}
|
| 1541 |
+
|
| 1542 |
+
.citation-tooltip::after {
|
| 1543 |
+
content: "";
|
| 1544 |
+
position: absolute;
|
| 1545 |
+
top: 100%;
|
| 1546 |
+
left: 50%;
|
| 1547 |
+
transform: translateX(-50%);
|
| 1548 |
+
border: 8px solid transparent;
|
| 1549 |
+
border-top-color: #10a37f;
|
| 1550 |
+
}
|
| 1551 |
+
|
| 1552 |
+
.citation-wrapper:hover .citation-tooltip {
|
| 1553 |
+
visibility: visible;
|
| 1554 |
+
opacity: 1;
|
| 1555 |
+
bottom: 130%;
|
| 1556 |
+
}
|
| 1557 |
+
|
| 1558 |
+
.citation-tooltip-title {
|
| 1559 |
+
font-weight: 600;
|
| 1560 |
+
color: #1e293b;
|
| 1561 |
+
font-size: 0.9rem;
|
| 1562 |
+
margin-bottom: 8px;
|
| 1563 |
+
line-height: 1.4;
|
| 1564 |
+
border-bottom: 1px solid #e5e7eb;
|
| 1565 |
+
padding-bottom: 6px;
|
| 1566 |
+
}
|
| 1567 |
+
|
| 1568 |
+
.citation-tooltip-snippet {
|
| 1569 |
+
color: #475569;
|
| 1570 |
+
font-size: 0.8rem;
|
| 1571 |
+
line-height: 1.5;
|
| 1572 |
+
margin-bottom: 8px;
|
| 1573 |
+
max-height: 80px;
|
| 1574 |
+
overflow-y: auto;
|
| 1575 |
+
}
|
| 1576 |
+
|
| 1577 |
+
.citation-tooltip-url {
|
| 1578 |
+
color: #10a37f;
|
| 1579 |
+
font-size: 0.75rem;
|
| 1580 |
+
font-family: 'SF Mono', Monaco, monospace;
|
| 1581 |
+
word-break: break-all;
|
| 1582 |
+
background: #f0fdf4;
|
| 1583 |
+
padding: 4px 6px;
|
| 1584 |
+
border-radius: 4px;
|
| 1585 |
+
border: 1px solid #10a37f20;
|
| 1586 |
+
}
|
| 1587 |
+
|
| 1588 |
/* User Message Bubble - 淡蓝色背景,右对齐 */
|
| 1589 |
.user-message-bubble {
|
| 1590 |
background: linear-gradient(135deg, #e0f2fe 0%, #bae6fd 100%);
|
|
|
|
| 2171 |
color: #f0f9ff !important;
|
| 2172 |
}
|
| 2173 |
|
| 2174 |
+
/* Citation Tooltip 深色模式 */
|
| 2175 |
+
.citation-link {
|
| 2176 |
+
background: #064e3b !important;
|
| 2177 |
+
border-color: #10a37f80 !important;
|
| 2178 |
+
color: #6ee7b7 !important;
|
| 2179 |
+
}
|
| 2180 |
+
|
| 2181 |
+
.citation-link:hover {
|
| 2182 |
+
background: #10a37f !important;
|
| 2183 |
+
color: #ffffff !important;
|
| 2184 |
+
}
|
| 2185 |
+
|
| 2186 |
+
.citation-tooltip {
|
| 2187 |
+
background: linear-gradient(135deg, #1f2937 0%, #111827 100%) !important;
|
| 2188 |
+
border-color: #10a37f !important;
|
| 2189 |
+
box-shadow: 0 8px 24px rgba(16, 163, 127, 0.4), 0 4px 8px rgba(0, 0, 0, 0.3) !important;
|
| 2190 |
+
}
|
| 2191 |
+
|
| 2192 |
+
.citation-tooltip-title {
|
| 2193 |
+
color: #e5e7eb !important;
|
| 2194 |
+
border-bottom-color: #374151 !important;
|
| 2195 |
+
}
|
| 2196 |
+
|
| 2197 |
+
.citation-tooltip-snippet {
|
| 2198 |
+
color: #d1d5db !important;
|
| 2199 |
+
}
|
| 2200 |
+
|
| 2201 |
+
.citation-tooltip-url {
|
| 2202 |
+
color: #6ee7b7 !important;
|
| 2203 |
+
background: #064e3b !important;
|
| 2204 |
+
border-color: #10a37f40 !important;
|
| 2205 |
+
}
|
| 2206 |
+
|
| 2207 |
/* 用户问题气泡深色模式 */
|
| 2208 |
.user-message-bubble {
|
| 2209 |
background: linear-gradient(135deg, #1e3a5f 0%, #1e40af 100%) !important;
|
|
|
|
| 2336 |
color: #e0f2fe !important;
|
| 2337 |
}
|
| 2338 |
|
| 2339 |
+
/* Citation Tooltip Gradio dark 模式 */
|
| 2340 |
+
.dark .citation-link {
|
| 2341 |
+
background: #064e3b !important;
|
| 2342 |
+
border-color: #10a37f80 !important;
|
| 2343 |
+
color: #6ee7b7 !important;
|
| 2344 |
+
}
|
| 2345 |
+
|
| 2346 |
+
.dark .citation-link:hover {
|
| 2347 |
+
background: #10a37f !important;
|
| 2348 |
+
color: #ffffff !important;
|
| 2349 |
+
}
|
| 2350 |
+
|
| 2351 |
+
.dark .citation-tooltip {
|
| 2352 |
+
background: linear-gradient(135deg, #1f2937 0%, #111827 100%) !important;
|
| 2353 |
+
border-color: #10a37f !important;
|
| 2354 |
+
box-shadow: 0 8px 24px rgba(16, 163, 127, 0.4), 0 4px 8px rgba(0, 0, 0, 0.3) !important;
|
| 2355 |
+
}
|
| 2356 |
+
|
| 2357 |
+
.dark .citation-tooltip-title {
|
| 2358 |
+
color: #e5e7eb !important;
|
| 2359 |
+
border-bottom-color: #374151 !important;
|
| 2360 |
+
}
|
| 2361 |
+
|
| 2362 |
+
.dark .citation-tooltip-snippet {
|
| 2363 |
+
color: #d1d5db !important;
|
| 2364 |
+
}
|
| 2365 |
+
|
| 2366 |
+
.dark .citation-tooltip-url {
|
| 2367 |
+
color: #6ee7b7 !important;
|
| 2368 |
+
background: #064e3b !important;
|
| 2369 |
+
border-color: #10a37f40 !important;
|
| 2370 |
+
}
|
| 2371 |
+
|
| 2372 |
.dark .search-result-card {
|
| 2373 |
background: #1f2937 !important;
|
| 2374 |
border-color: #374151 !important;
|