From 0c2e7fe4220470ba302943c61537cc1cd73a968a Mon Sep 17 00:00:00 2001 From: Shanshan Date: Wed, 20 Aug 2025 20:18:59 +0800 Subject: [PATCH] chore: add ask ai demo --- package.json | 4 + src/app/[locale]/layout.tsx | 2 + src/app/api/chat/route.ts | 105 +++++ src/app/sitemap.ts | 2 +- src/components/AIChatBox.tsx | 730 +++++++++++++++++++++++++++++++++ src/components/KIcon.tsx | 26 ++ src/components/SearchBar.tsx | 59 ++- src/components/SearchModal.tsx | 115 ++++-- src/locales/en.ts | 20 + src/locales/zh.ts | 20 + yarn.lock | 232 +++++++++++ 11 files changed, 1251 insertions(+), 64 deletions(-) create mode 100644 src/app/api/chat/route.ts create mode 100644 src/components/AIChatBox.tsx create mode 100644 src/components/KIcon.tsx diff --git a/package.json b/package.json index 8d0067d8..1747c722 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-scroll": "^1.8.10", "@types/remark-heading-id": "^1.0.0", + "axios": "^1.11.0", "cheerio": "^1.0.0", "fast-glob": "^3.3.3", "gray-matter": "^4.0.3", @@ -44,9 +45,11 @@ "react": "^19.0.0", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^19.0.0", + "react-markdown": "^9.0.1", "react-scroll": "^1.9.3", "react-slick": "^0.30.3", "react-stickynode": "^5.0.2", + "react-syntax-highlighter": "^15.5.0", "read-yaml-file": "^2.1.0", "rehype-highlight": "^7.0.2", "rehype-highlight-code-lines": "^1.1.3", @@ -72,6 +75,7 @@ "@types/react-dom": "^19", "@types/react-slick": "^0.23.13", "@types/react-stickynode": "^4.0.3", + "@types/react-syntax-highlighter": "^15.5.13", "eslint": "^9", "eslint-config-next": "^15.4.5", "eslint-config-prettier": "^10.1.8", diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 40a286bd..5b3e013b 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,3 +1,4 @@ +import AIChatBox from '@/components/AIChatBox'; import I18nProvider from '@/components/I18nProvider'; import MessageBox from '@/components/MessageBox'; import { MuiThemeProvider } from '@/components/MuiThemeProvider'; @@ -47,6 +48,7 @@ export default async function RootLayout({ {children} + diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts new file mode 100644 index 00000000..c0dd788d --- /dev/null +++ b/src/app/api/chat/route.ts @@ -0,0 +1,105 @@ +import axios from 'axios'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function POST(request: NextRequest) { + try { + const { message } = await request.json(); + + if (!message) { + return NextResponse.json( + { error: 'Message is required' }, + { status: 400 }, + ); + } + + // 检查必要的环境变量 + const apiKey = process.env.DASHSCOPE_API_KEY; + const appId = process.env.DASHSCOPE_APP_ID; + const pipelineIds = process.env.DASHSCOPE_PIPELINE_IDS?.split(',') || []; + + if (!apiKey) { + console.error('DASHSCOPE_API_KEY not configured'); + return NextResponse.json( + { error: 'AI service not configured' }, + { status: 500 }, + ); + } + + if (!appId) { + console.error('DASHSCOPE_APP_ID not configured'); + return NextResponse.json( + { error: 'AI service not configured' }, + { status: 500 }, + ); + } + + const url = `https://dashscope.aliyuncs.com/api/v1/apps/${appId}/completion`; + + const data = { + input: { + prompt: message, + }, + parameters: { + rag_options: { + pipeline_ids: pipelineIds.length > 0 ? pipelineIds : undefined, + }, + }, + debug: {}, + }; + + const response = await axios.post(url, data, { + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + timeout: 30000, // 30 seconds timeout + }); + + if (response.status === 200) { + const aiResponse = + response.data.output?.text || 'Sorry, I cannot answer this question.'; + return NextResponse.json({ response: aiResponse }); + } else { + console.error('DashScope API error:', { + status: response.status, + data: response.data, + requestId: response.headers['request_id'], + }); + return NextResponse.json( + { error: 'AI service temporarily unavailable' }, + { status: 500 }, + ); + } + } catch (error: any) { + console.error('Error calling DashScope API:', error.message); + + if (error.response) { + console.error('Response status:', error.response.status); + console.error('Response data:', error.response.data); + } + + // Return different responses based on error type + if (error.code === 'ECONNABORTED') { + return NextResponse.json({ error: 'Request timeout' }, { status: 408 }); + } + + if (error.response?.status === 401) { + return NextResponse.json( + { error: 'API authentication failed' }, + { status: 401 }, + ); + } + + if (error.response?.status === 429) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { status: 429 }, + ); + } + + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 }, + ); + } +} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 7d458449..0c1d7d4c 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -39,7 +39,7 @@ export default function sitemap(): MetadataRoute.Sitemap { ); // ignore some category - if(["release_notes"].includes(category)) { + if (['release_notes'].includes(category)) { return; } diff --git a/src/components/AIChatBox.tsx b/src/components/AIChatBox.tsx new file mode 100644 index 00000000..541eacd6 --- /dev/null +++ b/src/components/AIChatBox.tsx @@ -0,0 +1,730 @@ +'use client'; + +import { useI18n } from '@/locales/client'; +import { + Chat as ChatIcon, + Close as CloseIcon, + Person as PersonIcon, + Send as SendIcon, +} from '@mui/icons-material'; +import { + Avatar, + Box, + Button, + CircularProgress, + Divider, + Fade, + IconButton, + Paper, + Slide, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, +} from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import React, { useEffect, useRef, useState } from 'react'; +import ReactMarkdown from 'react-markdown'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { + oneDark, + oneLight, +} from 'react-syntax-highlighter/dist/esm/styles/prism'; +import remarkGfm from 'remark-gfm'; +import KIcon from './KIcon'; + +interface Message { + id: string; + text: string; + sender: 'user' | 'ai'; + timestamp: Date; +} + +interface AIChatBoxProps { + onSendMessage?: (message: string) => Promise; +} + +// Markdown message component +const MarkdownMessage: React.FC<{ content: string; isDarkMode: boolean }> = ({ + content, + isDarkMode, +}) => { + return ( + + {String(children).replace(/\n$/, '')} + + ) : ( + + {children} + + ); + }, + p: ({ children }) => ( + + {children} + + ), + ul: ({ children }) => ( + + {children} + + ), + ol: ({ children }) => ( + + {children} + + ), + li: ({ children }) => ( + + {children} + + ), + h1: ({ children }) => ( + + {children} + + ), + h2: ({ children }) => ( + + {children} + + ), + h3: ({ children }) => ( + + {children} + + ), + blockquote: ({ children }) => ( + + {children} + + ), + strong: ({ children }) => ( + + {children} + + ), + em: ({ children }) => ( + + {children} + + ), + table: ({ children }) => ( + + + {children} +
+
+ ), + thead: ({ children }) => {children}, + tbody: ({ children }) => {children}, + tr: ({ children }) => {children}, + th: ({ children }) => ( + + + {children} + + + ), + td: ({ children }) => ( + + + {children} + + + ), + }} + > + {content} +
+ ); +}; + +const AIChatBox: React.FC = ({ onSendMessage }) => { + const t = useI18n(); + const [isOpen, setIsOpen] = useState(false); + const [messages, setMessages] = useState([ + { + id: '1', + text: t('aiChatBox.welcomeMessage'), + sender: 'ai', + timestamp: new Date(), + }, + ]); + const [inputValue, setInputValue] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const messagesEndRef = useRef(null); + const theme = useTheme(); + const isDarkMode = theme.palette.mode === 'dark'; + + // Chat box size state + const [chatSize, setChatSize] = useState({ width: 380, height: 700 }); + const [isResizing, setIsResizing] = useState(false); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + // Resize functionality for edges + const handleEdgeMouseDown = (edge: 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right') => { + return (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + + const startX = event.clientX; + const startY = event.clientY; + const startWidth = chatSize.width; + const startHeight = chatSize.height; + + const handleMouseMove = (e: MouseEvent) => { + e.preventDefault(); + + let newWidth = startWidth; + let newHeight = startHeight; + + // Calculate new dimensions based on edge + if (edge.includes('left')) { + const deltaX = startX - e.clientX; + newWidth = Math.max(300, Math.min(600, startWidth + deltaX)); + } + if (edge.includes('right')) { + const deltaX = e.clientX - startX; + newWidth = Math.max(300, Math.min(600, startWidth + deltaX)); + } + if (edge.includes('top')) { + const deltaY = startY - e.clientY; + newHeight = Math.max(400, Math.min(900, startHeight + deltaY)); + } + if (edge.includes('bottom')) { + const deltaY = e.clientY - startY; + newHeight = Math.max(400, Math.min(900, startHeight + deltaY)); + } + + setChatSize({ width: newWidth, height: newHeight }); + }; + + const handleMouseUp = () => { + setIsResizing(false); + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + + setIsResizing(true); + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }; + }; + + + + const handleToggle = () => { + setIsOpen(!isOpen); + }; + + const handleSendMessage = async () => { + if (!inputValue.trim() || isLoading) return; + + const userMessage: Message = { + id: Date.now().toString(), + text: inputValue, + sender: 'user', + timestamp: new Date(), + }; + + setMessages((prev) => [...prev, userMessage]); + setInputValue(''); + setIsLoading(true); + + try { + let aiResponse = ''; + + if (onSendMessage) { + aiResponse = await onSendMessage(inputValue); + } else { + // Call DashScope API + try { + const response = await fetch('/api/chat', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ message: inputValue }), + }); + + if (response.ok) { + const data = await response.json(); + aiResponse = data.response; + } else { + // Use default response when API call fails + console.warn('API call failed, using default response'); + aiResponse = generateDefaultResponse(inputValue); + } + } catch (apiError) { + // Use default response when network error occurs + console.warn('Network error, using default response:', apiError); + aiResponse = generateDefaultResponse(inputValue); + } + } + + const aiMessage: Message = { + id: (Date.now() + 1).toString(), + text: aiResponse, + sender: 'ai', + timestamp: new Date(), + }; + + setMessages((prev) => [...prev, aiMessage]); + } catch { + const errorMessage: Message = { + id: (Date.now() + 1).toString(), + text: t('aiChatBox.errorMessage'), + sender: 'ai', + timestamp: new Date(), + }; + setMessages((prev) => [...prev, errorMessage]); + } finally { + setIsLoading(false); + } + }; + + const generateDefaultResponse = (userInput: string): string => { + const input = userInput.toLowerCase(); + + if (input.includes('安装') || input.includes('install')) { + return t('aiChatBox.defaultResponses.install'); + } + + if (input.includes('数据库') || input.includes('database')) { + return t('aiChatBox.defaultResponses.database'); + } + + if (input.includes('监控') || input.includes('monitor')) { + return t('aiChatBox.defaultResponses.monitoring'); + } + + if (input.includes('备份') || input.includes('backup')) { + return t('aiChatBox.defaultResponses.backup'); + } + + return t('aiChatBox.defaultResponses.default'); + }; + + const handleKeyPress = (event: React.KeyboardEvent) => { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + handleSendMessage(); + } + }; + + return ( + <> + {/* Chat button */} + + + + + + + + + {/* Chat window */} + + + {/* Header */} + + + + {t('aiChatBox.title')} + + + + + + + {/* Messages area */} + + {messages.map((message) => ( + + + {message.sender === 'user' ? ( + + ) : ( + + )} + + + {message.sender === 'ai' ? ( + + ) : ( + + {message.text} + + )} + + + ))} + + {isLoading && ( + + + + + + + + {t('aiChatBox.thinking')} + + + + )} + +
+ + + + + {/* Input area */} + + + setInputValue(e.target.value)} + onKeyPress={handleKeyPress} + disabled={isLoading} + variant="outlined" + size="small" + /> + + + + + {/* Invisible resize edges */} + {/* Top edge */} + + {/* Bottom edge */} + + {/* Left edge */} + + {/* Right edge */} + + {/* Corner resize areas */} + {/* Top-left corner */} + + {/* Top-right corner */} + + {/* Bottom-left corner */} + + {/* Bottom-right corner */} + + + + + ); +}; + +export default AIChatBox; diff --git a/src/components/KIcon.tsx b/src/components/KIcon.tsx new file mode 100644 index 00000000..f3fe4238 --- /dev/null +++ b/src/components/KIcon.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { SvgIcon, SvgIconProps } from '@mui/material'; +import React from 'react'; + +const KIcon: React.FC = (props) => { + return ( + + {/* KB Text */} + + KB + + + ); +}; + +export default KIcon; diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index 7d6504e2..5873e440 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -1,4 +1,5 @@ 'use client'; +import { searchBarComponentStyles } from '@/styles/searchBarComponent.styles'; import { Box, List, @@ -7,7 +8,6 @@ import { Paper, TextField, } from '@mui/material'; -import { searchBarComponentStyles } from '@/styles/searchBarComponent.styles'; import MiniSearch from 'minisearch'; import { useRouter } from 'next/navigation'; import { useCallback, useEffect, useRef, useState } from 'react'; @@ -100,38 +100,58 @@ export const SearchBar = () => { const index = new MiniSearch({ // 扩展搜索字段 - fields: ['title', 'content', 'summary', 'keywordsText', 'headingsText', 'category', 'tagsText'], + fields: [ + 'title', + 'content', + 'summary', + 'keywordsText', + 'headingsText', + 'category', + 'tagsText', + ], // 存储更多字段 - storeFields: ['id', 'title', 'path', 'summary', 'category', 'docType', 'wordCount'], + storeFields: [ + 'id', + 'title', + 'path', + 'summary', + 'category', + 'docType', + 'wordCount', + ], searchOptions: { // 优化权重 - boost: { + boost: { title: 3, summary: 2, keywordsText: 2, headingsText: 1.5, category: 1.5, tagsText: 1.2, - content: 1 + content: 1, }, fuzzy: 0.2, prefix: true, }, // 自定义分词器 tokenize: (string) => { - return string - .toLowerCase() - .replace(/([a-z])([A-Z])/g, '$1 $2') - .replace(/[-_]/g, ' ') - .match(/\b\w+\b/g) || []; + return ( + string + .toLowerCase() + .replace(/([a-z])([A-Z])/g, '$1 $2') + .replace(/[-_]/g, ' ') + .match(/\b\w+\b/g) || [] + ); }, }); // 处理数据,转换字段为搜索友好的格式 - const processedDocs = documents.map(doc => ({ + const processedDocs = documents.map((doc) => ({ ...doc, keywordsText: doc.keywords ? doc.keywords.join(' ') : '', - headingsText: doc.headings ? doc.headings.map((h: any) => h.text).join(' ') : '', + headingsText: doc.headings + ? doc.headings.map((h: any) => h.text).join(' ') + : '', tagsText: doc.tags ? doc.tags.join(' ') : '', })); @@ -231,7 +251,7 @@ export const SearchBar = () => { sx={searchBarComponentStyles.resultItem} > {highlightText(result.title, searchTerm)} {result.category && ( @@ -244,15 +264,18 @@ export const SearchBar = () => { secondary={
- {result.summary ? - highlightText(result.summary.slice(0, 100) + '...', searchTerm) : - highlightText(result.path, searchTerm) - } + {result.summary + ? highlightText( + result.summary.slice(0, 100) + '...', + searchTerm, + ) + : highlightText(result.path, searchTerm)}
{result.docType === 'blog' ? '📝 Blog' : '📚 Docs'} {result.wordCount && ` • ${result.wordCount} words`} - {result.score && ` • ${Math.round(result.score * 100)}% match`} + {result.score && + ` • ${Math.round(result.score * 100)}% match`}
} diff --git a/src/components/SearchModal.tsx b/src/components/SearchModal.tsx index fdeb3e09..f78772cb 100644 --- a/src/components/SearchModal.tsx +++ b/src/components/SearchModal.tsx @@ -1,7 +1,7 @@ +import { searchModalStyles } from '@/styles/searchModal.styles'; import SearchIcon from '@mui/icons-material/Search'; import MiniSearch from 'minisearch'; import { useEffect, useRef, useState } from 'react'; -import { searchModalStyles } from '@/styles/searchModal.styles'; interface SearchResult { id: string; @@ -44,13 +44,33 @@ export default function SearchModal({ .then((res) => res.json()) .then((json) => { const ms = new MiniSearch({ - // 扩展搜索字段,包含更多元数据 - fields: ['title', 'content', 'summary', 'keywords', 'headings', 'category', 'tags'], - // 存储更多字段用于显示 - storeFields: ['id', 'title', 'path', 'content', 'description', 'summary', 'keywords', 'category', 'docType', 'headings', 'wordCount'], - // 优化搜索选项 + // Expand search fields to include more metadata + fields: [ + 'title', + 'content', + 'summary', + 'keywords', + 'headings', + 'category', + 'tags', + ], + // Store more fields for display + storeFields: [ + 'id', + 'title', + 'path', + 'content', + 'description', + 'summary', + 'keywords', + 'category', + 'docType', + 'headings', + 'wordCount', + ], + // Optimize search options searchOptions: { - // 字段权重:标题最重要,然后是摘要和关键词 + // Field weights: title is most important, then summary and keywords boost: { title: 3, summary: 2, @@ -58,31 +78,33 @@ export default function SearchModal({ headings: 1.5, category: 1.5, tags: 1.2, - content: 1 + content: 1, }, - // 启用模糊搜索,容忍拼写错误 + // Enable fuzzy search, tolerate spelling errors fuzzy: 0.2, - // 启用前缀匹配 + // Enable prefix matching prefix: true, - // 组合多个字段的匹配 + // Combine multiple field matches combineWith: 'AND', }, - // 自定义分词器,处理驼峰命名和特殊字符 + // Custom tokenizer, handle camel case and special characters tokenize: (string) => { - return string - .toLowerCase() - // 处理驼峰命名 - .replace(/([a-z])([A-Z])/g, '$1 $2') - // 处理连字符和下划线 - .replace(/[-_]/g, ' ') - // 提取单词 - .match(/\b\w+\b/g) || []; + return ( + string + .toLowerCase() + // Handle camel case + .replace(/([a-z])([A-Z])/g, '$1 $2') + // Handle hyphens and underscores + .replace(/[-_]/g, ' ') + // Extract words + .match(/\b\w+\b/g) || [] + ); }, - // 自定义处理器,提取更多有用信息 + // Custom processor, extract more useful information processTerm: (term) => { - // 保留原词和词根 + // Preserve original term and root const processed = [term]; - // 简单的词根提取(移除常见后缀) + // Simple root extraction (remove common suffixes) if (term.length > 4) { const stemmed = term.replace(/(ing|ed|er|est|ly|tion|sion)$/, ''); if (stemmed !== term && stemmed.length > 2) { @@ -90,14 +112,16 @@ export default function SearchModal({ } } return processed; - } + }, }); - // 处理数据,确保keywords和headings是可搜索的文本 + // Process data, ensure keywords and headings are searchable text const processedDocs = json.map((doc: any) => ({ ...doc, keywords: doc.keywords ? doc.keywords.join(' ') : '', - headings: doc.headings ? doc.headings.map((h: any) => h.text).join(' ') : '', + headings: doc.headings + ? doc.headings.map((h: any) => h.text).join(' ') + : '', tags: doc.tags ? doc.tags.join(' ') : '', })); @@ -108,19 +132,19 @@ export default function SearchModal({ useEffect(() => { if (miniSearch && query.trim()) { - // 使用优化的搜索选项 + // Use optimized search options const rawResults = miniSearch.search(query, { prefix: true, fuzzy: 0.2, - // 根据查询长度调整策略 + // Adjust strategy based on query length combineWith: query.length > 10 ? 'OR' : 'AND', - // 提高结果质量阈值 + // Increase result quality threshold filter: (result) => result.score > 0.5, }); - // 处理和增强搜索结果 + // Process and enhance search results const enhancedResults = rawResults.map((r) => { - // 生成上下文摘要 + // Generate context summary const contextSummary = generateContextSummary(r.content, query); return { @@ -148,14 +172,14 @@ export default function SearchModal({ } }, [miniSearch, query]); - // 生成包含查询上下文的摘要 + // Generate context summary with query context const generateContextSummary = (content: string, query: string) => { if (!content || !query) return ''; const queryWords = query.toLowerCase().split(/\s+/); const sentences = content.split(/[.!?]+/).filter((s: string) => s.trim()); - // 找到包含查询词的句子 + // Find sentences containing query words const relevantSentences = sentences.filter((sentence: string) => { const lowerSentence = sentence.toLowerCase(); return queryWords.some((word: string) => lowerSentence.includes(word)); @@ -165,11 +189,11 @@ export default function SearchModal({ return content.slice(0, 200) + '...'; } - // 返回最相关的句子 + // Return most relevant sentence return relevantSentences[0].trim().slice(0, 200) + '...'; }; - // 关闭逻辑和键盘导航 + // Close logic and keyboard navigation useEffect(() => { function onKeyDown(e: KeyboardEvent) { if (e.key === 'Escape') onClose(); @@ -207,7 +231,10 @@ export default function SearchModal({ return (
-
e.stopPropagation()}> +
e.stopPropagation()} + >
0 ? (
    {results.slice(0, 10).map((r, idx) => { - const summary = r.contextSummary || r.description || r.content?.slice(0, 300) || ''; + const summary = + r.contextSummary || + r.description || + r.content?.slice(0, 300) || + ''; return (
  • (window.location.href = `/${r.path}`)} >
    -
    - {r.title} -
    +
    {r.title}
    {r.category && ( {r.category} @@ -266,14 +295,10 @@ export default function SearchModal({ })}
) : ( -
- No results -
+
No results
)}
); } - - diff --git a/src/locales/en.ts b/src/locales/en.ts index 101df434..2b45e529 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -24,4 +24,24 @@ export default { text: { version: 'Version', }, + aiChatBox: { + title: 'KubeBlocks AI', + placeholder: 'Ask your question...', + thinking: 'Thinking...', + welcomeMessage: + "Hello! I'm the KubeBlocks AI assistant. I can help you understand how to use KubeBlocks, its features, and best practices. How can I help you?", + defaultResponses: { + install: + 'To install KubeBlocks, you can use Helm or kbcli. We recommend using kbcli:\n\n```bash\ncurl -fsSL https://kubeblocks.io/installer/install_cli.sh | bash\nkbcli kubeblocks install\n```\n\nFor detailed installation instructions, please check our documentation.', + database: + 'KubeBlocks supports multiple databases including:\n- MySQL\n- PostgreSQL\n- MongoDB\n- Redis\n- Kafka\n- Elasticsearch\n- And more\n\nWhich database would you like to learn more about?', + monitoring: + 'KubeBlocks provides powerful monitoring capabilities with integrated Prometheus and Grafana. You can:\n- View cluster status\n- Monitor resource usage\n- Set up alert rules\n\nNeed help configuring monitoring?', + backup: + 'KubeBlocks supports automated backup and restore features:\n- Scheduled backups\n- Incremental backups\n- Cross-cloud backups\n- One-click restore\n\nWhich backup scenario would you like to learn about?', + default: + 'Thank you for your question! I can help you learn about various KubeBlocks features including installation, database management, monitoring, backups, and more. Feel free to ask me specific questions or browse our documentation for detailed information.', + }, + errorMessage: 'Sorry, I encountered some issues. Please try again later.', + }, } as const; diff --git a/src/locales/zh.ts b/src/locales/zh.ts index 4f5cdb33..0bd64834 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -24,4 +24,24 @@ export default { text: { version: '版本', }, + aiChatBox: { + title: 'KubeBlocks AI', + placeholder: '输入你的问题...', + thinking: '正在思考...', + welcomeMessage: + '你好!我是 KubeBlocks AI 助手。我可以帮助你了解 KubeBlocks 的使用方法、特性和最佳实践。有什么我可以帮助你的吗?', + defaultResponses: { + install: + '要安装 KubeBlocks,你可以使用 Helm 或 kbcli。推荐使用 kbcli:\n\n```bash\ncurl -fsSL https://kubeblocks.io/installer/install_cli.sh | bash\nkbcli kubeblocks install\n```\n\n详细的安装指南请查看我们的文档。', + database: + 'KubeBlocks 支持多种数据库,包括:\n- MySQL\n- PostgreSQL\n- MongoDB\n- Redis\n- Kafka\n- Elasticsearch\n- 等等\n\n你想了解哪个数据库的具体使用方法?', + monitoring: + 'KubeBlocks 提供了强大的监控功能,集成了 Prometheus 和 Grafana。你可以:\n- 查看集群状态\n- 监控资源使用情况\n- 设置告警规则\n\n需要帮助配置监控吗?', + backup: + 'KubeBlocks 支持自动化备份和恢复功能:\n- 定时备份\n- 增量备份\n- 跨云备份\n- 一键恢复\n\n你想了解哪种备份场景?', + default: + '感谢你的问题!我可以帮助你了解 KubeBlocks 的各种功能,包括安装、数据库管理、监控、备份等。你可以问我更具体的问题,或者浏览我们的文档获取详细信息。', + }, + errorMessage: '抱歉,我遇到了一些问题。请稍后再试。', + }, } as const; diff --git a/yarn.lock b/yarn.lock index 0d39f7c1..822abb6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -57,6 +57,11 @@ resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz#2ae5a9d51cc583bd1f5673b3bb70d6d819682473" integrity sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA== +"@babel/runtime@^7.3.1": + version "7.28.3" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz#75c5034b55ba868121668be5d5bb31cc64e6e61a" + integrity sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA== + "@babel/template@^7.27.2": version "7.27.2" resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" @@ -880,6 +885,13 @@ dependencies: "@types/react" "*" +"@types/react-syntax-highlighter@^15.5.13": + version "15.5.13" + resolved "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz#c5baf62a3219b3bf28d39cfea55d0a49a263d1f2" + integrity sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA== + dependencies: + "@types/react" "*" + "@types/react-transition-group@^4.4.12": version "4.4.12" resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" @@ -1283,6 +1295,11 @@ async-function@^1.0.0: resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -1295,6 +1312,15 @@ axe-core@^4.10.0: resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz#04145965ac7894faddbac30861e5d8f11bfd14fc" integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg== +axios@^1.11.0: + version "1.11.0" + resolved "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz#c2ec219e35e414c025b2095e8b8280278478fdb6" + integrity sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.4" + proxy-from-env "^1.1.0" + axobject-query@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" @@ -1448,16 +1474,31 @@ character-entities-html4@^2.0.0: resolved "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== +character-entities-legacy@^1.0.0: + version "1.1.4" + resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== + character-entities-legacy@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== +character-entities@^1.0.0: + version "1.2.4" + resolved "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== + character-entities@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== +character-reference-invalid@^1.0.0: + version "1.1.4" + resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== + character-reference-invalid@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" @@ -1562,6 +1603,18 @@ color@^4.2.3: color-convert "^2.0.1" color-string "^1.9.0" +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +comma-separated-tokens@^1.0.0: + version "1.0.8" + resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== + comma-separated-tokens@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" @@ -1725,6 +1778,11 @@ degenerator@^5.0.0: escodegen "^2.1.0" esprima "^4.0.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + dequal@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" @@ -2394,6 +2452,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fault@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" + integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== + dependencies: + format "^0.2.0" + fault@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c" @@ -2453,6 +2518,11 @@ flatted@^3.2.9: resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +follow-redirects@^1.15.6: + version "1.15.11" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== + for-each@^0.3.3, for-each@^0.3.5: version "0.3.5" resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" @@ -2460,6 +2530,17 @@ for-each@^0.3.3, for-each@^0.3.5: dependencies: is-callable "^1.2.7" +form-data@^4.0.4: + version "4.0.4" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" + integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" + format@^0.2.0: version "0.2.2" resolved "https://registry.npmjs.org/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" @@ -2650,6 +2731,11 @@ hast-util-is-element@^3.0.0: dependencies: "@types/hast" "^3.0.0" +hast-util-parse-selector@^2.0.0: + version "2.2.5" + resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" + integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== + hast-util-parse-selector@^3.0.0: version "3.1.1" resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz#25ab00ae9e75cbc62cf7a901f68a247eade659e2" @@ -2724,6 +2810,17 @@ hast-util-whitespace@^3.0.0: dependencies: "@types/hast" "^3.0.0" +hastscript@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" + integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== + dependencies: + "@types/hast" "^2.0.0" + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + hastscript@^7.0.2: version "7.2.0" resolved "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz#0eafb7afb153d047077fa2a833dc9b7ec604d10b" @@ -2746,11 +2843,21 @@ hastscript@^9.0.1: property-information "^7.0.0" space-separated-tokens "^2.0.0" +highlight.js@^10.4.1, highlight.js@~10.7.0: + version "10.7.3" + resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + highlight.js@^11.11.1, highlight.js@~11.11.0: version "11.11.1" resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz#fca06fa0e5aeecf6c4d437239135fabc15213585" integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w== +highlightjs-vue@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz#fdfe97fbea6354e70ee44e3a955875e114db086d" + integrity sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA== + hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -2758,6 +2865,11 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1: dependencies: react-is "^16.7.0" +html-url-attributes@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" + integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== + htmlparser2@^10.0.0: version "10.0.0" resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz#77ad249037b66bf8cc99c6e286ef73b83aeb621d" @@ -2841,11 +2953,24 @@ ip-address@^9.0.5: jsbn "1.1.0" sprintf-js "^1.1.3" +is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + is-alphabetical@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-alphanumerical@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" @@ -2935,6 +3060,11 @@ is-date-object@^1.0.5, is-date-object@^1.1.0: call-bound "^1.0.2" has-tostringtag "^1.0.2" +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + is-decimal@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" @@ -2979,6 +3109,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== + is-hexadecimal@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" @@ -3250,6 +3385,14 @@ loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lowlight@^1.17.0: + version "1.20.0" + resolved "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888" + integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw== + dependencies: + fault "^1.0.0" + highlight.js "~10.7.0" + lowlight@^3.0.0: version "3.3.0" resolved "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz#007b8a5bfcfd27cc65b96246d2de3e9dd4e23c6c" @@ -3900,6 +4043,18 @@ micromatch@^4.0.4, micromatch@^4.0.8: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -4153,6 +4308,18 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-entities@^4.0.0: version "4.0.2" resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" @@ -4285,6 +4452,16 @@ prettier@^3.6.2: resolved "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393" integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== +prismjs@^1.27.0: + version "1.30.0" + resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9" + integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw== + +prismjs@~1.27.0: + version "1.27.0" + resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" + integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== + progress@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -4299,6 +4476,13 @@ prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +property-information@^5.0.0: + version "5.6.0" + resolved "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" + integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== + dependencies: + xtend "^4.0.0" + property-information@^6.0.0: version "6.5.0" resolved "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" @@ -4402,6 +4586,23 @@ react-is@^19.0.0: resolved "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz#038ebe313cf18e1fd1235d51c87360eb87f7c36a" integrity sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA== +react-markdown@^9.0.1: + version "9.1.0" + resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz#606bd74c6af131ba382a7c1282ff506708ed2e26" + integrity sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + hast-util-to-jsx-runtime "^2.0.0" + html-url-attributes "^3.0.0" + mdast-util-to-hast "^13.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + unified "^11.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + react-scroll@^1.9.3: version "1.9.3" resolved "https://registry.npmjs.org/react-scroll/-/react-scroll-1.9.3.tgz#8312831244a7a8f86036e72c71de155a454a78c0" @@ -4431,6 +4632,18 @@ react-stickynode@^5.0.2: shallowequal "^1.0.0" subscribe-ui-event "^3.0.0" +react-syntax-highlighter@^15.5.0: + version "15.6.1" + resolved "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz#fa567cb0a9f96be7bbccf2c13a3c4b5657d9543e" + integrity sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg== + dependencies: + "@babel/runtime" "^7.3.1" + highlight.js "^10.4.1" + highlightjs-vue "^1.0.0" + lowlight "^1.17.0" + prismjs "^1.27.0" + refractor "^3.6.0" + react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" @@ -4508,6 +4721,15 @@ reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: get-proto "^1.0.1" which-builtin-type "^1.2.1" +refractor@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a" + integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA== + dependencies: + hastscript "^6.0.0" + parse-entities "^2.0.0" + prismjs "~1.27.0" + regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: version "1.5.4" resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" @@ -4960,6 +5182,11 @@ source-map@~0.6.1: resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +space-separated-tokens@^1.0.0: + version "1.1.5" + resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" + integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== + space-separated-tokens@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" @@ -5598,6 +5825,11 @@ ws@^8.18.3: resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"