fix: link score citations to their source text pages
This commit is contained in:
@@ -106,17 +106,20 @@ function CitationCards({ items }: { items: any[] }) {
|
||||
const page = item.page ?? item.slide; const place = page == null ? '' : `第 ${page} ${/\.pptx?$/i.test(name) ? '张幻灯片' : '页'}`;
|
||||
const quote = typeof item.quote === 'string' ? item.quote.trim() : '';
|
||||
const sourceHref = safeUrl(item.url);
|
||||
const imageHref = safeUrl(item.image_url);
|
||||
const textHref = safeUrl(item.text_url);
|
||||
const imageHref = safeUrl(item.image_url) || (sourceHref && isImageUrl(sourceHref) ? sourceHref : undefined);
|
||||
return <div className="citation-card" key={`${name}-${page}-${index}`}><div className="citation-heading"><Text strong>{name}</Text>{place && <Tag>{place}</Tag>}</div>
|
||||
{quote && (quote.length > 320 ? <details><summary>{quote.slice(0, 240)}… 展开原文</summary><p>{quote}</p></details> : <p>{quote}</p>)}
|
||||
<Space wrap size="small">{sourceHref && <a href={sourceHref} target="_blank" rel="noopener noreferrer">{sourceLabel(sourceHref)}</a>}
|
||||
{imageHref && imageHref !== sourceHref && <a href={imageHref} target="_blank" rel="noopener noreferrer">查看图像</a>}</Space></div>;
|
||||
<Space wrap size="small">{textHref && textHref !== sourceHref && <a href={textHref} target="_blank" rel="noopener noreferrer">查看页面文字</a>}
|
||||
{sourceHref && sourceHref !== imageHref && <a href={sourceHref} target="_blank" rel="noopener noreferrer">{sourceLabel(sourceHref)}</a>}
|
||||
{imageHref && <a href={imageHref} target="_blank" rel="noopener noreferrer">查看图像</a>}</Space></div>;
|
||||
})}</div>;
|
||||
}
|
||||
function Citations({ items }: { items: any[] }) {
|
||||
const seen = new Set<string>();
|
||||
const unique = (items || []).filter(item => { if (!item || typeof item !== 'object') return false;
|
||||
const key = JSON.stringify([item.title || item.document, item.page, item.quote, item.url, item.image_url]);
|
||||
const key = JSON.stringify([item.document_id, item.title || item.document, item.page, item.quote,
|
||||
item.url, item.text_url, item.image_url]);
|
||||
if (seen.has(key)) return false; seen.add(key); return true; });
|
||||
if (!unique.length) return null;
|
||||
return <section className="chat-citations" aria-label="引用资料"><details className="chat-citation-group"><summary><Text strong>引用 · {unique.length}</Text></summary><CitationCards items={unique} /></details></section>;
|
||||
|
||||
@@ -8,8 +8,10 @@ async function mockApi(page: Page) {
|
||||
content: '# 标题\n\n段落 [安全链接](https://example.com) [危险链接](javascript:alert(1)) <img src=x onerror=alert(1)>\n\n> 引用\n\n- 列表\n\n| A | B |\n|---|---|\n| 1 | 2 |\n\n```js\nconst x = 1;\n```',
|
||||
citations: [{ title: '资料.pdf', page: 2, quote: '原文摘录', url: '/api/knowledge/documents/10/pages/2', image_url: '/api/assets/5/2' },
|
||||
{ title: '资料.pdf', page: 2, quote: '原文摘录', url: '/api/knowledge/documents/10/pages/2', image_url: '/api/assets/5/2' },
|
||||
{ chunk_id: 2977, title: '综合报告.pdf', page: 48, quote: '第48页原文片段', url: '/api/assets/5/48' },
|
||||
{ title: '评分报告.pdf', page: 49, quote: '评分原文片段', url: '/?page=scores&batch_id=3&project_id=5', image_url: '/api/assets/5/49' },
|
||||
{ chunk_id: 2977, title: '综合报告.pdf', page: 48, quote: '第48页原文片段', url: '/api/assets/5/48', text_url: '/api/knowledge/documents/12/pages/48' },
|
||||
{ title: '仅图像.pdf', page: 50, quote: '未匹配片段', url: '/api/assets/5/50' },
|
||||
{ title: '评分报告.pdf', page: 49, quote: '评分原文片段', url: '/?page=scores&batch_id=3&project_id=5',
|
||||
image_url: '/api/assets/5/49', text_url: '/api/knowledge/documents/12/pages/49' },
|
||||
{ title: '课程资料.pdf', page: 5, quote: '课程说明', url: '/api/knowledge/documents/11/pages/5' }],
|
||||
tool_calls: [{ id: 'call-one', name: 'search_project_material', status: 'completed', arguments: { project_id: 1, omitted_text_fields: 1 },
|
||||
summary: '找到 1 条引用', result: { found: true, citations: [{ page: 48, quote: '第48页原文片段' }] } },
|
||||
@@ -96,7 +98,7 @@ test('markdown, citations, tool folding and narrow layout are safe', async ({ pa
|
||||
await expect(page.locator('.chat-markdown pre')).toBeVisible();
|
||||
await expect(page.locator('.chat-markdown img')).toHaveCount(0);
|
||||
await expect(page.getByText('危险链接')).not.toHaveAttribute('href');
|
||||
await expect(page.getByText('引用 · 4')).toBeVisible();
|
||||
await expect(page.getByText('引用 · 5')).toBeVisible();
|
||||
await expect(page.getByText('资料.pdf', { exact: true })).not.toBeVisible();
|
||||
await expect(page.getByText('后台进度')).toHaveCount(0);
|
||||
await expect(page.locator('.chat-tools')).toBeVisible();
|
||||
@@ -125,9 +127,14 @@ test('markdown, citations, tool folding and narrow layout are safe', async ({ pa
|
||||
await expect(searchCitation.getByRole('link', { name: '查看页面文字' })).toHaveAttribute('href', /\/api\/knowledge\/documents\/10\/pages\/2$/);
|
||||
await expect(searchCitation.getByRole('link', { name: '查看图像' })).toHaveAttribute('href', /\/api\/assets\/5\/2$/);
|
||||
const legacyImage = page.locator('.citation-card').filter({ hasText: '综合报告.pdf' });
|
||||
await expect(legacyImage.getByRole('link', { name: '查看页面文字' })).toHaveAttribute('href', /\/api\/knowledge\/documents\/12\/pages\/48$/);
|
||||
await expect(legacyImage.getByRole('link', { name: '查看图像' })).toHaveAttribute('href', /\/api\/assets\/5\/48$/);
|
||||
await expect(legacyImage.getByRole('link')).toHaveCount(1);
|
||||
await expect(legacyImage.getByRole('link')).toHaveCount(2);
|
||||
const imageOnly = page.locator('.citation-card').filter({ hasText: '仅图像.pdf' });
|
||||
await expect(imageOnly.getByRole('link', { name: '查看图像' })).toBeVisible();
|
||||
await expect(imageOnly.getByRole('link')).toHaveCount(1);
|
||||
const scoreCitation = page.locator('.citation-card').filter({ hasText: '评分报告.pdf' });
|
||||
await expect(scoreCitation.getByRole('link', { name: '查看页面文字' })).toHaveAttribute('href', /\/api\/knowledge\/documents\/12\/pages\/49$/);
|
||||
await expect(scoreCitation.getByRole('link', { name: '查看评分' })).toHaveAttribute('href', /page=scores&batch_id=3&project_id=5$/);
|
||||
await expect(scoreCitation.getByRole('link', { name: '查看图像' })).toHaveAttribute('href', /\/api\/assets\/5\/49$/);
|
||||
const textOnly = page.locator('.citation-card').filter({ hasText: '课程资料.pdf' });
|
||||
|
||||
Reference in New Issue
Block a user