Compare commits

...

1 Commits

Author SHA1 Message Date
Gavan d9e07f89ba feat: 优化,新需求 2025-11-14 17:47:11 +08:00
24 changed files with 867 additions and 396 deletions

View File

@ -588,6 +588,7 @@ export type ChatConversationItem = {
export type ChatConversationPair = {
user: string;
assistant: string;
thinking_content: string;
created_at: string;
info: {
feedback_content: string;

File diff suppressed because one or more lines are too long

View File

@ -262,7 +262,7 @@ const MemberAdd = ({
MenuProps={{
sx: {
'.Mui-disabled': {
opacity: 1,
opacity: '1 !important',
color: 'text.disabled',
},
},

View File

@ -2,10 +2,10 @@ import { ChatConversationPair } from '@/api';
import { getApiV1ConversationDetail } from '@/request/Conversation';
import { DomainConversationDetailResp } from '@/request/types';
import Avatar from '@/components/Avatar';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import Card from '@/components/Card';
import MarkDown from '@/components/MarkDown';
import { useAppSelector } from '@/store';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import {
Accordion,
AccordionDetails,
@ -13,10 +13,169 @@ import {
Box,
Stack,
useTheme,
styled,
alpha,
Typography,
} from '@mui/material';
import { Ellipsis, Icon, Modal } from '@ctzhian/ui';
import { useEffect, useState } from 'react';
const handleThinkingContent = (content: string) => {
const thinkRegex = /<think>([\s\S]*?)(?:<\/think>|$)/g;
const thinkMatches = [];
let match;
while ((match = thinkRegex.exec(content)) !== null) {
thinkMatches.push(match[1]);
}
let answerContent = content.replace(/<think>[\s\S]*?<\/think>/g, '');
answerContent = answerContent.replace(/<think>[\s\S]*$/, '');
return {
thinkingContent: thinkMatches.join(''),
answerContent: answerContent,
};
};
export const StyledConversationItem = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
}));
// 聊天气泡相关组件
export const StyledUserBubble = styled(Box)(({ theme }) => ({
alignSelf: 'flex-end',
maxWidth: '75%',
padding: theme.spacing(1, 2),
borderRadius: '10px 10px 0px 10px',
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
fontSize: 14,
wordBreak: 'break-word',
}));
export const StyledAiBubble = styled(Box)(({ theme }) => ({
alignSelf: 'flex-start',
display: 'flex',
flexDirection: 'column',
width: '100%',
gap: theme.spacing(3),
}));
export const StyledAiBubbleContent = styled(Box)(() => ({
wordBreak: 'break-word',
}));
// 对话相关组件
export const StyledAccordion = styled(Accordion)(() => ({
padding: 0,
border: 'none',
'&:before': {
content: '""',
height: 0,
},
background: 'transparent',
backgroundImage: 'none',
}));
export const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1),
userSelect: 'text',
borderRadius: '10px',
backgroundColor: theme.palette.background.paper3,
border: '1px solid',
borderColor: theme.palette.divider,
}));
export const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
padding: theme.spacing(2),
borderTop: 'none',
}));
export const StyledQuestionText = styled(Box)(() => ({
fontWeight: '700',
fontSize: 16,
lineHeight: '24px',
wordBreak: 'break-all',
}));
// 搜索结果相关组件
export const StyledChunkAccordion = styled(Accordion)(({ theme }) => ({
backgroundImage: 'none',
background: 'transparent',
border: 'none',
padding: 0,
}));
export const StyledChunkAccordionSummary = styled(AccordionSummary)(
({ theme }) => ({
justifyContent: 'flex-start',
gap: theme.spacing(2),
'.MuiAccordionSummary-content': {
flexGrow: 0,
},
}),
);
export const StyledChunkAccordionDetails = styled(AccordionDetails)(
({ theme }) => ({
paddingTop: 0,
paddingLeft: theme.spacing(2),
borderTop: 'none',
borderLeft: '1px solid',
borderColor: theme.palette.divider,
}),
);
export const StyledChunkItem = styled(Box)(({ theme }) => ({
cursor: 'pointer',
'&:hover': {
'.hover-primary': {
color: theme.palette.primary.main,
},
},
}));
// 思考过程相关组件
export const StyledThinkingAccordion = styled(Accordion)(({ theme }) => ({
backgroundColor: 'transparent',
border: 'none',
padding: 0,
paddingBottom: theme.spacing(2),
'&:before': {
content: '""',
height: 0,
},
}));
export const StyledThinkingAccordionSummary = styled(AccordionSummary)(
({ theme }) => ({
justifyContent: 'flex-start',
gap: theme.spacing(2),
'.MuiAccordionSummary-content': {
flexGrow: 0,
},
}),
);
export const StyledThinkingAccordionDetails = styled(AccordionDetails)(
({ theme }) => ({
paddingTop: 0,
paddingLeft: theme.spacing(2),
borderTop: 'none',
borderLeft: '1px solid',
borderColor: theme.palette.divider,
'.markdown-body': {
opacity: 0.75,
fontSize: 12,
},
}),
);
const Detail = ({
id,
open,
@ -55,7 +214,11 @@ const Detail = ({
};
} else if (message.role === 'assistant') {
if (currentPair.user) {
currentPair.assistant = message.content;
const { thinkingContent, answerContent } = handleThinkingContent(
message.content || '',
);
currentPair.assistant = answerContent;
currentPair.thinking_content = thinkingContent;
currentPair.created_at = message.created_at;
// @ts-expect-error 类型不兼容
currentPair.info = message.info;
@ -167,26 +330,43 @@ const Detail = ({
<Stack gap={2}>
{conversations &&
conversations.map((item, index) => (
<Box key={index}>
<Accordion defaultExpanded={true}>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={{ fontSize: 24 }} />}
sx={{
userSelect: 'text',
backgroundColor: 'background.paper3',
fontSize: '18px',
fontWeight: 'bold',
}}
>
{item.user}
</AccordionSummary>
<AccordionDetails>
<MarkDown
content={item.assistant || '未查询到回答内容'}
/>
</AccordionDetails>
</Accordion>
</Box>
<StyledConversationItem key={index}>
{/* 用户问题气泡 - 右对齐 */}
<StyledUserBubble>{item.user}</StyledUserBubble>
{/* AI回答气泡 - 左对齐 */}
<StyledAiBubble>
{/* 思考过程 */}
{!!item.thinking_content && (
<StyledThinkingAccordion defaultExpanded>
<StyledThinkingAccordionSummary
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
>
<Stack direction='row' alignItems='center' gap={1}>
<Typography
variant='body2'
sx={theme => ({
fontSize: 12,
color: alpha(theme.palette.text.primary, 0.5),
})}
>
</Typography>
</Stack>
</StyledThinkingAccordionSummary>
<StyledThinkingAccordionDetails>
<MarkDown content={item.thinking_content || ''} />
</StyledThinkingAccordionDetails>
</StyledThinkingAccordion>
)}
{/* AI回答内容 */}
<StyledAiBubbleContent>
<MarkDown content={item.assistant} />
</StyledAiBubbleContent>
</StyledAiBubble>
</StyledConversationItem>
))}
</Stack>
</Box>

View File

@ -446,15 +446,19 @@ const Content = () => {
>
{ragReStartCount}
</Box>
<Button
size='small'
sx={{ minWidth: 0, p: 0, fontSize: 12 }}
<ButtonBase
disableRipple
sx={{
fontSize: 12,
fontWeight: 400,
color: 'primary.main',
}}
onClick={() => {
setRagOpen(true);
}}
>
</Button>
</ButtonBase>
</>
)}
</Stack>

View File

@ -8,9 +8,21 @@ import {
AccordionDetails,
AccordionSummary,
Box,
Stack,
Typography,
alpha,
} from '@mui/material';
import { Ellipsis, Icon, Modal } from '@ctzhian/ui';
import { Ellipsis, Modal } from '@ctzhian/ui';
import { useEffect, useState } from 'react';
import {
StyledConversationItem,
StyledUserBubble,
StyledAiBubble,
StyledThinkingAccordion,
StyledThinkingAccordionSummary,
StyledThinkingAccordionDetails,
StyledAiBubbleContent,
} from '../conversation/Detail';
const Detail = ({
id,
@ -36,6 +48,7 @@ const Detail = ({
user: data.question,
assistant: res.content!,
created_at: res.created_at!,
thinking_content: '',
});
});
}
@ -62,24 +75,43 @@ const Detail = ({
>
<Box sx={{ fontSize: 14 }}>
<Box>
<Accordion defaultExpanded={true}>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={{ fontSize: 24 }} />}
sx={{
userSelect: 'text',
backgroundColor: 'background.paper3',
fontSize: '18px',
fontWeight: 'bold',
}}
>
{conversations?.user}
</AccordionSummary>
<AccordionDetails>
<MarkDown
content={conversations?.assistant || '未查询到回答内容'}
/>
</AccordionDetails>
</Accordion>
<StyledConversationItem>
{/* 用户问题气泡 - 右对齐 */}
<StyledUserBubble>{conversations?.user}</StyledUserBubble>
{/* AI回答气泡 - 左对齐 */}
<StyledAiBubble>
{/* 思考过程 */}
{!!conversations?.thinking_content && (
<StyledThinkingAccordion defaultExpanded>
<StyledThinkingAccordionSummary
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
>
<Stack direction='row' alignItems='center' gap={1}>
<Typography
variant='body2'
sx={theme => ({
fontSize: 12,
color: alpha(theme.palette.text.primary, 0.5),
})}
>
</Typography>
</Stack>
</StyledThinkingAccordionSummary>
<StyledThinkingAccordionDetails>
<MarkDown content={conversations?.thinking_content || ''} />
</StyledThinkingAccordionDetails>
</StyledThinkingAccordion>
)}
{/* AI回答内容 */}
<StyledAiBubbleContent>
<MarkDown content={conversations?.assistant || ''} />
</StyledAiBubbleContent>
</StyledAiBubble>
</StyledConversationItem>
</Box>
</Box>
</Modal>

View File

@ -215,7 +215,7 @@ const AddRole = ({ open, onCancel, onOk, selectedIds }: AddRoleProps) => {
MenuProps={{
sx: {
'.Mui-disabled': {
opacity: 1,
opacity: '1 !important',
color: 'text.disabled',
},
},

View File

@ -873,7 +873,7 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
MenuProps={{
sx: {
'.Mui-disabled': {
opacity: 1,
opacity: '1 !important',
color: 'text.disabled',
},
},

View File

@ -1,4 +1,4 @@
FROM node:20-alpine
FROM node:22-alpine
ENV NODE_ENV=production

View File

@ -72,7 +72,7 @@ export default isDevelopment
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
// This can increase your server load as well as your hosting bill.
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
// Note: Check that the configured route will not match with your Next.js proxy, otherwise reporting of client-
// side errors will fail.
tunnelRoute: '/monitoring',

View File

@ -3,7 +3,7 @@
"version": "2.9.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack -p 3010",
"dev": "next dev -p 3010",
"build": "next build",
"start": "next start",
"lint": "next lint",
@ -16,7 +16,7 @@
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@emotion/cache": "^11.14.0",
"@mui/material-nextjs": "^7.1.0",
"@mui/material-nextjs": "^7.3.5",
"@sentry/nextjs": "^10.8.0",
"@types/markdown-it": "13.0.1",
"@vscode/markdown-it-katex": "^1.1.2",
@ -25,12 +25,13 @@
"highlight.js": "^11.11.1",
"html-react-parser": "^5.2.5",
"html-to-image": "^1.11.13",
"import-in-the-middle": "^1.4.0",
"js-cookie": "^3.0.5",
"katex": "^0.16.22",
"markdown-it": "13.0.1",
"markdown-it-highlightjs": "^4.2.0",
"mermaid": "^11.9.0",
"next": "15.4.6",
"next": "^16.0.0",
"react-device-detect": "^2.2.3",
"react-markdown": "^10.1.0",
"react-photo-view": "^1.2.7",
@ -41,17 +42,23 @@
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"require-in-the-middle": "^7.5.2",
"uuid": "^11.1.0"
},
"devDependencies": {
"@ctzhian/cx-swagger-api": "^1.0.0",
"@eslint/eslintrc": "^3",
"@next/eslint-plugin-next": "^15.4.5",
"@next/eslint-plugin-next": "^16.0.0",
"@types/js-cookie": "^3.0.6",
"@types/rangy": "^1.3.0",
"@types/react-syntax-highlighter": "^15.5.13",
"eslint-config-next": "15.3.2",
"eslint-config-next": "16.0.0",
"eslint-config-prettier": "^9.1.2"
},
"packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac"
"packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac",
"pnpm": {
"overrides": {
"require-in-the-middle": "^7.5.2"
}
}
}

View File

@ -4,7 +4,7 @@ import { ThemeStoreProvider } from '@/provider/themeStore';
import { getShareV1AppWebInfo } from '@/request/ShareApp';
import { getShareProV1AuthInfo } from '@/request/pro/ShareAuth';
import { Box } from '@mui/material';
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
import { AppRouterCacheProvider } from '@mui/material-nextjs/v16-appRouter';
import type { Metadata, Viewport } from 'next';
import localFont from 'next/font/local';
import { headers, cookies } from 'next/headers';

View File

@ -49,7 +49,7 @@
--color-primary-main: #6e73fe;
/* 代码块颜色 */
--code-bg: #ffffff;
--code-bg: rgba(0, 0, 0, 0.03);
--code-color: #21222d;
--inline-code-bg: #fff5f5;
--inline-code-color: #ff502c;

View File

@ -1,10 +1,13 @@
'use client';
import Logo from '@/assets/images/logo.png';
import { Box } from '@mui/material';
import { Stack, Box, IconButton, alpha, Tooltip } from '@mui/material';
import { postShareProV1AuthLogout } from '@/request/pro/ShareAuth';
import { IconDengchu } from '@panda-wiki/icons';
import { useStore } from '@/provider';
import { usePathname } from 'next/navigation';
import { useMemo } from 'react';
import { Modal } from '@ctzhian/ui';
import {
Header as CustomHeader,
WelcomeHeader as WelcomeHeaderComponent,
@ -16,8 +19,42 @@ interface HeaderProps {
isWelcomePage?: boolean;
}
const LogoutButton = () => {
const handleLogout = () => {
Modal.confirm({
title: '退出登录',
content: '确定要退出登录吗?',
onOk: () => {
postShareProV1AuthLogout().then(() => {
window.location.href = '/auth/login';
});
},
});
};
return (
<Tooltip title='退出登录' arrow>
<IconButton size='small' onClick={handleLogout}>
<IconDengchu
sx={theme => ({
cursor: 'pointer',
color: alpha(theme.palette.text.primary, 0.65),
fontSize: 24,
'&:hover': { color: theme.palette.primary.main },
})}
/>
</IconButton>
</Tooltip>
);
};
const Header = ({ isDocPage = false, isWelcomePage = false }: HeaderProps) => {
const { mobile = false, kbDetail, catalogWidth, setQaModalOpen } = useStore();
const {
mobile = false,
kbDetail,
catalogWidth,
setQaModalOpen,
authInfo,
} = useStore();
const pathname = usePathname();
const docWidth = useMemo(() => {
if (isWelcomePage) return 'full';
@ -55,16 +92,23 @@ const Header = ({ isDocPage = false, isWelcomePage = false }: HeaderProps) => {
onSearch={handleSearch}
onQaClick={() => setQaModalOpen?.(true)}
>
<Box sx={{ ml: 2 }}>
<Stack sx={{ ml: 2 }} direction='row' alignItems='center' gap={1}>
<ThemeSwitch />
</Box>
{!!authInfo && <LogoutButton />}
</Stack>
<QaModal />
</CustomHeader>
);
};
export const WelcomeHeader = () => {
const { mobile = false, kbDetail, catalogWidth, setQaModalOpen } = useStore();
const {
mobile = false,
kbDetail,
catalogWidth,
setQaModalOpen,
authInfo,
} = useStore();
const handleSearch = (value?: string, type: 'chat' | 'search' = 'chat') => {
if (value?.trim()) {
if (type === 'chat') {
@ -91,6 +135,7 @@ export const WelcomeHeader = () => {
onSearch={handleSearch}
onQaClick={() => setQaModalOpen?.(true)}
>
<Box sx={{ ml: 2 }}>{!!authInfo && <LogoutButton />}</Box>
<QaModal />
</WelcomeHeaderComponent>
);

View File

@ -4,12 +4,12 @@ import React, { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import { styled, SvgIcon, SvgIconProps } from '@mui/material';
// ==================== 图片数据缓存 ====================
// 全局图片 blob URL 缓存,避免重复请求 OSS
const imageBlobCache = new Map<string, string>();
// ==================== 图片数据缓存工具函数 ====================
// 下载图片并转换为 blob URL
const fetchImageAsBlob = async (src: string): Promise<string> => {
const fetchImageAsBlob = async (
src: string,
imageBlobCache: Map<string, string>,
): Promise<string> => {
// 检查缓存
if (imageBlobCache.has(src)) {
return imageBlobCache.get(src)!;
@ -39,12 +39,8 @@ const fetchImageAsBlob = async (src: string): Promise<string> => {
}
};
// 导出获取图片 blob URL 的函数
export const getImageBlobUrl = (src: string): string | null => {
return imageBlobCache.get(src) || null;
};
export const clearImageBlobCache = () => {
// 清理图片 blob 缓存
export const clearImageBlobCache = (imageBlobCache: Map<string, string>) => {
imageBlobCache.forEach(url => {
URL.revokeObjectURL(url);
});
@ -54,7 +50,7 @@ export const clearImageBlobCache = () => {
const StyledErrorContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
padding: theme.spacing(2),
padding: theme.spacing(1, 6),
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
@ -71,7 +67,7 @@ const StyledErrorContainer = styled('div')(({ theme }) => ({
const StyledErrorText = styled('div')(() => ({
fontSize: '12px',
marginBottom: 16,
marginBottom: 10,
}));
export const ImageErrorIcon = (props: SvgIconProps) => {
@ -102,7 +98,7 @@ export const ImageErrorIcon = (props: SvgIconProps) => {
const ImageErrorDisplay: React.FC = () => (
<StyledErrorContainer>
<ImageErrorIcon
sx={{ color: 'var(--mui-palette-text-tertiary)', fontSize: 160 }}
sx={{ color: 'var(--mui-palette-text-tertiary)', fontSize: 140 }}
/>
<StyledErrorText></StyledErrorText>
</StyledErrorContainer>
@ -116,7 +112,7 @@ interface ImageComponentProps {
imageIndex: number;
onLoad: (index: number, html: string) => void;
onError: (index: number, html: string) => void;
onImageClick: (src: string) => void;
imageBlobCache: Map<string, string>;
}
// ==================== 图片组件 ====================
@ -127,7 +123,7 @@ const ImageComponent: React.FC<ImageComponentProps> = ({
imageIndex,
onLoad,
onError,
onImageClick,
imageBlobCache,
}) => {
const [status, setStatus] = useState<'loading' | 'success' | 'error'>(
'loading',
@ -149,7 +145,7 @@ const ImageComponent: React.FC<ImageComponentProps> = ({
// 获取图片 blob URL
useEffect(() => {
let mounted = true;
fetchImageAsBlob(src)
fetchImageAsBlob(src, imageBlobCache)
.then(url => {
if (mounted) {
setBlobUrl(url);
@ -166,7 +162,7 @@ const ImageComponent: React.FC<ImageComponentProps> = ({
return () => {
mounted = false;
};
}, [src]);
}, [src, imageBlobCache]);
// 解析自定义样式
const parseStyleString = (styleStr: string) => {
@ -238,7 +234,8 @@ const ImageComponent: React.FC<ImageComponentProps> = ({
referrerPolicy='no-referrer'
onLoad={handleLoad}
onError={handleError}
onClick={() => onImageClick(src)} // 传递原始 src 用于预览
data-original-src={src}
className='markdown-image'
{...getOtherProps()}
/>
) : (
@ -264,12 +261,13 @@ const ImageComponent: React.FC<ImageComponentProps> = ({
export interface ImageRendererOptions {
onImageLoad: (index: number, html: string) => void;
onImageError: (index: number, html: string) => void;
onImageClick: (src: string) => void;
imageRenderCache: Map<number, string>;
imageBlobCache: Map<string, string>;
}
export const createImageRenderer = (options: ImageRendererOptions) => {
const { onImageLoad, onImageError, imageRenderCache, onImageClick } = options;
const { onImageLoad, onImageError, imageRenderCache, imageBlobCache } =
options;
return (
src: string,
alt: string,
@ -279,29 +277,6 @@ export const createImageRenderer = (options: ImageRendererOptions) => {
// 检查缓存
const cached = imageRenderCache.get(imageIndex);
if (cached) {
// 下一帧对已缓存的DOM绑定原生点击事件避免事件丢失且不引起重渲染
requestAnimationFrame(() => {
const container = document.querySelector(
`.image-container-${imageIndex}`,
) as HTMLElement | null;
if (!container) return;
const img = container.querySelector('img') as HTMLImageElement | null;
if (!img) return;
const alreadyBound = (img as HTMLElement).getAttribute(
'data-click-bound',
);
if (!alreadyBound) {
(img as HTMLElement).setAttribute('data-click-bound', '1');
img.style.cursor = img.style.cursor || 'pointer';
img.addEventListener('click', () => {
try {
onImageClick(img.src);
} catch {
// noop
}
});
}
});
return cached;
}
@ -323,7 +298,7 @@ export const createImageRenderer = (options: ImageRendererOptions) => {
imageIndex={imageIndex}
onLoad={onImageLoad}
onError={onImageError}
onImageClick={onImageClick}
imageBlobCache={imageBlobCache}
/>,
);
} else {

View File

@ -15,11 +15,7 @@ import React, {
useState,
} from 'react';
import { useSmartScroll } from '@/hooks';
import {
clearImageBlobCache,
createImageRenderer,
getImageBlobUrl,
} from './imageRenderer';
import { clearImageBlobCache, createImageRenderer } from './imageRenderer';
import { incrementalRender } from './incrementalRenderer';
import { createMermaidRenderer } from './mermaidRenderer';
import {
@ -88,7 +84,8 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
const lastContentRef = useRef<string>('');
const mdRef = useRef<MarkdownIt | null>(null);
const mermaidSuccessIdRef = useRef<Map<number, string>>(new Map());
const imageRenderCacheRef = useRef<Map<number, string>>(new Map()); // 图片渲染缓存
const imageRenderCacheRef = useRef<Map<number, string>>(new Map()); // 图片渲染缓存HTML
const imageBlobCacheRef = useRef<Map<string, string>>(new Map()); // 图片 blob URL 缓存
// 使用智能滚动 hook
const { scrollToBottom } = useSmartScroll({
@ -125,13 +122,8 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
createImageRenderer({
onImageLoad: handleImageLoad,
onImageError: handleImageError,
onImageClick: (src: string) => {
// 尝试获取缓存的 blob URL如果不存在则使用原始 src
const blobUrl = getImageBlobUrl(src);
setPreviewImgBlobUrl(blobUrl || src);
setPreviewOpen(true);
},
imageRenderCache: imageRenderCacheRef.current,
imageBlobCache: imageBlobCacheRef.current,
}),
[handleImageLoad, handleImageError],
);
@ -158,6 +150,7 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
const originalFenceRender = md.renderer.rules.fence;
// 自定义图片渲染
let imageCount = 0;
let htmlImageCount = 0; // HTML 标签图片计数
let mermaidCount = 0;
md.renderer.rules.image = (tokens, idx) => {
imageCount++;
@ -240,6 +233,38 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
);
};
// 解析 HTML img 标签并提取属性
const parseImgTag = (
html: string,
): {
src: string;
alt: string;
attrs: [string, string][];
} | null => {
// 匹配 <img> 标签(支持自闭合和普通标签)
const imgMatch = html.match(/<img\s+([^>]*?)\/?>/i);
if (!imgMatch) return null;
const attrsString = imgMatch[1];
const attrs: [string, string][] = [];
let src = '';
let alt = '';
// 解析属性:匹配 name="value" 或 name='value' 或 name=value
const attrRegex =
/(\w+)(?:=["']([^"']*)["']|=(?:["'])?([^\s>]+)(?:["'])?)?/g;
let attrMatch;
while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
const name = attrMatch[1].toLowerCase();
const value = attrMatch[2] || attrMatch[3] || '';
attrs.push([name, value]);
if (name === 'src') src = value;
if (name === 'alt') alt = value;
}
return { src, alt, attrs };
};
md.renderer.rules.html_block = (
tokens,
idx,
@ -278,6 +303,21 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
if (content.includes('<error>')) return '<span class="chat-error">';
if (content.includes('</error>')) return '</span>';
// 处理 img 标签
if (content.includes('<img')) {
const imgData = parseImgTag(content);
if (imgData && imgData.src) {
const imageIndex = imageCount + htmlImageCount;
htmlImageCount++;
return renderImage(
imgData.src,
imgData.alt,
imgData.attrs,
imageIndex,
);
}
}
// 🔒 安全检查:不在白名单的标签,转义输出
if (!isAllowedTag(content)) {
return md.utils.escapeHtml(content);
@ -301,6 +341,21 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
if (content.includes('<error>')) return '<span class="chat-error">';
if (content.includes('</error>')) return '</span>';
// 处理 img 标签
if (content.includes('<img')) {
const imgData = parseImgTag(content);
if (imgData && imgData.src) {
const imageIndex = imageCount + htmlImageCount;
htmlImageCount++;
return renderImage(
imgData.src,
imgData.alt,
imgData.attrs,
imageIndex,
);
}
}
// 🔒 安全检查:不在白名单的标签,转义输出
if (!isAllowedTag(content)) {
return md.utils.escapeHtml(content);
@ -352,7 +407,7 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
}
}, [content, customizeRenderer, scrollToBottom]);
// 添加代码块点击复制功能
// 添加代码块点击复制和图片点击预览功能(事件代理)
useEffect(() => {
const container = containerRef.current;
if (!container) return;
@ -360,6 +415,21 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
const handleClick = (e: MouseEvent) => {
const target = e.target as HTMLElement;
// 检查是否点击了图片
const imgElement = target.closest(
'img.markdown-image',
) as HTMLImageElement;
if (imgElement) {
const originalSrc = imgElement.getAttribute('data-original-src');
if (originalSrc) {
// 尝试获取缓存的 blob URL如果不存在则使用原始 src
const blobUrl = imageBlobCacheRef.current.get(originalSrc);
setPreviewImgBlobUrl(blobUrl || originalSrc);
setPreviewOpen(true);
}
return;
}
// 检查是否点击了代码块
const preElement = target.closest('pre.hljs');
if (preElement) {
@ -368,6 +438,7 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
const code = codeElement.textContent || '';
copyText(code.replace(/\n$/, ''));
}
return;
}
// 检查是否点击了行内代码
@ -380,7 +451,7 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
container.addEventListener('click', handleClick);
return () => {
clearImageBlobCache();
clearImageBlobCache(imageBlobCacheRef.current);
container.removeEventListener('click', handleClick);
};
}, []);
@ -406,6 +477,9 @@ const MarkDown2: React.FC<MarkDown2Props> = ({
position: 'relative',
display: 'inline-block',
},
'.markdown-image': {
cursor: 'pointer',
},
'.image-error': {
display: 'flex',
alignItems: 'center',

View File

@ -1,95 +0,0 @@
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { v4 as uuidv4 } from 'uuid';
import { getShareV1AppWidgetInfo } from './request/ShareApp';
import { middleware as homeMiddleware } from './middleware/home';
const proxyShare = async (request: NextRequest) => {
// 转发到 process.env.TARGET
const kb_id = request.headers.get('x-kb-id') || process.env.DEV_KB_ID || '';
const targetOrigin = process.env.TARGET!;
const targetUrl = new URL(
request.nextUrl.pathname + request.nextUrl.search,
targetOrigin,
);
// 构造 fetch 选项
const fetchHeaders = new Headers(request.headers);
fetchHeaders.set('x-kb-id', kb_id);
const fetchOptions: RequestInit = {
method: request.method,
headers: fetchHeaders,
body: ['GET', 'HEAD'].includes(request.method) ? undefined : request.body,
redirect: 'manual',
};
const proxyRes = await fetch(targetUrl.toString(), fetchOptions);
const nextRes = new NextResponse(proxyRes.body, {
status: proxyRes.status,
headers: proxyRes.headers,
statusText: proxyRes.statusText,
});
return nextRes;
};
export async function middleware(request: NextRequest) {
const url = request.nextUrl.clone();
const pathname = url.pathname;
if (pathname.startsWith('/widget')) {
const widgetInfo: any = await getShareV1AppWidgetInfo();
if (widgetInfo) {
if (!widgetInfo?.settings?.widget_bot_settings?.is_open) {
return NextResponse.rewrite(new URL('/not-fount', request.url));
}
}
return;
}
const headers: Record<string, string> = {};
for (const [key, value] of request.headers.entries()) {
headers[key] = value;
}
let sessionId = request.cookies.get('x-pw-session-id')?.value || '';
let needSetSessionId = false;
if (!sessionId) {
sessionId = uuidv4();
needSetSessionId = true;
}
let response: NextResponse;
if (pathname.startsWith('/share/')) {
response = await proxyShare(request);
} else {
response = await homeMiddleware(request, headers, sessionId);
}
if (needSetSessionId) {
response.cookies.set('x-pw-session-id', sessionId, {
httpOnly: true,
maxAge: 60 * 60 * 24 * 365, // 1 年
});
}
if (!pathname.startsWith('/share')) {
response.headers.set('x-current-path', pathname);
response.headers.set('x-current-search', url.search);
}
return response;
}
export const config = {
matcher: [
'/',
'/home',
'/share/:path*',
'/chat/:path*',
'/widget',
'/welcome',
'/auth/login',
'/node/:path*',
'/node',
// '/client/:path*',
],
};

View File

@ -1,87 +0,0 @@
import { parsePathname } from '@/utils';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { postShareV1StatPage } from '@/request/ShareStat';
import { getShareV1NodeList } from '@/request/ShareNode';
import { getShareV1AppWebInfo } from '@/request/ShareApp';
import { filterEmptyFolders, convertToTree } from '@/utils/drag';
import { deepSearchFirstNode } from '@/utils';
const StatPage = {
welcome: 1,
node: 2,
chat: 3,
auth: 4,
} as const;
const getFirstNode = async () => {
const nodeListResult: any = await getShareV1NodeList();
const tree = filterEmptyFolders(convertToTree(nodeListResult || []));
return deepSearchFirstNode(tree);
};
const getHomePath = async () => {
const info = await getShareV1AppWebInfo();
return info?.settings?.home_page_setting;
};
export async function middleware(
request: NextRequest,
headers: Record<string, string>,
session: string,
) {
const url = request.nextUrl.clone();
const { page, id } = parsePathname(url.pathname);
try {
// 获取节点列表
if (url.pathname === '/') {
const homePath = await getHomePath();
if (homePath === 'custom') {
return NextResponse.rewrite(new URL('/home', request.url));
} else {
const [firstNode] = await Promise.all([getFirstNode(), getHomePath()]);
if (firstNode) {
return NextResponse.rewrite(
new URL(`/node/${firstNode.id}`, request.url),
);
}
return NextResponse.rewrite(new URL('/node', request.url));
}
}
// 页面上报
const pages = Object.keys(StatPage);
if (pages.includes(page) || pages.includes(id)) {
postShareV1StatPage(
{
scene: StatPage[page as keyof typeof StatPage],
node_id: id || '',
},
{
headers: {
'x-pw-session-id': session,
...headers,
},
},
);
}
return NextResponse.next();
} catch (error) {
if (
typeof error === 'object' &&
error !== null &&
'message' in error &&
error.message === 'NEXT_REDIRECT'
) {
return NextResponse.redirect(
new URL(
`/auth/login?redirect=${encodeURIComponent(url.pathname + url.search)}`,
request.url,
),
);
}
}
return NextResponse.next();
}

181
web/app/src/proxy.ts Normal file
View File

@ -0,0 +1,181 @@
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { v4 as uuidv4 } from 'uuid';
import { getShareV1AppWidgetInfo } from './request/ShareApp';
import { parsePathname } from '@/utils';
import { postShareV1StatPage } from '@/request/ShareStat';
import { getShareV1NodeList } from '@/request/ShareNode';
import { getShareV1AppWebInfo } from '@/request/ShareApp';
import { filterEmptyFolders, convertToTree } from '@/utils/drag';
import { deepSearchFirstNode } from '@/utils';
const StatPage = {
welcome: 1,
node: 2,
chat: 3,
auth: 4,
} as const;
const getFirstNode = async () => {
const nodeListResult: any = await getShareV1NodeList();
const tree = filterEmptyFolders(convertToTree(nodeListResult || []));
return deepSearchFirstNode(tree);
};
const getHomePath = async () => {
const info = await getShareV1AppWebInfo();
return info?.settings?.home_page_setting;
};
const homeProxy = async (
request: NextRequest,
headers: Record<string, string>,
session: string,
) => {
const url = request.nextUrl.clone();
const { page, id } = parsePathname(url.pathname);
try {
// 获取节点列表
if (url.pathname === '/') {
const homePath = await getHomePath();
if (homePath === 'custom') {
return NextResponse.rewrite(new URL('/home', request.url));
} else {
const [firstNode] = await Promise.all([getFirstNode(), getHomePath()]);
if (firstNode) {
return NextResponse.rewrite(
new URL(`/node/${firstNode.id}`, request.url),
);
}
return NextResponse.rewrite(new URL('/node', request.url));
}
}
// 页面上报
const pages = Object.keys(StatPage);
if (pages.includes(page) || pages.includes(id)) {
postShareV1StatPage(
{
scene: StatPage[page as keyof typeof StatPage],
node_id: id || '',
},
{
headers: {
'x-pw-session-id': session,
...headers,
},
},
);
}
return NextResponse.next();
} catch (error) {
if (
typeof error === 'object' &&
error !== null &&
'message' in error &&
error.message === 'NEXT_REDIRECT'
) {
return NextResponse.redirect(
new URL(
`/auth/login?redirect=${encodeURIComponent(url.pathname + url.search)}`,
request.url,
),
);
}
}
return NextResponse.next();
};
const proxyShare = async (request: NextRequest) => {
// 转发到 process.env.TARGET
const kb_id = request.headers.get('x-kb-id') || process.env.DEV_KB_ID || '';
const targetOrigin = process.env.TARGET!;
const targetUrl = new URL(
request.nextUrl.pathname + request.nextUrl.search,
targetOrigin,
);
// 构造 fetch 选项
const fetchHeaders = new Headers(request.headers);
fetchHeaders.set('x-kb-id', kb_id);
const hasBody = !['GET', 'HEAD'].includes(request.method);
const fetchOptions: RequestInit = {
method: request.method,
headers: fetchHeaders,
body: hasBody ? request.body : undefined,
redirect: 'manual',
...(hasBody && { duplex: 'half' as const }),
};
const proxyRes = await fetch(targetUrl.toString(), fetchOptions);
const nextRes = new NextResponse(proxyRes.body, {
status: proxyRes.status,
headers: proxyRes.headers,
statusText: proxyRes.statusText,
});
return nextRes;
};
export async function proxy(request: NextRequest) {
const url = request.nextUrl.clone();
const pathname = url.pathname;
if (pathname.startsWith('/widget')) {
const widgetInfo: any = await getShareV1AppWidgetInfo();
if (widgetInfo) {
if (!widgetInfo?.settings?.widget_bot_settings?.is_open) {
return NextResponse.rewrite(new URL('/not-fount', request.url));
}
}
return;
}
const headers: Record<string, string> = {};
for (const [key, value] of request.headers.entries()) {
headers[key] = value;
}
let sessionId = request.cookies.get('x-pw-session-id')?.value || '';
let needSetSessionId = false;
if (!sessionId) {
sessionId = uuidv4();
needSetSessionId = true;
}
let response: NextResponse;
if (pathname.startsWith('/share/')) {
response = await proxyShare(request);
} else {
response = await homeProxy(request, headers, sessionId);
}
if (needSetSessionId) {
response.cookies.set('x-pw-session-id', sessionId, {
httpOnly: true,
maxAge: 60 * 60 * 24 * 365, // 1 年
});
}
if (!pathname.startsWith('/share')) {
response.headers.set('x-current-path', pathname);
response.headers.set('x-current-search', url.search);
}
return response;
}
export const config = {
matcher: [
'/',
'/home',
'/share/:path*',
'/chat/:path*',
'/widget',
'/welcome',
'/auth/login',
'/node/:path*',
'/node',
],
};

View File

@ -24,6 +24,7 @@ import {
GithubComChaitinPandaWikiProApiShareV1AuthInfoResp,
GithubComChaitinPandaWikiProApiShareV1AuthLDAPReq,
GithubComChaitinPandaWikiProApiShareV1AuthLDAPResp,
GithubComChaitinPandaWikiProApiShareV1AuthLogoutResp,
GithubComChaitinPandaWikiProApiShareV1AuthOAuthReq,
GithubComChaitinPandaWikiProApiShareV1AuthOAuthResp,
GithubComChaitinPandaWikiProApiShareV1AuthWecomReq,
@ -206,6 +207,32 @@ export const postShareProV1AuthLdap = (
...params,
});
/**
* @description
*
* @tags ShareAuth
* @name PostShareProV1AuthLogout
* @summary
* @request POST:/share/pro/v1/auth/logout
* @response `200` `(DomainPWResponse & {
data?: GithubComChaitinPandaWikiProApiShareV1AuthLogoutResp,
})` OK
*/
export const postShareProV1AuthLogout = (params: RequestParams = {}) =>
httpRequest<
DomainPWResponse & {
data?: GithubComChaitinPandaWikiProApiShareV1AuthLogoutResp;
}
>({
path: `/share/pro/v1/auth/logout`,
method: "POST",
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description OAuth登录
*

View File

@ -52,10 +52,12 @@ export enum ConstsSourceType {
export enum ConstsLicenseEdition {
/** 开源版 */
LicenseEditionFree = 0,
/** 联创版 */
LicenseEditionContributor = 1,
/** 专业版 */
LicenseEditionProfession = 1,
/** 企业版 */
LicenseEditionEnterprise = 2,
/** 商业版 */
LicenseEditionBusiness = 3,
}
export enum ConstsContributeType {
@ -455,6 +457,11 @@ export type GithubComChaitinPandaWikiProApiShareV1AuthLDAPResp = Record<
any
>;
export type GithubComChaitinPandaWikiProApiShareV1AuthLogoutResp = Record<
string,
any
>;
export interface GithubComChaitinPandaWikiProApiShareV1AuthOAuthReq {
kb_id?: string;
redirect_url?: string;
@ -669,8 +676,6 @@ export interface GetApiProV1TokenListParams {
}
export interface PostApiV1LicensePayload {
/** license edition */
license_edition: "contributor" | "enterprise";
/** license type */
license_type: "file" | "code";
/**

View File

@ -171,14 +171,21 @@ export enum ConstsNodeAccessPerm {
NodeAccessPermClosed = "closed",
}
export enum ConstsModelSettingMode {
ModelSettingModeManual = "manual",
ModelSettingModeAuto = "auto",
}
/** @format int32 */
export enum ConstsLicenseEdition {
/** 开源版 */
LicenseEditionFree = 0,
/** 联创版 */
LicenseEditionContributor = 1,
/** 专业版 */
LicenseEditionProfession = 1,
/** 企业版 */
LicenseEditionEnterprise = 2,
/** 商业版 */
LicenseEditionBusiness = 3,
}
export enum ConstsHomePageSetting {
@ -922,6 +929,17 @@ export interface DomainMetricsConfig {
type?: string;
}
export interface DomainModelModeSetting {
/** 百智云 API Key */
auto_mode_api_key?: string;
/** 自定义对话模型名称 */
chat_model?: string;
/** 手动模式下嵌入模型是否更新 */
is_manual_embedding_updated?: boolean;
/** 模式: manual 或 auto */
mode?: ConstsModelSettingMode;
}
export interface DomainMoveNodeReq {
id: string;
kb_id: string;
@ -1195,6 +1213,18 @@ export interface DomainStatPageReq {
scene: 1 | 2 | 3 | 4;
}
export interface DomainSwitchModeReq {
/** 百智云 API Key */
auto_mode_api_key?: string;
/** 自定义对话模型名称 */
chat_model?: string;
mode: "manual" | "auto";
}
export interface DomainSwitchModeResp {
message?: string;
}
export interface DomainTextConfig {
title?: string;
type?: string;
@ -1336,11 +1366,18 @@ export interface DomainWecomAIBotSettings {
}
export interface DomainWidgetBotSettings {
btn_id?: string;
btn_logo?: string;
btn_position?: string;
btn_style?: string;
btn_text?: string;
disclaimer?: string;
is_open?: boolean;
modal_position?: string;
placeholder?: string;
recommend_node_ids?: string[];
recommend_questions?: string[];
search_mode?: string;
theme_mode?: string;
}

View File

@ -117,7 +117,7 @@ const DocContent = ({
setCommentImages([]);
message.success(
appDetail?.web_app_comment_settings?.moderation_enable
? '正在审核中...'
? '评论已提交,请耐心等待审核'
: '评论成功',
);
} catch (error: any) {

View File

@ -236,11 +236,11 @@ importers:
specifier: ^11.14.0
version: 11.14.0
'@mui/material-nextjs':
specifier: ^7.1.0
version: 7.3.3(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(next@15.4.6(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)
specifier: ^7.3.5
version: 7.3.5(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(next@16.0.3(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)
'@sentry/nextjs':
specifier: ^10.8.0
version: 10.22.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(next@15.4.6(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(webpack@5.102.1)
version: 10.22.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(next@16.0.3(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(webpack@5.102.1)
'@types/markdown-it':
specifier: 13.0.1
version: 13.0.1
@ -262,6 +262,9 @@ importers:
html-to-image:
specifier: ^1.11.13
version: 1.11.13
import-in-the-middle:
specifier: ^1.4.0
version: 1.15.0
js-cookie:
specifier: ^3.0.5
version: 3.0.5
@ -278,8 +281,8 @@ importers:
specifier: ^11.9.0
version: 11.12.1
next:
specifier: 15.4.6
version: 15.4.6(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
specifier: ^16.0.0
version: 16.0.3(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-device-detect:
specifier: ^2.2.3
version: 2.2.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@ -310,6 +313,9 @@ importers:
remark-math:
specifier: ^6.0.0
version: 6.0.0
require-in-the-middle:
specifier: ^7.5.2
version: 7.5.2
uuid:
specifier: ^11.1.0
version: 11.1.0
@ -321,8 +327,8 @@ importers:
specifier: ^3
version: 3.3.1
'@next/eslint-plugin-next':
specifier: ^15.4.5
version: 15.5.6
specifier: ^16.0.0
version: 16.0.3
'@types/js-cookie':
specifier: ^3.0.6
version: 3.0.6
@ -333,8 +339,8 @@ importers:
specifier: ^15.5.13
version: 15.5.13
eslint-config-next:
specifier: 15.3.2
version: 15.3.2(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
specifier: 16.0.0
version: 16.0.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
eslint-config-prettier:
specifier: ^9.1.2
version: 9.1.2(eslint@9.39.1(jiti@2.6.1))
@ -915,78 +921,92 @@ packages:
resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.2.3':
resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.2.3':
resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.2.3':
resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.2.3':
resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.2.3':
resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.2.3':
resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.34.4':
resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.4':
resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.34.4':
resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.4':
resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.4':
resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.34.4':
resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.4':
resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.34.4':
resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==}
@ -1047,15 +1067,15 @@ packages:
'@types/react':
optional: true
'@mui/material-nextjs@7.3.3':
resolution: {integrity: sha512-SHqEh/GIa/HkHzBMMGvTRCVSLvXYTATZ7QN7/QxngimW5kBDI+pWzjieS8HmLDgo36rxD8g+GcLY6Bu+pilciw==}
'@mui/material-nextjs@7.3.5':
resolution: {integrity: sha512-WWd8dbygIOnhqTlLiDP7lvKFZyIDTrZ0mClFxmYOYzohxJuYzeuax/yAgOW2lEGxIdoSDiSPqv/4qT8LZ96IVA==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/cache': ^11.11.0
'@emotion/react': ^11.11.4
'@emotion/server': ^11.11.0
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
next: ^13.0.0 || ^14.0.0 || ^15.0.0
next: ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/cache':
@ -1145,59 +1165,63 @@ packages:
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
'@next/env@15.4.6':
resolution: {integrity: sha512-yHDKVTcHrZy/8TWhj0B23ylKv5ypocuCwey9ZqPyv4rPdUdRzpGCkSi03t04KBPyU96kxVtUqx6O3nE1kpxASQ==}
'@next/env@16.0.3':
resolution: {integrity: sha512-IqgtY5Vwsm14mm/nmQaRMmywCU+yyMIYfk3/MHZ2ZTJvwVbBn3usZnjMi1GacrMVzVcAxJShTCpZlPs26EdEjQ==}
'@next/eslint-plugin-next@15.3.2':
resolution: {integrity: sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ==}
'@next/eslint-plugin-next@16.0.0':
resolution: {integrity: sha512-IB7RzmmtrPOrpAgEBR1PIQPD0yea5lggh5cq54m51jHjjljU80Ia+czfxJYMlSDl1DPvpzb8S9TalCc0VMo9Hw==}
'@next/eslint-plugin-next@15.5.6':
resolution: {integrity: sha512-YxDvsT2fwy1j5gMqk3ppXlsgDopHnkM4BoxSVASbvvgh5zgsK8lvWerDzPip8k3WVzsTZ1O7A7si1KNfN4OZfQ==}
'@next/eslint-plugin-next@16.0.3':
resolution: {integrity: sha512-6sPWmZetzFWMsz7Dhuxsdmbu3fK+/AxKRtj7OB0/3OZAI2MHB/v2FeYh271LZ9abvnM1WIwWc/5umYjx0jo5sQ==}
'@next/swc-darwin-arm64@15.4.6':
resolution: {integrity: sha512-667R0RTP4DwxzmrqTs4Lr5dcEda9OxuZsVFsjVtxVMVhzSpo6nLclXejJVfQo2/g7/Z9qF3ETDmN3h65mTjpTQ==}
'@next/swc-darwin-arm64@16.0.3':
resolution: {integrity: sha512-MOnbd92+OByu0p6QBAzq1ahVWzF6nyfiH07dQDez4/Nku7G249NjxDVyEfVhz8WkLiOEU+KFVnqtgcsfP2nLXg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@next/swc-darwin-x64@15.4.6':
resolution: {integrity: sha512-KMSFoistFkaiQYVQQnaU9MPWtp/3m0kn2Xed1Ces5ll+ag1+rlac20sxG+MqhH2qYWX1O2GFOATQXEyxKiIscg==}
'@next/swc-darwin-x64@16.0.3':
resolution: {integrity: sha512-i70C4O1VmbTivYdRlk+5lj9xRc2BlK3oUikt3yJeHT1unL4LsNtN7UiOhVanFdc7vDAgZn1tV/9mQwMkWOJvHg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@next/swc-linux-arm64-gnu@15.4.6':
resolution: {integrity: sha512-PnOx1YdO0W7m/HWFeYd2A6JtBO8O8Eb9h6nfJia2Dw1sRHoHpNf6lN1U4GKFRzRDBi9Nq2GrHk9PF3Vmwf7XVw==}
'@next/swc-linux-arm64-gnu@16.0.3':
resolution: {integrity: sha512-O88gCZ95sScwD00mn/AtalyCoykhhlokxH/wi1huFK+rmiP5LAYVs/i2ruk7xST6SuXN4NI5y4Xf5vepb2jf6A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@next/swc-linux-arm64-musl@15.4.6':
resolution: {integrity: sha512-XBbuQddtY1p5FGPc2naMO0kqs4YYtLYK/8aPausI5lyOjr4J77KTG9mtlU4P3NwkLI1+OjsPzKVvSJdMs3cFaw==}
'@next/swc-linux-arm64-musl@16.0.3':
resolution: {integrity: sha512-CEErFt78S/zYXzFIiv18iQCbRbLgBluS8z1TNDQoyPi8/Jr5qhR3e8XHAIxVxPBjDbEMITprqELVc5KTfFj0gg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@next/swc-linux-x64-gnu@15.4.6':
resolution: {integrity: sha512-+WTeK7Qdw82ez3U9JgD+igBAP75gqZ1vbK6R8PlEEuY0OIe5FuYXA4aTjL811kWPf7hNeslD4hHK2WoM9W0IgA==}
'@next/swc-linux-x64-gnu@16.0.3':
resolution: {integrity: sha512-Tc3i+nwt6mQ+Dwzcri/WNDj56iWdycGVh5YwwklleClzPzz7UpfaMw1ci7bLl6GRYMXhWDBfe707EXNjKtiswQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@next/swc-linux-x64-musl@15.4.6':
resolution: {integrity: sha512-XP824mCbgQsK20jlXKrUpZoh/iO3vUWhMpxCz8oYeagoiZ4V0TQiKy0ASji1KK6IAe3DYGfj5RfKP6+L2020OQ==}
'@next/swc-linux-x64-musl@16.0.3':
resolution: {integrity: sha512-zTh03Z/5PBBPdTurgEtr6nY0vI9KR9Ifp/jZCcHlODzwVOEKcKRBtQIGrkc7izFgOMuXDEJBmirwpGqdM/ZixA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@next/swc-win32-arm64-msvc@15.4.6':
resolution: {integrity: sha512-FxrsenhUz0LbgRkNWx6FRRJIPe/MI1JRA4W4EPd5leXO00AZ6YU8v5vfx4MDXTvN77lM/EqsE3+6d2CIeF5NYg==}
'@next/swc-win32-arm64-msvc@16.0.3':
resolution: {integrity: sha512-Jc1EHxtZovcJcg5zU43X3tuqzl/sS+CmLgjRP28ZT4vk869Ncm2NoF8qSTaL99gh6uOzgM99Shct06pSO6kA6g==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@next/swc-win32-x64-msvc@15.4.6':
resolution: {integrity: sha512-T4ufqnZ4u88ZheczkBTtOF+eKaM14V8kbjud/XrAakoM5DKQWjW09vD6B9fsdsWS2T7D5EY31hRHdta7QKWOng==}
'@next/swc-win32-x64-msvc@16.0.3':
resolution: {integrity: sha512-N7EJ6zbxgIYpI/sWNzpVKRMbfEGgsWuOIvzkML7wxAAZhPk1Msxuo/JDu1PKjWGrAoOLaZcIX5s+/pF5LIbBBg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@ -1495,56 +1519,67 @@ packages:
resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.52.5':
resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.52.5':
resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.52.5':
resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.52.5':
resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-gnu@4.52.5':
resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.52.5':
resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.52.5':
resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.52.5':
resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.52.5':
resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.52.5':
resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openharmony-arm64@4.52.5':
resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==}
@ -1574,9 +1609,6 @@ packages:
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
'@rushstack/eslint-patch@1.14.1':
resolution: {integrity: sha512-jGTk8UD/RdjsNZW8qq10r0RBvxL8OWtoT+kImlzPDFilmozzM+9QmIJsmze9UiSBrFU45ZxhTYBypn9q9z/VfQ==}
'@sentry-internal/browser-utils@10.22.0':
resolution: {integrity: sha512-BpJoLZEyJr7ORzkCrIjxRTnFWwO1mJNICVh3B9g5d9245niGT4OJvRozmLz89WgJkZFHWu84ls6Xfq5b/3tGFQ==}
engines: {node: '>=18'}
@ -2378,41 +2410,49 @@ packages:
resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
cpu: [x64]
os: [linux]
libc: [musl]
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
@ -3297,10 +3337,10 @@ packages:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
eslint-config-next@15.3.2:
resolution: {integrity: sha512-FerU4DYccO4FgeYFFglz0SnaKRe1ejXQrDb8kWUkTAg036YWi+jUsgg4sIGNCDhAsDITsZaL4MzBWKB6f4G1Dg==}
eslint-config-next@16.0.0:
resolution: {integrity: sha512-DWKT1YAO9ex2rK0/EeiPpKU++ghTiG59z6m08/ReLRECOYIaEv17maSCYT8zmFQLwIrY5lhJ+iaJPQdT4sJd4g==}
peerDependencies:
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
eslint: '>=9.0.0'
typescript: '>=3.3.1'
peerDependenciesMeta:
typescript:
@ -3371,6 +3411,12 @@ packages:
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
eslint-plugin-react-hooks@7.0.1:
resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==}
engines: {node: '>=18'}
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
eslint-plugin-react-refresh@0.4.24:
resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==}
peerDependencies:
@ -3623,6 +3669,10 @@ packages:
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
engines: {node: '>=18'}
globals@16.4.0:
resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==}
engines: {node: '>=18'}
globalthis@1.0.4:
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
engines: {node: '>= 0.4'}
@ -3719,6 +3769,12 @@ packages:
hastscript@9.0.1:
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
hermes-estree@0.25.1:
resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
hermes-parser@0.25.1:
resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
highlight.js@10.7.3:
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
@ -4454,9 +4510,9 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
next@15.4.6:
resolution: {integrity: sha512-us++E/Q80/8+UekzB3SAGs71AlLDsadpFMXVNM/uQ0BMwsh9m3mr0UNQIfjKed8vpWXsASe+Qifrnu1oLIcKEQ==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
next@16.0.3:
resolution: {integrity: sha512-Ka0/iNBblPFcIubTA1Jjh6gvwqfjrGq1Y2MTI5lbjeLIAfmC+p5bQmojpRZqgHHVu5cG4+qdIiwXiBSm/8lZ3w==}
engines: {node: '>=20.9.0'}
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.1.0
@ -5781,6 +5837,15 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
zod-validation-error@4.0.2:
resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
zod: ^3.25.0 || ^4.0.0
zod@4.1.12:
resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==}
zrender@5.6.1:
resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
@ -6519,11 +6584,11 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.2
'@mui/material-nextjs@7.3.3(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(next@15.4.6(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)':
'@mui/material-nextjs@7.3.5(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(next@16.0.3(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0)
next: 15.4.6(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next: 16.0.3(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react: 19.2.0
optionalDependencies:
'@emotion/cache': 11.14.0
@ -6613,38 +6678,38 @@ snapshots:
'@tybys/wasm-util': 0.10.1
optional: true
'@next/env@15.4.6': {}
'@next/env@16.0.3': {}
'@next/eslint-plugin-next@15.3.2':
'@next/eslint-plugin-next@16.0.0':
dependencies:
fast-glob: 3.3.1
'@next/eslint-plugin-next@15.5.6':
'@next/eslint-plugin-next@16.0.3':
dependencies:
fast-glob: 3.3.1
'@next/swc-darwin-arm64@15.4.6':
'@next/swc-darwin-arm64@16.0.3':
optional: true
'@next/swc-darwin-x64@15.4.6':
'@next/swc-darwin-x64@16.0.3':
optional: true
'@next/swc-linux-arm64-gnu@15.4.6':
'@next/swc-linux-arm64-gnu@16.0.3':
optional: true
'@next/swc-linux-arm64-musl@15.4.6':
'@next/swc-linux-arm64-musl@16.0.3':
optional: true
'@next/swc-linux-x64-gnu@15.4.6':
'@next/swc-linux-x64-gnu@16.0.3':
optional: true
'@next/swc-linux-x64-musl@15.4.6':
'@next/swc-linux-x64-musl@16.0.3':
optional: true
'@next/swc-win32-arm64-msvc@15.4.6':
'@next/swc-win32-arm64-msvc@16.0.3':
optional: true
'@next/swc-win32-x64-msvc@15.4.6':
'@next/swc-win32-x64-msvc@16.0.3':
optional: true
'@nodelib/fs.scandir@2.1.5':
@ -7031,8 +7096,6 @@ snapshots:
'@rtsao/scc@1.1.0': {}
'@rushstack/eslint-patch@1.14.1': {}
'@sentry-internal/browser-utils@10.22.0':
dependencies:
'@sentry/core': 10.22.0
@ -7121,7 +7184,7 @@ snapshots:
'@sentry/core@10.22.0': {}
'@sentry/nextjs@10.22.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(next@15.4.6(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(webpack@5.102.1)':
'@sentry/nextjs@10.22.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(next@16.0.3(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(webpack@5.102.1)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.37.0
@ -7134,7 +7197,7 @@ snapshots:
'@sentry/react': 10.22.0(react@19.2.0)
'@sentry/vercel-edge': 10.22.0
'@sentry/webpack-plugin': 4.6.0(webpack@5.102.1)
next: 15.4.6(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next: 16.0.3(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
resolve: 1.22.8
rollup: 4.52.5
stacktrace-parser: 0.1.11
@ -9047,22 +9110,22 @@ snapshots:
escape-string-regexp@5.0.0: {}
eslint-config-next@15.3.2(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3):
eslint-config-next@16.0.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3):
dependencies:
'@next/eslint-plugin-next': 15.3.2
'@rushstack/eslint-patch': 1.14.1
'@typescript-eslint/eslint-plugin': 8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
'@next/eslint-plugin-next': 16.0.0
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react-hooks: 7.0.1(eslint@9.39.1(jiti@2.6.1))
globals: 16.4.0
typescript-eslint: 8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-webpack
- eslint-plugin-import-x
- supports-color
@ -9090,22 +9153,21 @@ snapshots:
tinyglobby: 0.2.15
unrs-resolver: 1.11.1
optionalDependencies:
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
eslint-plugin-import@2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@ -9116,7 +9178,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@ -9127,8 +9189,6 @@ snapshots:
semver: 6.3.1
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
'@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
@ -9157,6 +9217,17 @@ snapshots:
dependencies:
eslint: 9.39.1(jiti@2.6.1)
eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@babel/core': 7.28.5
'@babel/parser': 7.28.5
eslint: 9.39.1(jiti@2.6.1)
hermes-parser: 0.25.1
zod: 4.1.12
zod-validation-error: 4.0.2(zod@4.1.12)
transitivePeerDependencies:
- supports-color
eslint-plugin-react-refresh@0.4.24(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@ -9444,6 +9515,8 @@ snapshots:
globals@15.15.0: {}
globals@16.4.0: {}
globalthis@1.0.4:
dependencies:
define-properties: 1.2.1
@ -9609,6 +9682,12 @@ snapshots:
property-information: 7.1.0
space-separated-tokens: 2.0.2
hermes-estree@0.25.1: {}
hermes-parser@0.25.1:
dependencies:
hermes-estree: 0.25.1
highlight.js@10.7.3: {}
highlight.js@11.11.1: {}
@ -10575,9 +10654,9 @@ snapshots:
neo-async@2.6.2: {}
next@15.4.6(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
next@16.0.3(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
'@next/env': 15.4.6
'@next/env': 16.0.3
'@swc/helpers': 0.5.15
caniuse-lite: 1.0.30001753
postcss: 8.4.31
@ -10585,14 +10664,14 @@ snapshots:
react-dom: 19.2.0(react@19.2.0)
styled-jsx: 5.1.6(react@19.2.0)
optionalDependencies:
'@next/swc-darwin-arm64': 15.4.6
'@next/swc-darwin-x64': 15.4.6
'@next/swc-linux-arm64-gnu': 15.4.6
'@next/swc-linux-arm64-musl': 15.4.6
'@next/swc-linux-x64-gnu': 15.4.6
'@next/swc-linux-x64-musl': 15.4.6
'@next/swc-win32-arm64-msvc': 15.4.6
'@next/swc-win32-x64-msvc': 15.4.6
'@next/swc-darwin-arm64': 16.0.3
'@next/swc-darwin-x64': 16.0.3
'@next/swc-linux-arm64-gnu': 16.0.3
'@next/swc-linux-arm64-musl': 16.0.3
'@next/swc-linux-x64-gnu': 16.0.3
'@next/swc-linux-x64-musl': 16.0.3
'@next/swc-win32-arm64-msvc': 16.0.3
'@next/swc-win32-x64-msvc': 16.0.3
'@opentelemetry/api': 1.9.0
sharp: 0.34.4
transitivePeerDependencies:
@ -12184,6 +12263,12 @@ snapshots:
yocto-queue@0.1.0: {}
zod-validation-error@4.0.2(zod@4.1.12):
dependencies:
zod: 4.1.12
zod@4.1.12: {}
zrender@5.6.1:
dependencies:
tslib: 2.3.0