feat(Messages): enhance citations display with improved styling and translation support
- Added a title for the citations list with translation using `useTranslation`. - Introduced an `Info` icon next to the citations title. - Updated the `CitationsContainer` styling for better visual appeal. - Refactored citation rendering logic in `MessageContent` to streamline citation handling.
This commit is contained in:
parent
1bb27ee3f9
commit
c7ed15684a
@ -1,7 +1,8 @@
|
|||||||
import Favicon from '@renderer/components/Icons/FallbackFavicon'
|
import Favicon from '@renderer/components/Icons/FallbackFavicon'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { FileSearch } from 'lucide-react'
|
import { FileSearch, Info } from 'lucide-react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
interface Citation {
|
interface Citation {
|
||||||
@ -19,11 +20,16 @@ interface CitationsListProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CitationsList: React.FC<CitationsListProps> = ({ citations }) => {
|
const CitationsList: React.FC<CitationsListProps> = ({ citations }) => {
|
||||||
console.log('CitationsList', citations)
|
const { t } = useTranslation()
|
||||||
|
|
||||||
if (!citations || citations.length === 0) return null
|
if (!citations || citations.length === 0) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CitationsContainer className="footnotes">
|
<CitationsContainer className="footnotes">
|
||||||
|
<CitationsTitle>
|
||||||
|
<span>{t('message.citations')}</span>
|
||||||
|
<Info size={14} style={{ opacity: 0.6 }} />
|
||||||
|
</CitationsTitle>
|
||||||
{citations.map((citation) => (
|
{citations.map((citation) => (
|
||||||
<HStack key={citation.url || citation.number} style={{ alignItems: 'center', gap: 8 }}>
|
<HStack key={citation.url || citation.number} style={{ alignItems: 'center', gap: 8 }}>
|
||||||
<span style={{ fontSize: 13, color: 'var(--color-text-2)' }}>{citation.number}.</span>
|
<span style={{ fontSize: 13, color: 'var(--color-text-2)' }}>{citation.number}.</span>
|
||||||
@ -83,7 +89,7 @@ const KnowledgeCitation: React.FC<{ citation: Citation }> = ({ citation }) => {
|
|||||||
|
|
||||||
const CitationsContainer = styled.div`
|
const CitationsContainer = styled.div`
|
||||||
background-color: rgb(242, 247, 253);
|
background-color: rgb(242, 247, 253);
|
||||||
border-radius: 4px;
|
border-radius: 10px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -95,6 +101,15 @@ const CitationsContainer = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const CitationsTitle = styled.div`
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
`
|
||||||
|
|
||||||
const CitationLink = styled.a`
|
const CitationLink = styled.a`
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { DownOutlined, InfoCircleOutlined, SyncOutlined, TranslationOutlined, UpOutlined } from '@ant-design/icons'
|
import { SyncOutlined, TranslationOutlined } from '@ant-design/icons'
|
||||||
import { isOpenAIWebSearch } from '@renderer/config/models'
|
import { isOpenAIWebSearch } from '@renderer/config/models'
|
||||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||||
import { Message, Model } from '@renderer/types'
|
import { Message, Model } from '@renderer/types'
|
||||||
@ -7,7 +7,7 @@ import { withMessageThought } from '@renderer/utils/formats'
|
|||||||
import { Divider, Flex } from 'antd'
|
import { Divider, Flex } from 'antd'
|
||||||
import { clone } from 'lodash'
|
import { clone } from 'lodash'
|
||||||
import { Search } from 'lucide-react'
|
import { Search } from 'lucide-react'
|
||||||
import React, { Fragment, useMemo, useState } from 'react'
|
import React, { Fragment, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import BarLoader from 'react-spinners/BarLoader'
|
import BarLoader from 'react-spinners/BarLoader'
|
||||||
import BeatLoader from 'react-spinners/BeatLoader'
|
import BeatLoader from 'react-spinners/BeatLoader'
|
||||||
@ -30,7 +30,6 @@ const MessageContent: React.FC<Props> = ({ message: _message, model }) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const message = withMessageThought(clone(_message))
|
const message = withMessageThought(clone(_message))
|
||||||
const isWebCitation = model && (isOpenAIWebSearch(model) || model.provider === 'openrouter')
|
const isWebCitation = model && (isOpenAIWebSearch(model) || model.provider === 'openrouter')
|
||||||
const [citationsCollapsed, setCitationsCollapsed] = useState(true)
|
|
||||||
|
|
||||||
// HTML实体编码辅助函数
|
// HTML实体编码辅助函数
|
||||||
const encodeHTML = (str: string) => {
|
const encodeHTML = (str: string) => {
|
||||||
@ -140,10 +139,11 @@ const MessageContent: React.FC<Props> = ({ message: _message, model }) => {
|
|||||||
return data
|
return data
|
||||||
}, [
|
}, [
|
||||||
formattedCitations,
|
formattedCitations,
|
||||||
message?.metadata?.annotations,
|
message.metadata?.annotations,
|
||||||
message?.metadata?.groundingMetadata?.groundingChunks,
|
message.metadata?.groundingMetadata?.groundingChunks,
|
||||||
message?.metadata?.webSearch?.results,
|
message.metadata?.knowledge,
|
||||||
message?.metadata?.webSearchInfo
|
message.metadata?.webSearch?.results,
|
||||||
|
message.metadata?.webSearchInfo
|
||||||
])
|
])
|
||||||
|
|
||||||
// Process content to make citation numbers clickable
|
// Process content to make citation numbers clickable
|
||||||
@ -251,83 +251,71 @@ const MessageContent: React.FC<Props> = ({ message: _message, model }) => {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
{hasCitations && (
|
{hasCitations && (
|
||||||
<CitationsContainer>
|
<>
|
||||||
<CitationsHeader onClick={() => setCitationsCollapsed(!citationsCollapsed)}>
|
{message?.metadata?.groundingMetadata && message.status === 'success' && (
|
||||||
<div>
|
<>
|
||||||
{t('message.citations')}
|
<CitationsList
|
||||||
<InfoCircleOutlined style={{ fontSize: '14px', marginLeft: '4px', opacity: 0.6 }} />
|
citations={
|
||||||
</div>
|
message.metadata.groundingMetadata?.groundingChunks?.map((chunk, index) => ({
|
||||||
{citationsCollapsed ? <DownOutlined /> : <UpOutlined />}
|
|
||||||
</CitationsHeader>
|
|
||||||
|
|
||||||
{!citationsCollapsed && (
|
|
||||||
<CitationsContent>
|
|
||||||
{message?.metadata?.groundingMetadata && message.status === 'success' && (
|
|
||||||
<>
|
|
||||||
<CitationsList
|
|
||||||
citations={
|
|
||||||
message.metadata.groundingMetadata?.groundingChunks?.map((chunk, index) => ({
|
|
||||||
number: index + 1,
|
|
||||||
url: chunk?.web?.uri || '',
|
|
||||||
title: chunk?.web?.title,
|
|
||||||
showFavicon: false
|
|
||||||
})) || []
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<SearchEntryPoint
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: message.metadata.groundingMetadata?.searchEntryPoint?.renderedContent
|
|
||||||
? message.metadata.groundingMetadata.searchEntryPoint.renderedContent
|
|
||||||
.replace(/@media \(prefers-color-scheme: light\)/g, 'body[theme-mode="light"]')
|
|
||||||
.replace(/@media \(prefers-color-scheme: dark\)/g, 'body[theme-mode="dark"]')
|
|
||||||
: ''
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{formattedCitations && (
|
|
||||||
<CitationsList
|
|
||||||
citations={formattedCitations.map((citation) => ({
|
|
||||||
number: citation.number,
|
|
||||||
url: citation.url,
|
|
||||||
hostname: citation.hostname,
|
|
||||||
showFavicon: isWebCitation
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{(message?.metadata?.webSearch || message.metadata?.knowledge) && message.status === 'success' && (
|
|
||||||
<CitationsList
|
|
||||||
citations={[
|
|
||||||
...(message.metadata.webSearch?.results.map((result, index) => ({
|
|
||||||
number: index + 1,
|
|
||||||
url: result.url,
|
|
||||||
title: result.title,
|
|
||||||
showFavicon: true,
|
|
||||||
type: 'websearch'
|
|
||||||
})) || []),
|
|
||||||
...(message.metadata.knowledge?.map((result, index) => ({
|
|
||||||
number: (message.metadata?.webSearch?.results?.length || 0) + index + 1,
|
|
||||||
url: result.sourceUrl,
|
|
||||||
title: result.sourceUrl,
|
|
||||||
showFavicon: true,
|
|
||||||
type: 'knowledge'
|
|
||||||
})) || [])
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{message?.metadata?.webSearchInfo && message.status === 'success' && (
|
|
||||||
<CitationsList
|
|
||||||
citations={message.metadata.webSearchInfo.map((result, index) => ({
|
|
||||||
number: index + 1,
|
number: index + 1,
|
||||||
url: result.link || result.url,
|
url: chunk?.web?.uri || '',
|
||||||
title: result.title,
|
title: chunk?.web?.title,
|
||||||
showFavicon: true
|
showFavicon: false
|
||||||
}))}
|
})) || []
|
||||||
/>
|
}
|
||||||
)}
|
/>
|
||||||
</CitationsContent>
|
<SearchEntryPoint
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: message.metadata.groundingMetadata?.searchEntryPoint?.renderedContent
|
||||||
|
? message.metadata.groundingMetadata.searchEntryPoint.renderedContent
|
||||||
|
.replace(/@media \(prefers-color-scheme: light\)/g, 'body[theme-mode="light"]')
|
||||||
|
.replace(/@media \(prefers-color-scheme: dark\)/g, 'body[theme-mode="dark"]')
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</CitationsContainer>
|
{formattedCitations && (
|
||||||
|
<CitationsList
|
||||||
|
citations={formattedCitations.map((citation) => ({
|
||||||
|
number: citation.number,
|
||||||
|
url: citation.url,
|
||||||
|
hostname: citation.hostname,
|
||||||
|
showFavicon: isWebCitation
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(message?.metadata?.webSearch || message.metadata?.knowledge) && message.status === 'success' && (
|
||||||
|
<CitationsList
|
||||||
|
citations={[
|
||||||
|
...(message.metadata.webSearch?.results.map((result, index) => ({
|
||||||
|
number: index + 1,
|
||||||
|
url: result.url,
|
||||||
|
title: result.title,
|
||||||
|
showFavicon: true,
|
||||||
|
type: 'websearch'
|
||||||
|
})) || []),
|
||||||
|
...(message.metadata.knowledge?.map((result, index) => ({
|
||||||
|
number: (message.metadata?.webSearch?.results?.length || 0) + index + 1,
|
||||||
|
url: result.sourceUrl,
|
||||||
|
title: result.sourceUrl,
|
||||||
|
showFavicon: true,
|
||||||
|
type: 'knowledge'
|
||||||
|
})) || [])
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{message?.metadata?.webSearchInfo && message.status === 'success' && (
|
||||||
|
<CitationsList
|
||||||
|
citations={message.metadata.webSearchInfo.map((result, index) => ({
|
||||||
|
number: index + 1,
|
||||||
|
url: result.link || result.url,
|
||||||
|
title: result.title,
|
||||||
|
showFavicon: true
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MessageAttachments message={message} />
|
<MessageAttachments message={message} />
|
||||||
@ -335,30 +323,6 @@ const MessageContent: React.FC<Props> = ({ message: _message, model }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CitationsContainer = styled.div`
|
|
||||||
margin-top: 12px;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
`
|
|
||||||
|
|
||||||
const CitationsHeader = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 12px;
|
|
||||||
background-color: var(--color-background-mute);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-border);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const CitationsContent = styled.div`
|
|
||||||
padding: 10px;
|
|
||||||
background-color: var(--color-background-mute);
|
|
||||||
`
|
|
||||||
const MessageContentLoading = styled.div`
|
const MessageContentLoading = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user