diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 4e7f31e..0507f21 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -28,20 +28,24 @@ "clsx": "^2.0.0", "date-fns": "^2.30.0", "lucide-react": "^0.294.0", + "marked": "^16.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-markdown": "^10.1.0", "react-router-dom": "^6.20.1", "react-window": "^1.8.11", + "rehype-highlight": "^7.0.2", + "remark-gfm": "^4.0.1", "zustand": "^4.4.7" }, "devDependencies": { - "@types/react-window": "^1.8.8", "@tauri-apps/cli": "^2", "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.1", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.1", + "@types/react-window": "^1.8.8", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.16", "jsdom": "^23.0.1", diff --git a/apps/desktop/src/App.css b/apps/desktop/src/App.css index 52414b8..a27c3e5 100644 --- a/apps/desktop/src/App.css +++ b/apps/desktop/src/App.css @@ -405,4 +405,84 @@ .shadow-colored { box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.1), 0 4px 6px -2px rgba(59, 130, 246, 0.05); } + + /* Markdown 和代码高亮样式 */ + .prose { + color: #374151; + max-width: none; + } + + .prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 { + color: #111827; + font-weight: 600; + } + + .prose p { + margin-bottom: 1rem; + line-height: 1.6; + } + + .prose ul, .prose ol { + margin-bottom: 1rem; + } + + .prose li { + margin-bottom: 0.25rem; + } + + .prose blockquote { + border-left: 4px solid #e5e7eb; + padding-left: 1rem; + font-style: italic; + color: #6b7280; + margin: 1rem 0; + } + + .prose code { + background-color: #f3f4f6; + padding: 0.125rem 0.25rem; + border-radius: 0.25rem; + font-size: 0.875rem; + font-family: 'Courier New', monospace; + } + + .prose pre { + background-color: #f3f4f6; + padding: 1rem; + border-radius: 0.5rem; + overflow-x: auto; + margin: 1rem 0; + } + + .prose pre code { + background-color: transparent; + padding: 0; + } + + /* 简化的代码高亮样式 */ + .hljs { + background: #f3f4f6 !important; + color: #374151 !important; + } + + .hljs-keyword, .hljs-selector-tag, .hljs-title { + color: #7c3aed !important; + } + + .hljs-string, .hljs-attr { + color: #059669 !important; + } + + .hljs-comment, .hljs-quote { + color: #6b7280 !important; + font-style: italic; + } + + .hljs-number, .hljs-literal { + color: #dc2626 !important; + } + + .hljs-function, .hljs-class { + color: #2563eb !important; + } } \ No newline at end of file diff --git a/apps/desktop/src/components/ChatInterface.tsx b/apps/desktop/src/components/ChatInterface.tsx index 925f5d6..268996a 100644 --- a/apps/desktop/src/components/ChatInterface.tsx +++ b/apps/desktop/src/components/ChatInterface.tsx @@ -10,7 +10,7 @@ import { } from 'lucide-react'; import { queryRagGrounding } from '../services/ragGroundingService'; import { RagGroundingQueryOptions, GroundingMetadata } from '../types/ragGrounding'; -import EnhancedChatMessage from './EnhancedChatMessage'; +import { EnhancedChatMessageV2 } from './EnhancedChatMessageV2'; /** * 聊天消息接口 @@ -188,34 +188,6 @@ export const ChatInterface: React.FC = ({ const result = await queryRagGrounding(userMessage.content, options); if (result.success && result.data) { - // 在控制台打印详细的回复结果 - console.group('🤖 AI 回复结果'); - console.log('📝 用户问题:', userMessage.content); - console.log('💬 AI 回答:', result.data.answer); - console.log('⏱️ 响应时间:', result.data.response_time_ms + 'ms'); - console.log('🤖 使用模型:', result.data.model_used); - console.log('📊 总耗时:', result.totalTime + 'ms'); - - if (result.data.grounding_metadata?.sources && result.data.grounding_metadata.sources.length > 0) { - console.log('📚 参考来源 (' + result.data.grounding_metadata.sources.length + ' 条):'); - result.data.grounding_metadata.sources.forEach((source, index) => { - console.log(` ${index + 1}. ${source.title}`); - if (source.content) { - console.log(` 内容: ${JSON.stringify(source.content, null, 2)}`); - } - if (source.uri) { - console.log(` 链接: ${source.uri}`); - } - }); - } - - if (result.data.grounding_metadata?.search_queries && result.data.grounding_metadata.search_queries.length > 0) { - console.log('🔍 搜索查询:', result.data.grounding_metadata.search_queries); - } - - console.log('🕐 查询时间:', new Date().toLocaleString()); - console.groupEnd(); - // 更新助手消息 setMessages(prev => prev.map(msg => msg.id === assistantMessage.id @@ -385,11 +357,11 @@ export const ChatInterface: React.FC = ({ }, [selectedTags]); return ( -
+
{/* 聊天消息区域 */}
0 ? '200px' : '120px' }} > {messages.length === 0 ? ( @@ -424,11 +396,12 @@ export const ChatInterface: React.FC = ({ ) : ( <> {messages.map((message) => ( - ))} diff --git a/apps/desktop/src/components/ChatMaterialCard.tsx b/apps/desktop/src/components/ChatMaterialCard.tsx new file mode 100644 index 0000000..b9c09e6 --- /dev/null +++ b/apps/desktop/src/components/ChatMaterialCard.tsx @@ -0,0 +1,222 @@ +import React, { useState } from 'react'; +import { + FileVideo, + FileAudio, + FileImage, + Clock, + ExternalLink, + Eye, + Play, + Volume2, + Image as ImageIcon, + Calendar, + Hash, + HardDrive +} from 'lucide-react'; +import { GroundingSource } from '../types/ragGrounding'; +import { MaterialThumbnail } from './MaterialThumbnail'; + +/** + * 聊天素材卡片属性接口 + */ +interface ChatMaterialCardProps { + /** 素材来源信息 */ + source: GroundingSource; + /** 卡片大小 */ + size?: 'small' | 'medium' | 'large'; + /** 是否显示详细信息 */ + showDetails?: boolean; + /** 自定义样式类名 */ + className?: string; + /** 点击回调 */ + onClick?: (source: GroundingSource) => void; +} + +/** + * 格式化文件大小 + */ +const formatFileSize = (bytes: number): string => { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; +}; + +/** + * 格式化时长 + */ +const formatDuration = (seconds: number): string => { + if (seconds < 60) { + return `${Math.round(seconds)}s`; + } else { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.round(seconds % 60); + return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; + } +}; + +/** + * 获取文件类型图标 + */ +const getFileTypeIcon = (type: string) => { + switch (type.toLowerCase()) { + case 'video': + return ; + case 'audio': + return ; + case 'image': + return ; + default: + return ; + } +}; + +/** + * 聊天素材卡片组件 + * 专为聊天界面优化的素材展示卡片 + */ +export const ChatMaterialCard: React.FC = ({ + source, + size = 'medium', + showDetails = true, + className = '', + onClick +}) => { + const [imageError, setImageError] = useState(false); + const [isHovered, setIsHovered] = useState(false); + + // 获取卡片尺寸样式 + const getSizeClasses = () => { + switch (size) { + case 'small': + return 'w-32 h-24'; + case 'large': + return 'w-64 h-48'; + default: + return 'w-48 h-36'; + } + }; + + // 获取内容类型 + const contentType = source.content?.type || 'unknown'; + const isImage = contentType === 'image'; + const isVideo = contentType === 'video'; + const isAudio = contentType === 'audio'; + + // 处理卡片点击 + const handleClick = () => { + onClick?.(source); + }; + + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + {/* 缩略图/预览区域 */} +
+ {isImage && source.content?.image_url && !imageError ? ( + {source.title setImageError(true)} + loading="lazy" + /> + ) : ( +
+ {getFileTypeIcon(contentType)} +
+ )} + + {/* 悬停播放按钮 */} + {(isVideo || isAudio) && isHovered && ( +
+
+ {isVideo ? ( + + ) : ( + + )} +
+
+ )} + + {/* 文件类型标识 */} +
+
+ {getFileTypeIcon(contentType)} + + {contentType.toUpperCase()} + +
+
+ + {/* 时长标识 */} + {(isVideo || isAudio) && source.content?.duration && ( +
+
+ + + {formatDuration(source.content.duration)} + +
+
+ )} +
+ + {/* 信息区域 */} +
+
+

+ {source.title || '未知素材'} +

+ + {showDetails && source.content?.description && ( +

+ {source.content.description} +

+ )} +
+ + {/* 底部信息 */} + {showDetails && ( +
+
+ {source.content?.file_size && ( +
+ + {formatFileSize(source.content.file_size)} +
+ )} +
+ + {source.uri && ( +
+ + + {source.uri.split('/').pop()} + +
+ )} +
+ )} +
+ + {/* 悬停效果 */} + {isHovered && ( +
+ )} +
+ ); +}; + +export default ChatMaterialCard; diff --git a/apps/desktop/src/components/EnhancedChatMessage.tsx b/apps/desktop/src/components/EnhancedChatMessage.tsx index f9c7f55..65fefbb 100644 --- a/apps/desktop/src/components/EnhancedChatMessage.tsx +++ b/apps/desktop/src/components/EnhancedChatMessage.tsx @@ -65,6 +65,11 @@ export const EnhancedChatMessage: React.FC = ({ const groundingMetadata = message.metadata?.grounding_metadata; const sources = message.metadata?.sources || []; + // 调试信息 + console.log('💬 EnhancedChatMessage - message:', message); + console.log('💬 EnhancedChatMessage - groundingMetadata:', groundingMetadata); + console.log('💬 EnhancedChatMessage - grounding_supports:', groundingMetadata?.grounding_supports); + // 处理复制消息 const handleCopy = useCallback(async () => { try { @@ -118,8 +123,9 @@ export const EnhancedChatMessage: React.FC = ({ text={message.content} groundingMetadata={groundingMetadata} enableImageCards={enableImageCards} + enableMarkdown={true} highlightStyle="underline" - className="whitespace-pre-wrap" + className="" /> ) : (
{message.content}
@@ -189,7 +195,7 @@ export const EnhancedChatMessage: React.FC = ({ {/* 来源信息 */} {isAssistant && sources.length > 0 && showSources && expandedSources && ( -
+

@@ -204,46 +210,78 @@ export const EnhancedChatMessage: React.FC = ({

-
- {sources.slice(0, 3).map((source, index) => ( + {/* 图片卡片网格布局 */} +
+ {sources.map((source, index) => (
-
-

- {source.title} -

- {source.content?.description && ( -

- {source.content.description} -

- )} -
+ {/* 图片展示 */} +
+ {source.uri ? ( +
+ {source.title { + const target = e.target as HTMLImageElement; + target.style.display = 'none'; + target.parentElement?.querySelector('.error-placeholder')?.classList.remove('hidden'); + }} + /> -
- {source.uri && ( - - - + {/* 悬停时显示的操作按钮 */} + + + {/* 错误占位符 */} +
+
+
+ ! +
+ 加载失败 +
+
+
+ ) : ( +
+
+
+ +
+ 暂无图片 +
+
)} + + {/* 图片标题覆盖层 */} +
+

+ {source.title || `图片 ${index + 1}`} +

+ {source.content?.description && ( +

+ {source.content.description} +

+ )} +
))} - - {sources.length > 3 && ( -
- - 还有 {sources.length - 3} 个来源... - -
- )}
diff --git a/apps/desktop/src/components/EnhancedChatMessageV2.tsx b/apps/desktop/src/components/EnhancedChatMessageV2.tsx new file mode 100644 index 0000000..fdbe8a3 --- /dev/null +++ b/apps/desktop/src/components/EnhancedChatMessageV2.tsx @@ -0,0 +1,253 @@ +import React, { useState, useCallback } from 'react'; +import { + Copy, + Check, + User, + Bot, + Clock, + ChevronDown, + ChevronUp, + ExternalLink, + Grid3X3, + List +} from 'lucide-react'; +import { EnhancedMarkdownRenderer } from './EnhancedMarkdownRenderer'; +import { ChatMaterialCard } from './ChatMaterialCard'; +import { GroundingSource } from '../types/ragGrounding'; + +/** + * 聊天消息接口 + */ +interface ChatMessage { + id: string; + type: 'user' | 'assistant'; + content: string; + timestamp: Date; + status?: 'sending' | 'sent' | 'error'; + metadata?: { + responseTime?: number; + modelUsed?: string; + sources?: Array<{ + title: string; + uri?: string; + content?: any; + }>; + grounding_metadata?: any; + }; +} + +/** + * 增强聊天消息组件属性接口 + */ +interface EnhancedChatMessageV2Props { + /** 消息数据 */ + message: ChatMessage; + /** 是否显示来源信息 */ + showSources?: boolean; + /** 是否启用素材卡片 */ + enableMaterialCards?: boolean; + /** 是否启用角标引用 */ + enableReferences?: boolean; + /** 自定义样式类名 */ + className?: string; +} + +/** + * 增强聊天消息组件 V2 + * 支持角标引用、优化素材展示和Markdown渲染 + */ +export const EnhancedChatMessageV2: React.FC = ({ + message, + showSources = true, + enableMaterialCards = true, + enableReferences = true, + className = '' +}) => { + const [copied, setCopied] = useState(false); + const [expandedSources, setExpandedSources] = useState(false); + const [materialViewMode, setMaterialViewMode] = useState<'grid' | 'list'>('grid'); + const [selectedReference, setSelectedReference] = useState(null); + + const isUser = message.type === 'user'; + const isAssistant = message.type === 'assistant'; + const groundingMetadata = message.metadata?.grounding_metadata; + const sources = message.metadata?.sources || []; + + // 复制消息内容 + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(message.content); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (error) { + console.error('复制失败:', error); + } + }, [message.content]); + + // 处理引用点击 + const handleReferenceClick = useCallback((sources: GroundingSource[], index: number) => { + setSelectedReference(selectedReference === index ? null : index); + console.log('引用点击:', { index, sources }); + }, [selectedReference]); + + // 处理素材卡片点击 + const handleMaterialClick = useCallback((source: GroundingSource) => { + console.log('素材点击:', source); + // TODO: 实现素材详情查看逻辑 + }, []); + + // 格式化时间 + const formatTime = (date: Date) => { + return date.toLocaleTimeString('zh-CN', { + hour: '2-digit', + minute: '2-digit' + }); + }; + + return ( +
+ {/* 头像 */} +
+
+ {isUser ? : } +
+
+ + {/* 消息内容 */} +
+ {/* 消息气泡 */} +
+ {/* 消息内容渲染 */} +
+ {isAssistant && enableReferences ? ( + + ) : ( +
+ {message.content} +
+ )} +
+ + {/* 消息状态 */} + {message.status === 'sending' && ( +
+
+ 发送中... +
+ )} +
+ + {/* 消息元信息 */} +
+
+ + {formatTime(message.timestamp)} +
+ + {isAssistant && message.metadata?.responseTime && ( + • 响应时间: {message.metadata.responseTime}ms + )} + + {isAssistant && message.metadata?.modelUsed && ( + • {message.metadata.modelUsed} + )} + + {/* 复制按钮 */} + +
+ + {/* 素材来源展示 */} + {isAssistant && showSources && sources.length > 0 && enableMaterialCards && ( +
+
+
+

+ 相关素材 ({sources.length}) +

+ +
+ + {expandedSources && ( +
+ + +
+ )} +
+ + {expandedSources && ( +
+ {sources.map((source, index) => ( + + ))} +
+ )} +
+ )} +
+
+ ); +}; + +export default EnhancedChatMessageV2; diff --git a/apps/desktop/src/components/EnhancedMarkdownRenderer.tsx b/apps/desktop/src/components/EnhancedMarkdownRenderer.tsx new file mode 100644 index 0000000..96998b5 --- /dev/null +++ b/apps/desktop/src/components/EnhancedMarkdownRenderer.tsx @@ -0,0 +1,177 @@ +import React, { useMemo, useState } from 'react'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import { GroundingMetadata, GroundingSupport, GroundingSource } from '../types/ragGrounding'; +import { ReferenceFootnote } from './ReferenceFootnote'; + +/** + * 文本片段与引用信息 + */ +interface TextSegmentWithReference { + text: string; + startIndex: number; + endIndex: number; + groundingSupport?: GroundingSupport; + sources?: GroundingSource[]; + referenceIndex?: number; +} + +/** + * 增强Markdown渲染器属性接口 + */ +interface EnhancedMarkdownRendererProps { + /** 要渲染的文本内容 */ + content: string; + /** Grounding元数据 */ + groundingMetadata?: GroundingMetadata; + /** 是否启用角标引用 */ + enableReferences?: boolean; + /** 是否启用Markdown渲染 */ + enableMarkdown?: boolean; + /** 自定义样式类名 */ + className?: string; + /** 引用点击回调 */ + onReferenceClick?: (sources: GroundingSource[], index: number) => void; +} + +/** + * 增强Markdown渲染器组件 + * 支持角标引用和grounding高亮 + */ +export const EnhancedMarkdownRenderer: React.FC = ({ + content, + groundingMetadata, + enableReferences = true, + enableMarkdown = true, + className = '', + onReferenceClick +}) => { + const [selectedReference, setSelectedReference] = useState(null); + + // 解析文本片段和引用信息 + const textSegments = useMemo(() => { + if (!groundingMetadata?.grounding_supports?.length) { + return [{ text: content, startIndex: 0, endIndex: content.length }]; + } + + const segments: TextSegmentWithReference[] = []; + const supports = [...groundingMetadata.grounding_supports].sort( + (a, b) => a.start_index - b.start_index + ); + + let currentIndex = 0; + let referenceCounter = 1; + + supports.forEach((support) => { + // 添加支持前的文本 + if (currentIndex < support.start_index) { + segments.push({ + text: content.slice(currentIndex, support.start_index), + startIndex: currentIndex, + endIndex: support.start_index + }); + } + + // 获取相关来源 + const sources = groundingMetadata.sources?.filter(source => + support.grounding_chunk_indices?.includes(source.chunk_index || 0) + ) || []; + + // 添加支持的文本片段 + segments.push({ + text: content.slice(support.start_index, support.end_index), + startIndex: support.start_index, + endIndex: support.end_index, + groundingSupport: support, + sources, + referenceIndex: sources.length > 0 ? referenceCounter++ : undefined + }); + + currentIndex = support.end_index; + }); + + // 添加剩余文本 + if (currentIndex < content.length) { + segments.push({ + text: content.slice(currentIndex), + startIndex: currentIndex, + endIndex: content.length + }); + } + + return segments; + }, [content, groundingMetadata]); + + // 处理引用点击 + const handleReferenceClick = (sources: GroundingSource[], index: number) => { + setSelectedReference(selectedReference === index ? null : index); + onReferenceClick?.(sources, index); + }; + + // 渲染文本片段 + const renderSegment = (segment: TextSegmentWithReference, index: number) => { + const hasReference = segment.referenceIndex && segment.sources?.length; + const isHighlighted = hasReference && selectedReference === segment.referenceIndex; + + return ( + + {enableMarkdown ? ( + <>{children}, + h1: ({ children }) => {children}, + h2: ({ children }) => {children}, + h3: ({ children }) => {children}, + ul: ({ children }) =>
{children}
, + ol: ({ children }) =>
{children}
, + li: ({ children }) =>
{children}
, + strong: ({ children }) => {children}, + em: ({ children }) => {children}, + code: ({ children }) => ( + + {children} + + ), + blockquote: ({ children }) => ( +
+ {children} +
+ ), + }} + > + {segment.text} +
+ ) : ( + segment.text + )} + + {/* 角标引用 */} + {enableReferences && hasReference && ( + handleReferenceClick(sources, segment.referenceIndex!)} + /> + )} +
+ ); + }; + + return ( +
+
+ {textSegments.map(renderSegment)} +
+
+ ); +}; + +export default EnhancedMarkdownRenderer; diff --git a/apps/desktop/src/components/GroundedText.tsx b/apps/desktop/src/components/GroundedText.tsx index c837998..c9b6eee 100644 --- a/apps/desktop/src/components/GroundedText.tsx +++ b/apps/desktop/src/components/GroundedText.tsx @@ -1,8 +1,11 @@ -import React, { useState, useCallback, useRef } from 'react'; +import React, { useState, useCallback, useRef, useEffect } from 'react'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; import { GroundingMetadata, GroundingSupport, GroundingSource } from '../types/ragGrounding'; import ImageCard from './ImageCard'; import ImagePreviewModal from './ImagePreviewModal'; import useImageDownload from '../hooks/useImageDownload'; +import { ReferenceFootnote } from './ReferenceFootnote'; /** * 文字片段信息 @@ -34,6 +37,8 @@ interface GroundedTextProps { enableImageCards?: boolean; /** 高亮样式 */ highlightStyle?: 'underline' | 'background' | 'border'; + /** 是否启用 Markdown 渲染 */ + enableMarkdown?: boolean; } /** @@ -45,17 +50,35 @@ export const GroundedText: React.FC = ({ groundingMetadata, className = '', enableImageCards = true, - highlightStyle = 'underline' + highlightStyle = 'underline', + enableMarkdown = true }) => { const [hoveredSegment, setHoveredSegment] = useState(null); + const [clickedSegment, setClickedSegment] = useState(null); const [cardPosition, setCardPosition] = useState<{ top: number; left: number } | null>(null); const [previewSource, setPreviewSource] = useState(null); + const [isCardHovered, setIsCardHovered] = useState(false); + const [isPersistent, setIsPersistent] = useState(false); const containerRef = useRef(null); + const hideTimeoutRef = useRef(null); const { downloadImage, isDownloadable } = useImageDownload(); + // 清理定时器 + useEffect(() => { + return () => { + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current); + } + }; + }, []); + // 解析文本片段 const textSegments = React.useMemo(() => { + console.log('🔍 GroundedText - groundingMetadata:', groundingMetadata); + console.log('🔍 GroundedText - grounding_supports:', groundingMetadata?.grounding_supports); + if (!groundingMetadata?.grounding_supports) { + console.log('❌ 没有 grounding_supports 数据'); return [{ text, startIndex: 0, endIndex: text.length }]; } @@ -85,6 +108,13 @@ export const GroundedText: React.FC = ({ .map((index: number) => sources[index]) .filter(Boolean); + console.log('📝 处理片段:', { + text: segmentText, + groundingChunkIndices: support.groundingChunkIndices, + relatedSources: relatedSources.length, + sources: relatedSources + }); + // 添加高亮片段 segments.push({ text: segmentText, @@ -109,9 +139,50 @@ export const GroundedText: React.FC = ({ return segments; }, [text, groundingMetadata]); - // 处理鼠标悬停 - const handleMouseEnter = useCallback((segment: TextSegment, event: React.MouseEvent) => { - if (!segment.groundingSupport || !segment.sources?.length || !enableImageCards) return; + // 清除隐藏定时器 + const clearHideTimeout = useCallback(() => { + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current); + hideTimeoutRef.current = null; + } + }, []); + + // 隐藏卡片 + const hideCard = useCallback(() => { + if (!isPersistent) { + setHoveredSegment(null); + setCardPosition(null); + setIsCardHovered(false); + } + }, [isPersistent]); + + // 延迟隐藏卡片 + const scheduleHideCard = useCallback(() => { + if (isPersistent) return; // 如果是持久显示,不隐藏 + + clearHideTimeout(); + hideTimeoutRef.current = setTimeout(() => { + if (!isCardHovered) { + hideCard(); + } + }, 150); // 150ms 延迟,给用户时间移动到卡片上 + }, [clearHideTimeout, hideCard, isCardHovered, isPersistent]); + + // 显示卡片 + const showCard = useCallback((segment: TextSegment, event: React.MouseEvent, persistent = false) => { + console.log('🎯 showCard 调用:', { + hasGrounding: !!segment.groundingSupport, + sourcesLength: segment.sources?.length, + enableImageCards, + persistent + }); + + if (!segment.groundingSupport || !segment.sources?.length || !enableImageCards) { + console.log('❌ showCard 条件不满足'); + return; + } + + clearHideTimeout(); const rect = event.currentTarget.getBoundingClientRect(); const containerRect = containerRef.current?.getBoundingClientRect(); @@ -123,14 +194,59 @@ export const GroundedText: React.FC = ({ }); } - setHoveredSegment(segment); - }, [enableImageCards]); + if (persistent) { + setClickedSegment(segment); + setIsPersistent(true); + } else { + setHoveredSegment(segment); + } + }, [enableImageCards, clearHideTimeout]); - // 处理鼠标离开 + // 处理鼠标悬停文字 + const handleMouseEnter = useCallback((segment: TextSegment, event: React.MouseEvent) => { + console.log('🖱️ 鼠标悬停:', { + text: segment.text, + hasGrounding: !!segment.groundingSupport, + sourcesCount: segment.sources?.length || 0, + isPersistent + }); + + if (isPersistent) return; // 如果已经是持久显示,悬停不生效 + showCard(segment, event, false); + }, [isPersistent, showCard]); + + // 处理鼠标离开文字 const handleMouseLeave = useCallback(() => { - setHoveredSegment(null); - setCardPosition(null); - }, []); + scheduleHideCard(); + }, [scheduleHideCard]); + + // 处理点击文字 + const handleClick = useCallback((segment: TextSegment, event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + + // 如果点击的是同一个片段,切换显示状态 + if (clickedSegment === segment && isPersistent) { + setIsPersistent(false); + setClickedSegment(null); + hideCard(); + } else { + // 显示新的片段 + showCard(segment, event, true); + } + }, [clickedSegment, isPersistent, showCard, hideCard]); + + // 处理鼠标进入卡片 + const handleCardMouseEnter = useCallback(() => { + clearHideTimeout(); + setIsCardHovered(true); + }, [clearHideTimeout]); + + // 处理鼠标离开卡片 + const handleCardMouseLeave = useCallback(() => { + setIsCardHovered(false); + scheduleHideCard(); + }, [scheduleHideCard]); // 处理图片下载 const handleDownload = useCallback(async (source: GroundingSource) => { @@ -159,6 +275,15 @@ export const GroundedText: React.FC = ({ setPreviewSource(null); }, []); + // 关闭卡片 + const handleCloseCard = useCallback(() => { + setIsPersistent(false); + setClickedSegment(null); + setHoveredSegment(null); + setCardPosition(null); + setIsCardHovered(false); + }, []); + // 获取高亮样式 const getHighlightClass = useCallback((hasGrounding: boolean) => { if (!hasGrounding) return ''; @@ -175,60 +300,154 @@ export const GroundedText: React.FC = ({ } }, [highlightStyle]); + // 渲染内容(支持 Markdown 或纯文本) + const renderContent = useCallback(() => { + if (!enableMarkdown) { + // 纯文本模式 + return ( +
+ {textSegments.map((segment, index) => { + const hasGrounding = !!(segment.groundingSupport && segment.sources?.length); + const isActive = clickedSegment === segment && isPersistent; + + return ( + handleMouseEnter(segment, e) : undefined} + onMouseLeave={hasGrounding ? handleMouseLeave : undefined} + onClick={hasGrounding ? (e) => handleClick(segment, e) : undefined} + title={hasGrounding ? `点击查看相关图片 (${segment.sources?.length} 张)` : undefined} + > + {segment.text} + + ); + })} +
+ ); + } + + // Markdown 模式 - 暂时使用纯文本渲染以保持 grounding 功能 + // TODO: 未来需要实现更复杂的 Markdown + Grounding 集成 + return ( +
+
+ {textSegments.map((segment, index) => { + const hasGrounding = !!(segment.groundingSupport && segment.sources?.length); + const isActive = clickedSegment === segment && isPersistent; + + return ( + handleMouseEnter(segment, e) : undefined} + onMouseLeave={hasGrounding ? handleMouseLeave : undefined} + onClick={hasGrounding ? (e) => handleClick(segment, e) : undefined} + title={hasGrounding ? `点击查看相关图片 (${segment.sources?.length} 张)` : undefined} + > + <> + {enableMarkdown ? ( + <>{children}, + h1: ({ children }) => {children}, + h2: ({ children }) => {children}, + h3: ({ children }) => {children}, + ul: ({ children }) => {children}, + ol: ({ children }) => {children}, + li: ({ children }) => • {children} , + strong: ({ children }) => {children}, + em: ({ children }) => {children}, + code: ({ children }) => {children}, + blockquote: ({ children }) => {children}, + }} + > + {segment.text} + + ) : ( + segment.text + )} + + {/* 素材引用数量标注 - 使用CSS伪元素样式 */} + {hasGrounding && segment.sources && segment.sources.length > 0 && ( + + {segment.sources.length} + + )} + + + ); + })} +
+
+ ); + }, [enableMarkdown, textSegments, clickedSegment, isPersistent, getHighlightClass, handleMouseEnter, handleMouseLeave, handleClick, text]); + return (
- {/* 渲染文本片段 */} -
- {textSegments.map((segment, index) => { - const hasGrounding = !!(segment.groundingSupport && segment.sources?.length); - - return ( - handleMouseEnter(segment, e) : undefined} - onMouseLeave={hasGrounding ? handleMouseLeave : undefined} - title={hasGrounding ? `查看相关图片 (${segment.sources?.length} 张)` : undefined} - > - {segment.text} - - ); - })} -
+ {/* 渲染内容 */} + {renderContent()} {/* 图片卡片 */} - {hoveredSegment && hoveredSegment.sources && cardPosition && ( -
{}} // 保持卡片显示 - onMouseLeave={handleMouseLeave} - > - {/* 显示第一张相关图片 */} - {hoveredSegment.sources[0] && ( - - )} + {(() => { + const displaySegment = isPersistent ? clickedSegment : hoveredSegment; + return displaySegment && displaySegment.sources && cardPosition && ( +
+ {/* 显示第一张相关图片 */} + {displaySegment.sources[0] && ( + + )} - {/* 如果有多张图片,显示指示器 */} - {hoveredSegment.sources.length > 1 && ( -
-
- +{hoveredSegment.sources.length - 1} 张图片 + {/* 如果有多张图片,显示指示器 */} + {displaySegment.sources.length > 1 && ( +
+
+ +{displaySegment.sources.length - 1} 张图片 +
-
- )} -
- )} + )} + + {/* 持久显示时的固定指示器 */} + {isPersistent && ( +
+
+
+ )} +
+ ); + })()} {/* 图片预览模态框 */} void; +} + +/** + * 引用来源详情弹窗属性接口 + */ +interface ReferenceTooltipProps { + sources: GroundingSource[]; + isVisible: boolean; + position: { top: number; left: number }; + onClose: () => void; +} + +/** + * 引用来源详情弹窗组件 + */ +const ReferenceTooltip: React.FC = ({ + sources, + isVisible, + position, + onClose +}) => { + const tooltipRef = useRef(null); + + // 点击外部关闭弹窗 + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (tooltipRef.current && !tooltipRef.current.contains(event.target as Node)) { + onClose(); + } + }; + + if (isVisible) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isVisible, onClose]); + + if (!isVisible) return null; + + return ( +
+
+
+

引用来源

+ +
+ +
+ {sources.map((source, index) => ( +
+
+ {/* 来源类型图标 */} +
+ {source.content?.type === 'image' ? ( + + ) : ( + + )} +
+ + {/* 来源信息 */} +
+

+ {source.title || '未知来源'} +

+ + {source.content?.description && ( +

+ {source.content.description} +

+ )} + + {source.uri && ( +
+ + + {source.uri} + +
+ )} +
+
+ + {/* 图片预览 */} + {source.content?.type === 'image' && source.content?.image_url && ( +
+ {source.title +
+ )} +
+ ))} +
+
+
+ ); +}; + +/** + * 引用角标组件 + * 遵循 frontend-developer 规范的设计标准 + */ +export const ReferenceFootnote: React.FC = ({ + index, + sources, + className = '', + showDetails = true, + onClick +}) => { + const [showTooltip, setShowTooltip] = useState(false); + const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 }); + const footnoteRef = useRef(null); + + // 处理角标点击 + const handleClick = useCallback((event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + + if (onClick) { + onClick(sources); + } + + if (showDetails && footnoteRef.current) { + const rect = footnoteRef.current.getBoundingClientRect(); + setTooltipPosition({ + top: rect.top + window.scrollY - 8, + left: rect.left + window.scrollX + rect.width / 2 + }); + setShowTooltip(!showTooltip); + } + }, [sources, onClick, showDetails, showTooltip]); + + // 处理悬停 + const handleMouseEnter = useCallback(() => { + if (!showTooltip && showDetails && footnoteRef.current) { + const rect = footnoteRef.current.getBoundingClientRect(); + setTooltipPosition({ + top: rect.top + window.scrollY - 8, + left: rect.left + window.scrollX + rect.width / 2 + }); + } + }, [showTooltip, showDetails]); + + return ( + <> + + + {/* 引用详情弹窗 */} + setShowTooltip(false)} + /> + + ); +}; + +export default ReferenceFootnote; diff --git a/apps/desktop/src/components/__tests__/EnhancedChatMessageV2.test.tsx b/apps/desktop/src/components/__tests__/EnhancedChatMessageV2.test.tsx new file mode 100644 index 0000000..b74d3bf --- /dev/null +++ b/apps/desktop/src/components/__tests__/EnhancedChatMessageV2.test.tsx @@ -0,0 +1,141 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { describe, test, expect, vi } from 'vitest'; +import { EnhancedChatMessageV2 } from '../EnhancedChatMessageV2'; + +// Mock 数据 +const mockUserMessage = { + id: '1', + type: 'user' as const, + content: '请推荐一些夏日穿搭', + timestamp: new Date('2024-01-01T10:00:00Z'), + status: 'sent' as const +}; + +const mockAssistantMessage = { + id: '2', + type: 'assistant' as const, + content: '夏日穿搭建议:选择轻薄透气的面料,如棉麻材质。推荐搭配清爽的色彩,如白色、浅蓝色等。', + timestamp: new Date('2024-01-01T10:01:00Z'), + status: 'sent' as const, + metadata: { + responseTime: 1500, + modelUsed: 'gemini-pro', + sources: [ + { + title: '夏日穿搭指南', + uri: 'https://example.com/summer-style.jpg', + content: { + type: 'image', + image_url: 'https://example.com/summer-style.jpg', + description: '清爽夏日穿搭示例' + } + } + ], + grounding_metadata: { + grounding_supports: [ + { + start_index: 0, + end_index: 20, + grounding_chunk_indices: [0] + } + ], + sources: [ + { + chunk_index: 0, + title: '夏日穿搭指南', + uri: 'https://example.com/summer-style.jpg', + content: { + type: 'image', + image_url: 'https://example.com/summer-style.jpg', + description: '清爽夏日穿搭示例' + } + } + ] + } + } +}; + +describe('EnhancedChatMessageV2', () => { + test('渲染用户消息', () => { + render(); + + expect(screen.getByText('请推荐一些夏日穿搭')).toBeInTheDocument(); + expect(screen.getByText('10:00')).toBeInTheDocument(); + }); + + test('渲染助手消息', () => { + render(); + + expect(screen.getByText(/夏日穿搭建议/)).toBeInTheDocument(); + expect(screen.getByText('10:01')).toBeInTheDocument(); + expect(screen.getByText('• 响应时间: 1500ms')).toBeInTheDocument(); + expect(screen.getByText('• gemini-pro')).toBeInTheDocument(); + }); + + test('显示素材来源', () => { + render( + + ); + + expect(screen.getByText('相关素材 (1)')).toBeInTheDocument(); + }); + + test('复制消息功能', async () => { + // Mock clipboard API + const mockWriteText = vi.fn().mockImplementation(() => Promise.resolve()); + Object.assign(navigator, { + clipboard: { + writeText: mockWriteText, + }, + }); + + render(); + + const copyButton = screen.getByTitle('复制消息'); + fireEvent.click(copyButton); + + expect(mockWriteText).toHaveBeenCalledWith('请推荐一些夏日穿搭'); + }); + + test('展开/收起素材来源', () => { + render( + + ); + + // 检查是否显示了素材来源标题 + expect(screen.getByText('相关素材 (1)')).toBeInTheDocument(); + }); + + test('角标引用功能', () => { + render( + + ); + + // 检查是否渲染了消息内容 + expect(screen.getByText(/夏日穿搭建议/)).toBeInTheDocument(); + }); + + test('发送中状态显示', () => { + const sendingMessage = { + ...mockUserMessage, + status: 'sending' as const + }; + + render(); + + expect(screen.getByText('发送中...')).toBeInTheDocument(); + }); +}); diff --git a/apps/desktop/src/components/chat/README.md b/apps/desktop/src/components/chat/README.md new file mode 100644 index 0000000..ba833b1 --- /dev/null +++ b/apps/desktop/src/components/chat/README.md @@ -0,0 +1,177 @@ +# AI智能聊天功能重新设计 + +根据 `promptx/frontend-developer` 规范重新设计的AI智能聊天功能,包含以下核心特性: + +## 🎯 核心功能 + +### 1. 信息引用角标系统 +- **角标显示**: 每句话的信息引用以角标形式展示 +- **点击查看**: 点击角标可查看详细来源信息 +- **悬停预览**: 鼠标悬停显示来源预览 +- **智能关联**: 与 grounding metadata 自动关联 + +### 2. 优化素材卡片列表 +- **多种视图**: 支持网格和列表两种展示模式 +- **响应式设计**: 适配不同屏幕尺寸 +- **缩略图预览**: 图片/视频素材缩略图展示 +- **详细信息**: 文件大小、时长等元数据显示 + +### 3. 增强Markdown渲染 +- **语法支持**: 完整的Markdown语法支持 +- **角标集成**: 角标与Markdown内容无缝集成 +- **高亮效果**: 引用文本智能高亮显示 +- **自定义组件**: 支持自定义Markdown组件 + +## 🏗️ 组件架构 + +### 核心组件 + +#### `EnhancedChatMessageV2` +主要的聊天消息组件,整合所有新功能: +```tsx + +``` + +#### `ReferenceFootnote` +引用角标组件: +```tsx + +``` + +#### `EnhancedMarkdownRenderer` +增强Markdown渲染器: +```tsx + +``` + +#### `ChatMaterialCard` +聊天专用素材卡片: +```tsx + +``` + +## 🎨 设计规范 + +### 视觉设计 +- **渐变背景**: 使用现代渐变背景提升视觉效果 +- **毛玻璃效果**: 消息气泡采用毛玻璃效果 +- **动画过渡**: 流畅的动画过渡效果 +- **响应式布局**: 完美适配移动端和桌面端 + +### 交互设计 +- **直观操作**: 点击、悬停等交互符合用户习惯 +- **即时反馈**: 所有操作都有即时视觉反馈 +- **无障碍访问**: 支持键盘导航和屏幕阅读器 +- **性能优化**: 懒加载和虚拟化提升性能 + +## 🔧 技术实现 + +### 依赖库 +- `react-markdown`: Markdown渲染 +- `remark-gfm`: GitHub风格Markdown支持 +- `lucide-react`: 图标库 +- `@testing-library/react`: 单元测试 + +### 样式系统 +- **CSS类**: 使用语义化CSS类名 +- **设计系统**: 遵循统一的设计系统 +- **响应式**: 移动优先的响应式设计 +- **动画**: CSS动画和过渡效果 + +### 状态管理 +- **本地状态**: 使用React Hooks管理组件状态 +- **全局状态**: 与现有状态管理系统集成 +- **缓存策略**: 智能缓存提升性能 + +## 📱 使用示例 + +### 基础用法 +```tsx +import { EnhancedChatMessageV2 } from './components/EnhancedChatMessageV2'; + +function ChatInterface() { + return ( +
+ {messages.map((message) => ( + + ))} +
+ ); +} +``` + +### 自定义配置 +```tsx + +``` + +## 🧪 测试 + +### 单元测试 +```bash +npm test -- EnhancedChatMessageV2.test.tsx +``` + +### 功能测试 +- 消息渲染测试 +- 角标引用测试 +- 素材卡片测试 +- 交互功能测试 + +## 🚀 性能优化 + +### 渲染优化 +- **虚拟化**: 长列表虚拟化 +- **懒加载**: 图片和素材懒加载 +- **缓存**: 智能缓存策略 +- **防抖**: 用户输入防抖处理 + +### 内存管理 +- **清理**: 及时清理事件监听器 +- **优化**: 避免内存泄漏 +- **回收**: 自动垃圾回收 + +## 📋 待办事项 + +- [ ] 添加更多Markdown组件支持 +- [ ] 实现素材详情查看功能 +- [ ] 添加更多动画效果 +- [ ] 优化移动端体验 +- [ ] 添加国际化支持 + +## 🔗 相关文档 + +- [Frontend Developer 规范](../../promptx/frontend-developer/) +- [设计系统文档](../../styles/design-system.css) +- [组件测试指南](../__tests__/README.md) diff --git a/apps/desktop/src/styles/design-system.css b/apps/desktop/src/styles/design-system.css index 5a127d6..0fb6fb7 100644 --- a/apps/desktop/src/styles/design-system.css +++ b/apps/desktop/src/styles/design-system.css @@ -2285,6 +2285,155 @@ main::-webkit-scrollbar-thumb:hover { background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); } +/* 增强聊天界面样式 */ +.enhanced-chat-interface { + background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); +} + +.chat-message-bubble { + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.chat-message-bubble.user { + background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); + box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3); +} + +.chat-message-bubble.assistant { + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +} + +/* 引用角标样式 */ +.reference-footnote { + background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%); + box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +.reference-footnote:hover { + transform: scale(1.1); + box-shadow: 0 4px 8px rgba(59, 130, 246, 0.4); +} + +/* 素材卡片样式 */ +.chat-material-card { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border: 1px solid rgba(229, 231, 235, 0.8); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.chat-material-card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); + border-color: rgba(59, 130, 246, 0.3); +} + +/* 引用提示框样式 */ +.reference-tooltip { + background: rgba(255, 255, 255, 0.98); + backdrop-filter: blur(20px); + border: 1px solid rgba(229, 231, 235, 0.8); + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +/* 动画效果 */ +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in { + animation: fade-in 0.3s ease-out; +} + +/* 文本高亮效果 */ +.text-highlight { + background: linear-gradient(120deg, rgba(59, 130, 246, 0.1) 0%, rgba(147, 197, 253, 0.1) 100%); + border-bottom: 1px dotted rgba(59, 130, 246, 0.5); + transition: all 0.2s ease; + border-radius: 2px; + padding: 1px 2px; +} + +.text-highlight:hover { + background: linear-gradient(120deg, rgba(59, 130, 246, 0.15) 0%, rgba(147, 197, 253, 0.15) 100%); + border-bottom-color: rgba(59, 130, 246, 0.8); +} + +/* 聊天消息动画 */ +@keyframes message-slide-in { + from { + opacity: 0; + transform: translateY(20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.chat-message-enter { + animation: message-slide-in 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* 素材网格布局优化 */ +.material-grid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); +} + +.material-grid.compact { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: 0.75rem; +} + +/* 响应式优化 */ +@media (max-width: 768px) { + .material-grid { + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 0.5rem; + } + + .chat-message-bubble { + max-width: 85%; + } + + .reference-footnote { + width: 1.25rem; + height: 1.25rem; + font-size: 0.625rem; + } +} + +/* 滚动条样式优化 */ +.chat-scroll::-webkit-scrollbar { + width: 6px; +} + +.chat-scroll::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.05); + border-radius: 3px; +} + +.chat-scroll::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.2); + border-radius: 3px; +} + +.chat-scroll::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.3); +} + .thumbnail-container::before { content: ''; position: absolute; diff --git a/apps/desktop/src/utils/testChatFunction.ts b/apps/desktop/src/utils/testChatFunction.ts index d5d2ddb..844d394 100644 --- a/apps/desktop/src/utils/testChatFunction.ts +++ b/apps/desktop/src/utils/testChatFunction.ts @@ -92,7 +92,6 @@ export async function testConfig(): Promise { */ export async function testSimpleQuery(): Promise { try { - console.log('🔍 测试简单查询...'); const result = await queryRagGrounding('你好,请介绍一下自己', { sessionId: 'test-session', includeMetadata: true, @@ -100,26 +99,6 @@ export async function testSimpleQuery(): Promise { }); if (result.success && result.data) { - console.group('✅ 简单查询测试成功'); - console.log('💬 AI 回答:', result.data.answer); - console.log('⏱️ 响应时间:', result.data.response_time_ms + 'ms'); - console.log('🤖 使用模型:', result.data.model_used); - console.log('🕐 总耗时:', result.totalTime + 'ms'); - - if (result.data.grounding_metadata?.sources) { - console.log('📚 参考来源数量:', result.data.grounding_metadata.sources.length); - result.data.grounding_metadata.sources.forEach((source, index) => { - const contentPreview = source.content - ? (typeof source.content === 'string' ? source.content.substring(0, 50) : JSON.stringify(source.content).substring(0, 50)) - : '无内容'; - console.log(` ${index + 1}. ${source.title}: ${contentPreview}...`); - }); - } - - if (result.data.grounding_metadata?.search_queries) { - console.log('🔍 搜索查询:', result.data.grounding_metadata.search_queries); - } - console.groupEnd(); return true; } else { console.error('❌ 查询失败:', result.error); diff --git a/examples/rag-grounding-usage.ts b/examples/rag-grounding-usage.ts index b803a83..4a8ad56 100644 --- a/examples/rag-grounding-usage.ts +++ b/examples/rag-grounding-usage.ts @@ -39,10 +39,6 @@ async function basicQueryExample() { console.log('参考来源:'); result.data.grounding_metadata.sources.forEach((source, index) => { console.log(` ${index + 1}. ${source.title}`); - console.log(` 片段: ${source.snippet}`); - if (source.relevance_score) { - console.log(` 相关性: ${(source.relevance_score * 100).toFixed(1)}%`); - } }); } } else { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd2606b..e8d9ff0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,18 +63,30 @@ importers: lucide-react: specifier: ^0.294.0 version: 0.294.0(react@18.3.1) + marked: + specifier: ^16.1.1 + version: 16.1.1 react: specifier: ^18.3.1 version: 18.3.1 react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@18.3.23)(react@18.3.1) react-router-dom: specifier: ^6.20.1 version: 6.30.1(react-dom@18.3.1)(react@18.3.1) react-window: specifier: ^1.8.11 version: 1.8.11(react-dom@18.3.1)(react@18.3.1) + rehype-highlight: + specifier: ^7.0.2 + version: 7.0.2 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 zustand: specifier: ^4.4.7 version: 4.5.7(@types/react@18.3.23)(react@18.3.1) @@ -1374,9 +1386,36 @@ packages: '@babel/types': 7.28.1 dev: true + /@types/debug@4.1.12: + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + dependencies: + '@types/ms': 2.1.0 + dev: false + + /@types/estree-jsx@1.0.5: + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + dependencies: + '@types/estree': 1.0.8 + dev: false + /@types/estree@1.0.8: resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - dev: true + + /@types/hast@3.0.4: + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /@types/mdast@4.0.4: + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /@types/ms@2.1.0: + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + dev: false /@types/node@24.0.13: resolution: {integrity: sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==} @@ -1406,9 +1445,16 @@ packages: '@types/prop-types': 15.7.15 csstype: 3.1.3 + /@types/unist@2.0.11: + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + dev: false + + /@types/unist@3.0.3: + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + dev: false + /@ungap/structured-clone@1.3.0: resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - dev: true /@vitejs/plugin-react@4.6.0(vite@6.3.5): resolution: {integrity: sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==} @@ -1604,6 +1650,10 @@ packages: possible-typed-array-names: 1.1.0 dev: true + /bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true @@ -1705,6 +1755,10 @@ packages: resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} dev: true + /ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + dev: false + /chai@4.5.0: resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} engines: {node: '>=4'} @@ -1734,6 +1788,22 @@ packages: supports-color: 7.2.0 dev: true + /character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + dev: false + + /character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + dev: false + + /character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + dev: false + + /character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + dev: false + /check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} dependencies: @@ -1794,6 +1864,10 @@ packages: delayed-stream: 1.0.0 dev: true + /comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + dev: false + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1894,12 +1968,17 @@ packages: optional: true dependencies: ms: 2.1.3 - dev: true /decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} dev: true + /decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + dependencies: + character-entities: 2.0.2 + dev: false + /deep-eql@4.1.4: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} @@ -1961,7 +2040,12 @@ packages: /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - dev: true + + /devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dependencies: + dequal: 2.0.3 + dev: false /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -2137,6 +2221,11 @@ packages: engines: {node: '>=10'} dev: true + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: false + /eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2226,6 +2315,10 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + dev: false + /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: @@ -2252,6 +2345,10 @@ packages: strip-final-newline: 3.0.0 dev: true + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -2523,6 +2620,54 @@ packages: function-bind: 1.1.2 dev: true + /hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + dependencies: + '@types/hast': 3.0.4 + dev: false + + /hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.17 + unist-util-position: 5.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: false + + /hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + dev: false + + /hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + dependencies: + '@types/hast': 3.0.4 + dev: false + + /highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + dev: false + /html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -2530,6 +2675,10 @@ packages: whatwg-encoding: 3.1.1 dev: true + /html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + dev: false + /http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -2597,6 +2746,10 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true + /inline-style-parser@0.2.4: + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + dev: false + /internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -2606,6 +2759,17 @@ packages: side-channel: 1.1.0 dev: true + /is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + dev: false + + /is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + dev: false + /is-arguments@1.2.0: resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} engines: {node: '>= 0.4'} @@ -2665,6 +2829,10 @@ packages: has-tostringtag: 1.0.2 dev: true + /is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + dev: false + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2682,6 +2850,10 @@ packages: is-extglob: 2.1.1 dev: true + /is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + dev: false + /is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -2705,6 +2877,11 @@ packages: engines: {node: '>=8'} dev: true + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + dev: false + /is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: true @@ -2921,6 +3098,10 @@ packages: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true + /longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + dev: false + /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2933,6 +3114,14 @@ packages: get-func-name: 2.0.2 dev: true + /lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.11.1 + dev: false + /lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} dev: true @@ -2962,11 +3151,204 @@ packages: '@jridgewell/sourcemap-codec': 1.5.4 dev: true + /markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + dev: false + + /marked@16.1.1: + resolution: {integrity: sha512-ij/2lXfCRT71L6u0M29tJPhP0bM5shLL3u5BePhFwPELj2blMJ6GDtD7PfJhRLhJ/c2UwrK17ySVcDzy2YHjHQ==} + engines: {node: '>= 20'} + hasBin: true + dev: false + /math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} dev: true + /mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + dev: false + + /mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + dev: false + + /mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + dev: false + + /mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + dev: false + + /mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + dev: false + + /mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + dependencies: + '@types/mdast': 4.0.4 + dev: false + /mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} dev: true @@ -2984,6 +3366,253 @@ packages: engines: {node: '>= 8'} dev: true + /micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + dependencies: + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + dependencies: + micromark-util-symbol: 2.0.1 + dev: false + + /micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + dependencies: + micromark-util-symbol: 2.0.1 + dev: false + + /micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + dev: false + + /micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + dev: false + + /micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + dev: false + + /micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + dependencies: + micromark-util-symbol: 2.0.1 + dev: false + + /micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + dependencies: + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + dev: false + + /micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + dev: false + + /micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + dev: false + + /micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.1 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -3043,7 +3672,6 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -3181,6 +3809,18 @@ packages: callsites: 3.1.0 dev: true + /parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.2.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + dev: false + /parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} dependencies: @@ -3389,6 +4029,10 @@ packages: react-is: 18.3.1 dev: true + /property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + dev: false + /psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} dependencies: @@ -3425,6 +4069,29 @@ packages: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} dev: true + /react-markdown@10.1.0(@types/react@18.3.23)(react@18.3.1): + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 18.3.23 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.0 + react: 18.3.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + dev: false + /react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -3510,6 +4177,58 @@ packages: set-function-name: 2.0.2 dev: true + /rehype-highlight@7.0.2: + resolution: {integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==} + dependencies: + '@types/hast': 3.0.4 + hast-util-to-text: 4.0.2 + lowlight: 3.3.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + dev: false + + /remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + dev: false + + /remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + dev: false + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -3741,6 +4460,10 @@ packages: whatwg-url: 7.1.0 dev: true + /space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + dev: false + /spawn-command@0.0.2: resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} dev: true @@ -3779,6 +4502,13 @@ packages: strip-ansi: 7.1.0 dev: true + /stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + dev: false + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3816,6 +4546,18 @@ packages: js-tokens: 9.0.1 dev: true + /style-to-js@1.1.17: + resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} + dependencies: + style-to-object: 1.0.9 + dev: false + + /style-to-object@1.0.9: + resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==} + dependencies: + inline-style-parser: 0.2.4 + dev: false + /sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -3962,6 +4704,14 @@ packages: hasBin: true dev: true + /trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + dev: false + + /trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + dev: false + /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true @@ -4062,6 +4812,58 @@ packages: resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} dev: true + /unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + dev: false + + /unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + dev: false + + /unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + dev: false + + /unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + dev: false + /universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} @@ -4103,6 +4905,20 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true + /vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + dev: false + + /vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + dev: false + /vite-node@1.6.1(@types/node@24.0.13): resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -4480,3 +5296,7 @@ packages: react: 18.3.1 use-sync-external-store: 1.5.0(react@18.3.1) dev: false + + /zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + dev: false