diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index fb0cd52eb8..3da222ee94 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -313,6 +313,7 @@ "fullscreen": "Entered fullscreen mode. Press F11 to exit", "knowledge_base": "Knowledge Base", "language": "Language", + "loading": "Loading...", "model": "Model", "models": "Models", "more": "More", @@ -533,6 +534,7 @@ "backup.start.success": "Backup started", "backup.success": "Backup successful", "chat.completion.paused": "Chat completion paused", + "citation": "{{count}} citations", "citations": "References", "copied": "Copied!", "copy.failed": "Copy failed", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 2c0ad9c1fe..5437feb109 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -313,6 +313,7 @@ "fullscreen": "全画面モードに入りました。F11キーで終了します", "knowledge_base": "ナレッジベース", "language": "言語", + "loading": "読み込み中...", "model": "モデル", "models": "モデル", "more": "もっと", @@ -533,7 +534,8 @@ "backup.start.success": "バックアップを開始しました", "backup.success": "バックアップに成功しました", "chat.completion.paused": "チャットの完了が一時停止されました", - "citations": "参考文献", + "citation": "{{count}}個の引用内容", + "citations": "引用内容", "copied": "コピーしました!", "copy.failed": "コピーに失敗しました", "copy.success": "コピーしました!", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index b686874718..fe5776109a 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -313,6 +313,7 @@ "fullscreen": "Вы вошли в полноэкранный режим. Нажмите F11 для выхода", "knowledge_base": "База знаний", "language": "Язык", + "loading": "Загрузка...", "model": "Модель", "models": "Модели", "more": "Ещё", @@ -533,7 +534,8 @@ "backup.start.success": "Создание резервной копии начато", "backup.success": "Резервная копия успешно создана", "chat.completion.paused": "Завершение чата приостановлено", - "citations": "Источники", + "citation": "{{count}} цитат", + "citations": "Содержание цитат", "copied": "Скопировано!", "copy.failed": "Не удалось скопировать", "copy.success": "Скопировано!", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index eff35502d4..c4b4a0e9ae 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -312,6 +312,7 @@ "fullscreen": "已进入全屏模式,按 F11 退出", "knowledge_base": "知识库", "language": "语言", + "loading": "加载中...", "model": "模型", "models": "模型", "more": "更多", @@ -532,6 +533,7 @@ "backup.start.success": "开始备份", "backup.success": "备份成功", "chat.completion.paused": "会话已停止", + "citation": "{{count}}个引用内容", "citations": "引用内容", "copied": "已复制", "copy.failed": "复制失败", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index d3b2bcbe44..736e6091ed 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -312,6 +312,7 @@ "fullscreen": "已進入全螢幕模式,按 F11 結束", "knowledge_base": "知識庫", "language": "語言", + "loading": "加載中...", "model": "模型", "models": "模型", "more": "更多", @@ -532,7 +533,8 @@ "backup.start.success": "開始備份", "backup.success": "備份成功", "chat.completion.paused": "聊天完成已暫停", - "citations": "參考文獻", + "citation": "{{count}} 個引用內容", + "citations": "引用內容", "copied": "已複製!", "copy.failed": "複製失敗", "copy.success": "已複製!", diff --git a/src/renderer/src/pages/home/Markdown/Markdown.tsx b/src/renderer/src/pages/home/Markdown/Markdown.tsx index e3cc299784..34c156a3a2 100644 --- a/src/renderer/src/pages/home/Markdown/Markdown.tsx +++ b/src/renderer/src/pages/home/Markdown/Markdown.tsx @@ -66,13 +66,12 @@ const Markdown: FC = ({ block }) => { }, [mathEngine, messageContent]) const components = useMemo(() => { - const baseComponents = { + return { a: (props: any) => , code: CodeBlock, img: ImagePreview, pre: (props: any) =>
     } as Partial
-    return baseComponents
   }, [])
 
   // if (role === 'user' && !renderInputMessageAsMarkdown) {
diff --git a/src/renderer/src/pages/home/Messages/CitationsList.tsx b/src/renderer/src/pages/home/Messages/CitationsList.tsx
index ae875da19b..ab82ddd2b1 100644
--- a/src/renderer/src/pages/home/Messages/CitationsList.tsx
+++ b/src/renderer/src/pages/home/Messages/CitationsList.tsx
@@ -1,8 +1,9 @@
 import Favicon from '@renderer/components/Icons/FallbackFavicon'
 import { HStack } from '@renderer/components/Layout'
-import { Collapse, theme } from 'antd'
-import { FileSearch, Info } from 'lucide-react'
-import React, { useMemo } from 'react'
+import { fetchWebContent } from '@renderer/utils/fetch'
+import { Button, Drawer } from 'antd'
+import { FileSearch } from 'lucide-react'
+import React, { useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import styled from 'styled-components'
 
@@ -18,135 +19,205 @@ export interface Citation {
 
 interface CitationsListProps {
   citations: Citation[]
-  hideTitle?: boolean
+}
+
+/**
+ * 限制文本长度
+ * @param text
+ * @param maxLength
+ */
+const truncateText = (text: string, maxLength = 100) => {
+  if (!text) return ''
+  return text.length > maxLength ? text.slice(0, maxLength) + '...' : text
+}
+
+/**
+ * 清理Markdown内容
+ * @param text
+ */
+const cleanMarkdownContent = (text: string): string => {
+  if (!text) return ''
+  let cleaned = text.replace(/!\[.*?]\(.*?\)/g, '')
+  cleaned = cleaned.replace(/\[(.*?)]\(.*?\)/g, '$1')
+  cleaned = cleaned.replace(/https?:\/\/\S+/g, '')
+  cleaned = cleaned.replace(/[-—–_=+]{3,}/g, ' ')
+  cleaned = cleaned.replace(/[¥$€£¥%@#&*^()[\]{}<>~`'"\\|/_.]+/g, '')
+  cleaned = cleaned.replace(/\s+/g, ' ').trim()
+  return cleaned
 }
 
 const CitationsList: React.FC = ({ citations }) => {
   const { t } = useTranslation()
+  const [open, setOpen] = useState(false)
 
-  const { token } = theme.useToken()
-  const items = useMemo(() => {
-    return !citations || citations.length === 0
-      ? []
-      : [
-          {
-            key: '1',
-            label: (
-              
-                {t('message.citations')}
-                
-              
-            ),
-            style: {
-              backgroundColor: token.colorFillAlter
-            },
-            children: (
-              <>
-                {citations.map((citation) => (
-                  
-                    {citation.number}.
-                    {citation.type === 'websearch' ? (
-                      
-                    ) : (
-                      
-                    )}
-                  
-                ))}
-              
-            )
-          }
-        ]
-  }, [citations, t])
+  const hasCitations = citations.length > 0
+  const count = citations.length
+  const previewItems = citations.slice(0, 3)
 
-  if (!citations || citations.length === 0) return null
+  if (!hasCitations) return null
+
+  const handleOpen = () => {
+    setOpen(true)
+  }
+
+  const handleClose = () => {
+    setOpen(false)
+  }
 
   return (
-    
-      
-    
+    <>
+      
+        
+          {previewItems.map((c, i) => (
+            
+              {c.type === 'websearch' && c.url ? (
+                
+              ) : (
+                
+              )}
+            
+          ))}
+        
+        {t('message.citation', { count: count })}
+      
+
+      
+        {citations.map((citation) => (
+          
+            {citation.type === 'websearch' ? (
+              
+            ) : (
+              
+            )}
+          
+        ))}
+      
+    
   )
 }
 
 const handleLinkClick = (url: string, event: React.MouseEvent) => {
-  if (!url) return
-
   event.preventDefault()
-
-  // 检查是否是网络URL
-  if (url.startsWith('http://') || url.startsWith('https://')) {
-    window.open(url, '_blank', 'noopener,noreferrer')
-  } else {
-    try {
-      window.api.file.openPath(url)
-    } catch (error) {
-      console.error('打开本地文件失败:', error)
-    }
-  }
+  if (url.startsWith('http')) window.open(url, '_blank', 'noopener,noreferrer')
+  else window.api.file.openPath(url)
 }
 
-// 网络搜索引用组件
 const WebSearchCitation: React.FC<{ citation: Citation }> = ({ citation }) => {
+  const { t } = useTranslation()
+  const [fetchedContent, setFetchedContent] = React.useState('')
+  const [isLoading, setIsLoading] = React.useState(false)
+  React.useEffect(() => {
+    if (citation.url) {
+      setIsLoading(true)
+      fetchWebContent(citation.url, 'markdown')
+        .then((res) => {
+          const cleaned = cleanMarkdownContent(res.content)
+          setFetchedContent(truncateText(cleaned, 100))
+        })
+        .finally(() => setIsLoading(false))
+    }
+  }, [citation.url])
+
   return (
-    <>
-      {citation.showFavicon && citation.url && (
-        
-      )}
-       handleLinkClick(citation.url, e)}>
-        {citation.title ? citation.title : {citation.hostname}}
-      
-    
+    
+      
+ {citation.showFavicon && citation.url && ( + + )} + handleLinkClick(citation.url, e)}> + {citation.title || {citation.hostname}} + +
+ {isLoading ?
{t('common.loading')}
: fetchedContent} +
) } -// 知识库引用组件 -const KnowledgeCitation: React.FC<{ citation: Citation }> = ({ citation }) => { - return ( - <> - {citation.showFavicon && citation.url && } - handleLinkClick(citation.url, e)}> - {citation.title} - - - ) -} +const KnowledgeCitation: React.FC<{ citation: Citation }> = ({ citation }) => ( + <> + {citation.showFavicon && } + handleLinkClick(citation.url, e)}> + {citation.title} + + +) -const CitationsContainer = styled.div` - background-color: rgb(242, 247, 253); - border-radius: 10px; - padding: 8px 12px; - margin: 12px 0; - display: inline-block; - /* display: flex; */ - /* flex-direction: column; */ - gap: 4px; - - body[theme-mode='dark'] & { - background-color: rgba(255, 255, 255, 0.05); - } -` - -const CitationsTitle = styled.div` - font-weight: 500; - margin-bottom: 4px; - color: var(--color-text-1); +const OpenButton = styled(Button)` display: flex; align-items: center; - gap: 6px; + padding: 2px 6px; + margin-bottom: 8px; + align-self: flex-start; + font-size: 12px; +` + +const PreviewIcons = styled.div` + display: flex; + align-items: center; + margin-right: 8px; +` + +const PreviewIcon = styled.div` + width: 24px; + height: 24px; + border-radius: 50%; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background: #fff; + border: 1px solid #fff; + margin-left: -8px; + + &:first-child { + margin-left: 0; + } ` const CitationLink = styled.a` font-size: 14px; line-height: 1.6; - text-decoration: none; color: var(--color-text-1); - - .hostname { - color: var(--color-link); - } + text-decoration: none; &:hover { text-decoration: underline; } + + .hostname { + color: var(--color-link); + } +` + +const WebSearchCard = styled.div` + display: flex; + flex-direction: column; + width: 100%; + padding: 12px; + margin-bottom: 8px; + border-radius: 8px; + border: 1px solid var(--color-border); + background-color: var(--color-bg-2); + transition: all 0.3s ease; + + &:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + background-color: var(--color-bg-3); + border-color: var(--color-primary-light); + transform: translateY(-2px); + } ` export default CitationsList diff --git a/src/renderer/src/windows/mini/chat/components/Messages.tsx b/src/renderer/src/windows/mini/chat/components/Messages.tsx index 95176b578e..932446312f 100644 --- a/src/renderer/src/windows/mini/chat/components/Messages.tsx +++ b/src/renderer/src/windows/mini/chat/components/Messages.tsx @@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' import MessageItem from './Message' + interface Props { assistant: Assistant route: string @@ -33,7 +34,6 @@ const Messages: FC = ({ assistant, route }) => { // setMessages((prev) => { // const assistantMessage = getAssistantMessage({ assistant, topic: assistant.topics[0] }) // store.dispatch(newMessagesActions.addMessage({ topicId: assistant.topics[0].id, message: assistantMessage })) - // const messages = prev.concat([message, assistantMessage]) // return messages // })