mirror of https://github.com/chaitin/PandaWiki.git
Compare commits
1 Commits
b7cdec0d4a
...
e1b7902ba2
| Author | SHA1 | Date |
|---|---|---|
|
|
e1b7902ba2 |
|
|
@ -58,8 +58,8 @@ type NodePermissionEditResp struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeRestudyReq struct {
|
type NodeRestudyReq struct {
|
||||||
NodeIds []string `json:"node_ids" validate:"required,min=1"`
|
NodeIds []string `json:"node_ids"`
|
||||||
KbId string `json:"kb_id" validate:"required"`
|
KbId string `json:"kb_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeRestudyResp struct {
|
type NodeRestudyResp struct {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
/// <reference types="@panda-wiki/themes/types" />
|
|
||||||
|
|
||||||
declare module 'swiper/css' {
|
declare module 'swiper/css' {
|
||||||
const content: string;
|
const content: string;
|
||||||
export default content;
|
export default content;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,27 @@
|
||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Vite + React 特有配置 */
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
},
|
},
|
||||||
|
"incremental": true,
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Vite 配置文件特有 */
|
"incremental": true,
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"lib": ["ES2023"],
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
/// <reference types="@panda-wiki/themes/types" />
|
|
||||||
|
|
||||||
declare module '@cap.js/widget' {
|
declare module '@cap.js/widget' {
|
||||||
interface CapOptions {
|
interface CapOptions {
|
||||||
apiEndpoint: string;
|
apiEndpoint: string;
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ const Layout = async ({
|
||||||
|
|
||||||
const [kbDetailResolve, authInfoResolve] = await Promise.allSettled([
|
const [kbDetailResolve, authInfoResolve] = await Promise.allSettled([
|
||||||
getShareV1AppWebInfo(),
|
getShareV1AppWebInfo(),
|
||||||
|
// @ts-ignore
|
||||||
getShareProV1AuthInfo({}),
|
getShareProV1AuthInfo({}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { useEffect, useRef, useState } from 'react';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { ChunkResultItem } from '@/assets/type';
|
import { ChunkResultItem } from '@/assets/type';
|
||||||
import Logo from '@/assets/images/logo.png';
|
|
||||||
import aiLoading from '@/assets/images/ai-loading.gif';
|
import aiLoading from '@/assets/images/ai-loading.gif';
|
||||||
import { getShareV1ConversationDetail } from '@/request/ShareConversation';
|
import { getShareV1ConversationDetail } from '@/request/ShareConversation';
|
||||||
import { message } from '@ctzhian/ui';
|
import { message } from '@ctzhian/ui';
|
||||||
|
|
@ -26,33 +25,21 @@ import MarkDown2 from '@/components/markdown2';
|
||||||
import { postShareV1ChatFeedback } from '@/request/ShareChat';
|
import { postShareV1ChatFeedback } from '@/request/ShareChat';
|
||||||
import { copyText } from '@/utils';
|
import { copyText } from '@/utils';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import {
|
import { Box, IconButton, Stack, Typography, Tooltip } from '@mui/material';
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
Stack,
|
|
||||||
Typography,
|
|
||||||
Tooltip,
|
|
||||||
alpha,
|
|
||||||
} from '@mui/material';
|
|
||||||
import 'dayjs/locale/zh-cn';
|
import 'dayjs/locale/zh-cn';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import ChatLoading from '../../views/chat/ChatLoading';
|
import ChatLoading from '../../views/chat/ChatLoading';
|
||||||
import {
|
import { IconTupian, IconFasong } from '@panda-wiki/icons';
|
||||||
IconTupian,
|
|
||||||
IconFasong,
|
|
||||||
IconXingxing,
|
|
||||||
IconXinduihua,
|
|
||||||
} from '@panda-wiki/icons';
|
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import {
|
import {
|
||||||
StyledMainContainer,
|
StyledMainContainer,
|
||||||
StyledConversationContainer,
|
StyledConversationContainer,
|
||||||
StyledConversationItem,
|
StyledConversationItem,
|
||||||
StyledUserBubble,
|
StyledAccordion,
|
||||||
StyledAiBubble,
|
StyledAccordionSummary,
|
||||||
StyledAiBubbleContent,
|
StyledAccordionDetails,
|
||||||
|
StyledQuestionText,
|
||||||
StyledChunkAccordion,
|
StyledChunkAccordion,
|
||||||
StyledChunkAccordionSummary,
|
StyledChunkAccordionSummary,
|
||||||
StyledChunkAccordionDetails,
|
StyledChunkAccordionDetails,
|
||||||
|
|
@ -70,9 +57,8 @@ import {
|
||||||
StyledActionButtonStack,
|
StyledActionButtonStack,
|
||||||
StyledFuzzySuggestionsStack,
|
StyledFuzzySuggestionsStack,
|
||||||
StyledFuzzySuggestionItem,
|
StyledFuzzySuggestionItem,
|
||||||
StyledHotSearchContainer,
|
StyledHotSearchStack,
|
||||||
StyledHotSearchColumn,
|
StyledHotSearchItem,
|
||||||
StyledHotSearchColumnItem,
|
|
||||||
} from './StyledComponents';
|
} from './StyledComponents';
|
||||||
|
|
||||||
export interface ConversationItem {
|
export interface ConversationItem {
|
||||||
|
|
@ -105,13 +91,7 @@ const LoadingContent = ({
|
||||||
return (
|
return (
|
||||||
<Stack direction='row' alignItems='center' gap={1} sx={{ pb: 1 }}>
|
<Stack direction='row' alignItems='center' gap={1} sx={{ pb: 1 }}>
|
||||||
<Image src={aiLoading} alt='ai-loading' width={20} height={20} />
|
<Image src={aiLoading} alt='ai-loading' width={20} height={20} />
|
||||||
<Typography
|
<Typography variant='body2' sx={{ fontSize: 12, color: 'text.tertiary' }}>
|
||||||
variant='body2'
|
|
||||||
sx={theme => ({
|
|
||||||
fontSize: 12,
|
|
||||||
color: alpha(theme.palette.text.primary, 0.5),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{AnswerStatus[thinking]}
|
{AnswerStatus[thinking]}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
@ -161,10 +141,6 @@ const AiQaContent: React.FC<{
|
||||||
});
|
});
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
if (loading) {
|
|
||||||
handleSearchAbort();
|
|
||||||
}
|
|
||||||
handleSearch(true);
|
|
||||||
setConversationId('');
|
setConversationId('');
|
||||||
setConversation([]);
|
setConversation([]);
|
||||||
setFullAnswer('');
|
setFullAnswer('');
|
||||||
|
|
@ -180,9 +156,9 @@ const AiQaContent: React.FC<{
|
||||||
setNonce('');
|
setNonce('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = (reset: boolean = false) => {
|
const handleSearch = () => {
|
||||||
if (input.length > 0) {
|
if (input.length > 0) {
|
||||||
onSearch(input, reset);
|
onSearch(input);
|
||||||
setInput('');
|
setInput('');
|
||||||
// 清理图片URL
|
// 清理图片URL
|
||||||
uploadedImages.forEach(img => {
|
uploadedImages.forEach(img => {
|
||||||
|
|
@ -687,296 +663,156 @@ const AiQaContent: React.FC<{
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledMainContainer className={palette.mode === 'dark' ? 'md-dark' : ''}>
|
<StyledMainContainer className={palette.mode === 'dark' ? 'md-dark' : ''}>
|
||||||
{/* 无对话时显示欢迎界面 */}
|
|
||||||
{conversation.length === 0 && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
flex: 1,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
gap: 4,
|
|
||||||
pb: 5,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Logo区域 */}
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, my: 8 }}>
|
|
||||||
<Image
|
|
||||||
src={kbDetail?.settings?.icon || Logo.src}
|
|
||||||
alt='logo'
|
|
||||||
width={46}
|
|
||||||
height={46}
|
|
||||||
unoptimized
|
|
||||||
style={{
|
|
||||||
objectFit: 'contain',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Typography
|
|
||||||
variant='h6'
|
|
||||||
sx={{ fontSize: 32, color: 'text.primary', fontWeight: 700 }}
|
|
||||||
>
|
|
||||||
{kbDetail?.settings?.title}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 热门搜索区域 */}
|
|
||||||
{hotSearch.length > 0 && (
|
|
||||||
<Box sx={{ width: '100%' }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
mb: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 500,
|
|
||||||
color: 'primary.main',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 0.5,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconXingxing sx={{ fontSize: 14 }} />
|
|
||||||
大家都在搜什么?
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 热门搜索列表 - 两列布局 */}
|
|
||||||
<StyledHotSearchContainer>
|
|
||||||
{/* 左列 */}
|
|
||||||
<StyledHotSearchColumn>
|
|
||||||
{hotSearch
|
|
||||||
.filter((_, index) => index % 2 === 0)
|
|
||||||
.map((suggestion, index) => (
|
|
||||||
<StyledHotSearchColumnItem
|
|
||||||
key={index * 2}
|
|
||||||
onClick={() => onSuggestionClick(suggestion)}
|
|
||||||
>
|
|
||||||
• {suggestion}
|
|
||||||
</StyledHotSearchColumnItem>
|
|
||||||
))}
|
|
||||||
</StyledHotSearchColumn>
|
|
||||||
|
|
||||||
{/* 右列 */}
|
|
||||||
<StyledHotSearchColumn>
|
|
||||||
{hotSearch
|
|
||||||
.filter((_, index) => index % 2 === 1)
|
|
||||||
.map((suggestion, index) => (
|
|
||||||
<StyledHotSearchColumnItem
|
|
||||||
key={index * 2 + 1}
|
|
||||||
onClick={() => onSuggestionClick(suggestion)}
|
|
||||||
>
|
|
||||||
• {suggestion}
|
|
||||||
</StyledHotSearchColumnItem>
|
|
||||||
))}
|
|
||||||
</StyledHotSearchColumn>
|
|
||||||
</StyledHotSearchContainer>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 有对话时显示对话历史 */}
|
|
||||||
<StyledConversationContainer
|
<StyledConversationContainer
|
||||||
direction='column'
|
direction='column'
|
||||||
gap={2}
|
gap={2}
|
||||||
className='conversation-container'
|
className='conversation-container'
|
||||||
sx={{
|
sx={{
|
||||||
mb: conversation?.length > 0 ? 2 : 0,
|
mb: conversation?.length > 0 ? 2 : 0,
|
||||||
display: conversation.length > 0 ? 'flex' : 'none',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{conversation.map((item, index) => (
|
{conversation.map((item, index) => (
|
||||||
<StyledConversationItem key={index}>
|
<StyledConversationItem key={index}>
|
||||||
{/* 用户问题气泡 - 右对齐 */}
|
<StyledAccordion key={index} defaultExpanded={true}>
|
||||||
<StyledUserBubble>{item.q}</StyledUserBubble>
|
<StyledAccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon sx={{ fontSize: 18 }} />}
|
||||||
{/* AI回答气泡 - 左对齐 */}
|
>
|
||||||
<StyledAiBubble>
|
<StyledQuestionText>{item.q}</StyledQuestionText>
|
||||||
{/* 搜索结果 */}
|
</StyledAccordionSummary>
|
||||||
{item.chunk_result.length > 0 && (
|
<StyledAccordionDetails>
|
||||||
<StyledChunkAccordion defaultExpanded>
|
{item.chunk_result.length > 0 && (
|
||||||
<StyledChunkAccordionSummary
|
<StyledChunkAccordion defaultExpanded>
|
||||||
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
<StyledChunkAccordionSummary
|
||||||
>
|
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
||||||
<Typography
|
|
||||||
variant='body2'
|
|
||||||
sx={theme => ({
|
|
||||||
fontSize: 12,
|
|
||||||
color: alpha(theme.palette.text.primary, 0.5),
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
共找到 {item.chunk_result.length} 个结果
|
|
||||||
</Typography>
|
|
||||||
</StyledChunkAccordionSummary>
|
|
||||||
|
|
||||||
<StyledChunkAccordionDetails>
|
|
||||||
<Stack gap={1}>
|
|
||||||
{item.chunk_result.map((chunk, chunkIndex) => (
|
|
||||||
<StyledChunkItem key={chunkIndex}>
|
|
||||||
<Typography
|
|
||||||
variant='body2'
|
|
||||||
className='hover-primary'
|
|
||||||
sx={theme => ({
|
|
||||||
fontSize: 12,
|
|
||||||
color: alpha(theme.palette.text.primary, 0.5),
|
|
||||||
})}
|
|
||||||
onClick={() => {
|
|
||||||
window.open(`/node/${chunk.node_id}`, '_blank');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{chunk.name}
|
|
||||||
</Typography>
|
|
||||||
</StyledChunkItem>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
</StyledChunkAccordionDetails>
|
|
||||||
</StyledChunkAccordion>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 加载状态 */}
|
|
||||||
{index === conversation.length - 1 && loading && (
|
|
||||||
<LoadingContent thinking={thinking} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 思考过程 */}
|
|
||||||
{!!item.thinking_content && (
|
|
||||||
<StyledThinkingAccordion defaultExpanded>
|
|
||||||
<StyledThinkingAccordionSummary
|
|
||||||
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
|
||||||
>
|
|
||||||
<Stack direction='row' alignItems='center' gap={1}>
|
|
||||||
{thinking === 2 && index === conversation.length - 1 && (
|
|
||||||
<Image
|
|
||||||
src={aiLoading}
|
|
||||||
alt='ai-loading'
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
variant='body2'
|
variant='body2'
|
||||||
sx={theme => ({
|
sx={{ fontSize: 12, color: 'text.tertiary' }}
|
||||||
fontSize: 12,
|
|
||||||
color: alpha(theme.palette.text.primary, 0.5),
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
{thinking === 2 && index === conversation.length - 1
|
共找到 {item.chunk_result.length} 个结果
|
||||||
? '思考中...'
|
|
||||||
: '已思考'}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</StyledChunkAccordionSummary>
|
||||||
</StyledThinkingAccordionSummary>
|
|
||||||
|
|
||||||
<StyledThinkingAccordionDetails>
|
<StyledChunkAccordionDetails>
|
||||||
<MarkDown2
|
<Stack gap={1}>
|
||||||
content={item.thinking_content || ''}
|
{item.chunk_result.map((chunk, index) => (
|
||||||
autoScroll={false}
|
<StyledChunkItem key={index}>
|
||||||
/>
|
<Typography
|
||||||
</StyledThinkingAccordionDetails>
|
variant='body2'
|
||||||
</StyledThinkingAccordion>
|
className='hover-primary'
|
||||||
)}
|
sx={{ fontSize: 12, color: 'text.tertiary' }}
|
||||||
|
onClick={() => {
|
||||||
|
window.open(`/node/${chunk.node_id}`, '_blank');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{chunk.name}
|
||||||
|
</Typography>
|
||||||
|
</StyledChunkItem>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</StyledChunkAccordionDetails>
|
||||||
|
</StyledChunkAccordion>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{index === conversation.length - 1 && loading && (
|
||||||
|
<>
|
||||||
|
<LoadingContent thinking={thinking} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!!item.thinking_content && (
|
||||||
|
<StyledThinkingAccordion defaultExpanded>
|
||||||
|
<StyledThinkingAccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
||||||
|
>
|
||||||
|
<Stack direction='row' alignItems='center' gap={1}>
|
||||||
|
{thinking === 2 && (
|
||||||
|
<Image
|
||||||
|
src={aiLoading}
|
||||||
|
alt='ai-loading'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant='body2'
|
||||||
|
sx={{ fontSize: 12, color: 'text.tertiary' }}
|
||||||
|
>
|
||||||
|
{thinking === 2 ? '思考中...' : '已思考'}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</StyledThinkingAccordionSummary>
|
||||||
|
|
||||||
|
<StyledThinkingAccordionDetails>
|
||||||
|
<MarkDown2
|
||||||
|
content={item.thinking_content || ''}
|
||||||
|
autoScroll={false}
|
||||||
|
/>
|
||||||
|
</StyledThinkingAccordionDetails>
|
||||||
|
</StyledThinkingAccordion>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* AI回答内容 */}
|
|
||||||
<StyledAiBubbleContent>
|
|
||||||
{item.source === 'history' ? (
|
{item.source === 'history' ? (
|
||||||
<MarkDown content={item.a} />
|
<MarkDown content={item.a} />
|
||||||
) : (
|
) : (
|
||||||
<MarkDown2 content={item.a} autoScroll={false} />
|
<MarkDown2 content={item.a} autoScroll={false} />
|
||||||
)}
|
)}
|
||||||
</StyledAiBubbleContent>
|
</StyledAccordionDetails>
|
||||||
|
</StyledAccordion>
|
||||||
|
{(index !== conversation.length - 1 || !loading) && (
|
||||||
|
<StyledActionStack
|
||||||
|
direction={mobile ? 'column' : 'row'}
|
||||||
|
alignItems={mobile ? 'flex-start' : 'center'}
|
||||||
|
justifyContent='space-between'
|
||||||
|
gap={mobile ? 1 : 3}
|
||||||
|
>
|
||||||
|
<Stack direction='row' gap={3} alignItems='center'>
|
||||||
|
<span>生成于 {dayjs(item.update_time).fromNow()}</span>
|
||||||
|
|
||||||
{/* 操作按钮 */}
|
<IconCopy
|
||||||
{(index !== conversation.length - 1 || !loading) && (
|
sx={{ cursor: 'pointer' }}
|
||||||
<StyledActionStack
|
onClick={() => {
|
||||||
direction={mobile ? 'column' : 'row'}
|
copyText(item.a);
|
||||||
alignItems={mobile ? 'flex-start' : 'center'}
|
}}
|
||||||
justifyContent='space-between'
|
/>
|
||||||
gap={mobile ? 1 : 3}
|
|
||||||
>
|
|
||||||
<Stack direction='row' gap={3} alignItems='center'>
|
|
||||||
<span>生成于 {dayjs(item.update_time).fromNow()}</span>
|
|
||||||
|
|
||||||
<IconCopy
|
{isFeedbackEnabled && item.source === 'chat' && (
|
||||||
sx={{ cursor: 'pointer' }}
|
<>
|
||||||
onClick={() => {
|
{item.score === 1 && (
|
||||||
copyText(item.a);
|
<IconZaned sx={{ cursor: 'pointer' }} />
|
||||||
}}
|
)}
|
||||||
/>
|
{item.score !== 1 && (
|
||||||
|
<IconZan
|
||||||
{isFeedbackEnabled && item.source === 'chat' && (
|
sx={{ cursor: 'pointer' }}
|
||||||
<>
|
onClick={() => {
|
||||||
{item.score === 1 && (
|
if (item.score === 0)
|
||||||
<IconZaned sx={{ cursor: 'pointer' }} />
|
handleScore(item.message_id, 1);
|
||||||
)}
|
}}
|
||||||
{item.score !== 1 && (
|
/>
|
||||||
<IconZan
|
)}
|
||||||
sx={{ cursor: 'pointer' }}
|
{item.score !== -1 && (
|
||||||
onClick={() => {
|
<IconCai
|
||||||
if (item.score === 0)
|
sx={{ cursor: 'pointer' }}
|
||||||
handleScore(item.message_id, 1);
|
onClick={() => {
|
||||||
}}
|
if (item.score === 0) {
|
||||||
/>
|
setConversationItem(item);
|
||||||
)}
|
setOpen(true);
|
||||||
{item.score !== -1 && (
|
}
|
||||||
<IconCai
|
}}
|
||||||
sx={{ cursor: 'pointer' }}
|
/>
|
||||||
onClick={() => {
|
)}
|
||||||
if (item.score === 0) {
|
{item.score === -1 && (
|
||||||
setConversationItem(item);
|
<IconCaied sx={{ cursor: 'pointer' }} />
|
||||||
setOpen(true);
|
)}
|
||||||
}
|
</>
|
||||||
}}
|
)}
|
||||||
/>
|
</Stack>
|
||||||
)}
|
</StyledActionStack>
|
||||||
{item.score === -1 && (
|
)}
|
||||||
<IconCaied sx={{ cursor: 'pointer' }} />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</StyledActionStack>
|
|
||||||
)}
|
|
||||||
</StyledAiBubble>
|
|
||||||
</StyledConversationItem>
|
</StyledConversationItem>
|
||||||
))}
|
))}
|
||||||
</StyledConversationContainer>
|
</StyledConversationContainer>
|
||||||
{conversation.length > 0 && (
|
|
||||||
<Button
|
|
||||||
variant='contained'
|
|
||||||
sx={theme => ({
|
|
||||||
textTransform: 'none',
|
|
||||||
minWidth: 'auto',
|
|
||||||
px: 3.5,
|
|
||||||
py: '2px',
|
|
||||||
gap: 0.5,
|
|
||||||
fontSize: 12,
|
|
||||||
backgroundColor: 'background.default',
|
|
||||||
color: 'text.primary',
|
|
||||||
boxShadow: `0px 1px 2px 0px ${alpha(theme.palette.text.primary, 0.06)}`,
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: alpha(theme.palette.text.primary, 0.1),
|
|
||||||
cursor: 'pointer',
|
|
||||||
'&:hover': {
|
|
||||||
boxShadow: `0px 1px 2px 0px ${alpha(theme.palette.text.primary, 0.06)}`,
|
|
||||||
borderColor: 'primary.main',
|
|
||||||
color: 'primary.main',
|
|
||||||
},
|
|
||||||
mb: 2,
|
|
||||||
})}
|
|
||||||
onClick={onReset}
|
|
||||||
>
|
|
||||||
<IconXinduihua sx={{ fontSize: 14 }} />
|
|
||||||
新会话
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<StyledInputContainer>
|
<StyledInputContainer>
|
||||||
<StyledInputWrapper>
|
<StyledInputWrapper>
|
||||||
{/* 多张图片预览 */}
|
{/* 多张图片预览 */}
|
||||||
|
|
@ -1096,6 +932,12 @@ const AiQaContent: React.FC<{
|
||||||
</StyledActionButtonStack>
|
</StyledActionButtonStack>
|
||||||
</StyledInputWrapper>
|
</StyledInputWrapper>
|
||||||
</StyledInputContainer>
|
</StyledInputContainer>
|
||||||
|
<Feedback
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
onSubmit={handleScore}
|
||||||
|
data={conversationItem}
|
||||||
|
/>
|
||||||
{/* 模糊搜索建议列表 */}
|
{/* 模糊搜索建议列表 */}
|
||||||
{showFuzzySuggestions &&
|
{showFuzzySuggestions &&
|
||||||
fuzzySuggestions.length > 0 &&
|
fuzzySuggestions.length > 0 &&
|
||||||
|
|
@ -1112,12 +954,29 @@ const AiQaContent: React.FC<{
|
||||||
</StyledFuzzySuggestionsStack>
|
</StyledFuzzySuggestionsStack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Feedback
|
{/* 原始搜索建议列表 - 只在没有模糊搜索建议时显示 */}
|
||||||
open={open}
|
{!showFuzzySuggestions &&
|
||||||
onClose={() => setOpen(false)}
|
hotSearch.length > 0 &&
|
||||||
onSubmit={handleScore}
|
conversation.length === 0 && (
|
||||||
data={conversationItem}
|
<StyledHotSearchStack gap={1}>
|
||||||
/>
|
{hotSearch.map((suggestion, index) => (
|
||||||
|
<StyledHotSearchItem
|
||||||
|
key={index}
|
||||||
|
onClick={() => onSuggestionClick(suggestion)}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant='body2'
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{suggestion} →
|
||||||
|
</Typography>
|
||||||
|
</StyledHotSearchItem>
|
||||||
|
))}
|
||||||
|
</StyledHotSearchStack>
|
||||||
|
)}
|
||||||
</StyledMainContainer>
|
</StyledMainContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,7 @@ import {
|
||||||
Typography,
|
Typography,
|
||||||
Stack,
|
Stack,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
alpha,
|
|
||||||
Skeleton,
|
|
||||||
styled,
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Logo from '@/assets/images/logo.png';
|
|
||||||
import noDocImage from '@/assets/images/no-doc.png';
|
import noDocImage from '@/assets/images/no-doc.png';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { IconJinsousuo, IconFasong, IconMianbaoxie } from '@panda-wiki/icons';
|
import { IconJinsousuo, IconFasong, IconMianbaoxie } from '@panda-wiki/icons';
|
||||||
|
|
@ -20,51 +16,7 @@ import { postShareV1ChatSearch } from '@/request/ShareChatSearch';
|
||||||
import { DomainNodeContentChunkSSE } from '@/request/types';
|
import { DomainNodeContentChunkSSE } from '@/request/types';
|
||||||
import { message } from '@ctzhian/ui';
|
import { message } from '@ctzhian/ui';
|
||||||
import { IconWenjian } from '@panda-wiki/icons';
|
import { IconWenjian } from '@panda-wiki/icons';
|
||||||
import { useStore } from '@/provider';
|
|
||||||
|
|
||||||
const StyledSearchResultItem = styled(Stack)(({ theme }) => ({
|
|
||||||
position: 'relative',
|
|
||||||
'&::before': {
|
|
||||||
content: '""',
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: '100%',
|
|
||||||
borderBottom: '1px dashed',
|
|
||||||
borderColor: alpha(theme.palette.text.primary, 0.1),
|
|
||||||
},
|
|
||||||
'&::after': {
|
|
||||||
content: '""',
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
width: '100%',
|
|
||||||
borderBottom: '1px dashed',
|
|
||||||
borderColor: alpha(theme.palette.text.primary, 0.1),
|
|
||||||
},
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
borderRadius: '8px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
transition: 'all 0.2s ease-in-out',
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: alpha(theme.palette.text.primary, 0.02),
|
|
||||||
'.hover-primary': {
|
|
||||||
color: 'primary.main',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const SearchDocSkeleton = () => {
|
|
||||||
return (
|
|
||||||
<StyledSearchResultItem>
|
|
||||||
<Stack gap={1}>
|
|
||||||
<Skeleton variant='rounded' height={16} width={200} />
|
|
||||||
<Skeleton variant='rounded' height={22} width={400} />
|
|
||||||
<Skeleton variant='rounded' height={16} width={500} />
|
|
||||||
</Stack>
|
|
||||||
</StyledSearchResultItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
interface SearchDocContentProps {
|
interface SearchDocContentProps {
|
||||||
inputRef: React.RefObject<HTMLInputElement | null>;
|
inputRef: React.RefObject<HTMLInputElement | null>;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
|
|
@ -74,7 +26,6 @@ const SearchDocContent: React.FC<SearchDocContentProps> = ({
|
||||||
inputRef,
|
inputRef,
|
||||||
placeholder,
|
placeholder,
|
||||||
}) => {
|
}) => {
|
||||||
const { kbDetail } = useStore();
|
|
||||||
// 模糊搜索相关状态
|
// 模糊搜索相关状态
|
||||||
const [fuzzySuggestions, setFuzzySuggestions] = useState<string[]>([]);
|
const [fuzzySuggestions, setFuzzySuggestions] = useState<string[]>([]);
|
||||||
const [showFuzzySuggestions, setShowFuzzySuggestions] = useState(false);
|
const [showFuzzySuggestions, setShowFuzzySuggestions] = useState(false);
|
||||||
|
|
@ -197,30 +148,6 @@ const SearchDocContent: React.FC<SearchDocContentProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack
|
|
||||||
direction='row'
|
|
||||||
alignItems='center'
|
|
||||||
justifyContent='center'
|
|
||||||
gap={2}
|
|
||||||
sx={{ mb: 3, mt: 1 }}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={kbDetail?.settings?.icon || Logo.src}
|
|
||||||
alt='logo'
|
|
||||||
width={46}
|
|
||||||
height={46}
|
|
||||||
unoptimized
|
|
||||||
style={{
|
|
||||||
objectFit: 'contain',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Typography
|
|
||||||
variant='h6'
|
|
||||||
sx={{ fontSize: 32, color: 'text.primary', fontWeight: 700 }}
|
|
||||||
>
|
|
||||||
{kbDetail?.settings?.title}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
{/* 搜索输入框 */}
|
{/* 搜索输入框 */}
|
||||||
<TextField
|
<TextField
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
|
@ -230,27 +157,26 @@ const SearchDocContent: React.FC<SearchDocContentProps> = ({
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
fullWidth
|
fullWidth
|
||||||
autoFocus
|
autoFocus
|
||||||
sx={theme => ({
|
sx={{
|
||||||
boxShadow: `0px 20px 40px 0px ${alpha(theme.palette.text.primary, 0.06)}`,
|
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
'& .MuiInputBase-root': {
|
'& .MuiInputBase-root': {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
backgroundColor: theme.palette.background.default,
|
bgcolor: 'background.paper3',
|
||||||
'& fieldset': {
|
'& fieldset': {
|
||||||
borderColor: alpha(theme.palette.text.primary, 0.1),
|
borderColor: 'background.paper3',
|
||||||
},
|
},
|
||||||
'&:hover fieldset': {
|
'&:hover fieldset': {
|
||||||
borderColor: 'primary.main',
|
borderColor: 'primary.main',
|
||||||
},
|
},
|
||||||
'&.Mui-focused fieldset': {
|
'&.Mui-focused fieldset': {
|
||||||
borderColor: `${theme.palette.primary.main} !important`,
|
borderColor: 'primary.main',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'& .MuiInputBase-input': {
|
'& .MuiInputBase-input': {
|
||||||
py: 1.5,
|
py: 1.5,
|
||||||
},
|
},
|
||||||
})}
|
}}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
input: {
|
input: {
|
||||||
startAdornment: (
|
startAdornment: (
|
||||||
|
|
@ -338,15 +264,31 @@ const SearchDocContent: React.FC<SearchDocContentProps> = ({
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* 搜索结果列表 */}
|
{/* 搜索结果列表 */}
|
||||||
<Stack sx={{ overflow: 'auto', maxHeight: 'calc(100vh - 334px)' }}>
|
<Stack sx={{ overflow: 'auto', maxHeight: 'calc(100vh - 284px)' }}>
|
||||||
{searchResults.map((result, index) => (
|
{searchResults.map((result, index) => (
|
||||||
<StyledSearchResultItem
|
<Stack
|
||||||
direction='row'
|
direction='row'
|
||||||
justifyContent='space-between'
|
justifyContent='space-between'
|
||||||
alignItems='center'
|
alignItems='center'
|
||||||
key={result.node_id}
|
key={result.node_id}
|
||||||
gap={2}
|
gap={2}
|
||||||
onClick={() => handleSearchResultClick(result)}
|
onClick={() => handleSearchResultClick(result)}
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
borderRadius: '8px',
|
||||||
|
borderBottom:
|
||||||
|
index !== searchResults.length - 1 ? 'none' : '1px dashed',
|
||||||
|
borderTop: '1px dashed',
|
||||||
|
borderColor: 'divider',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s ease-in-out',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'background.paper3',
|
||||||
|
'.hover-primary': {
|
||||||
|
color: 'primary.main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Stack sx={{ flex: 1, width: 0 }} gap={0.5}>
|
<Stack sx={{ flex: 1, width: 0 }} gap={0.5}>
|
||||||
{/* 路径 */}
|
{/* 路径 */}
|
||||||
|
|
@ -400,7 +342,7 @@ const SearchDocContent: React.FC<SearchDocContentProps> = ({
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<IconMianbaoxie sx={{ fontSize: 12 }} />
|
<IconMianbaoxie sx={{ fontSize: 12 }} />
|
||||||
</StyledSearchResultItem>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -417,11 +359,11 @@ const SearchDocContent: React.FC<SearchDocContentProps> = ({
|
||||||
|
|
||||||
{/* 搜索中状态 */}
|
{/* 搜索中状态 */}
|
||||||
{isSearching && (
|
{isSearching && (
|
||||||
<Stack sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2, textAlign: 'center' }}>
|
||||||
{[...Array(3)].map((_, index) => (
|
<Typography variant='body2' sx={{ color: 'text.tertiary' }}>
|
||||||
<SearchDocSkeleton key={index} />
|
搜索中...
|
||||||
))}
|
</Typography>
|
||||||
</Stack>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export const StyledMainContainer = styled(Box)(() => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledConversationContainer = styled(Stack)(() => ({
|
export const StyledConversationContainer = styled(Stack)(() => ({
|
||||||
maxHeight: 'calc(100vh - 332px)',
|
maxHeight: 'calc(100vh - 334px)',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
scrollbarWidth: 'none',
|
scrollbarWidth: 'none',
|
||||||
msOverflowStyle: 'none',
|
msOverflowStyle: 'none',
|
||||||
|
|
@ -27,38 +27,10 @@ export const StyledConversationContainer = styled(Stack)(() => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledConversationItem = styled(Box)(({ theme }) => ({
|
export const StyledConversationItem = styled(Box)(() => ({}));
|
||||||
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',
|
|
||||||
maxWidth: '85%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: theme.spacing(3),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const StyledAiBubbleContent = styled(Box)(() => ({
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 对话相关组件
|
// 对话相关组件
|
||||||
export const StyledAccordion = styled(Accordion)(() => ({
|
export const StyledAccordion = styled(Accordion)(({ theme }) => ({
|
||||||
padding: 0,
|
padding: 0,
|
||||||
border: 'none',
|
border: 'none',
|
||||||
'&:before': {
|
'&:before': {
|
||||||
|
|
@ -99,6 +71,7 @@ export const StyledChunkAccordion = styled(Accordion)(({ theme }) => ({
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
paddingBottom: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledChunkAccordionSummary = styled(AccordionSummary)(
|
export const StyledChunkAccordionSummary = styled(AccordionSummary)(
|
||||||
|
|
@ -169,7 +142,8 @@ export const StyledThinkingAccordionDetails = styled(AccordionDetails)(
|
||||||
// 操作区域组件
|
// 操作区域组件
|
||||||
export const StyledActionStack = styled(Stack)(({ theme }) => ({
|
export const StyledActionStack = styled(Stack)(({ theme }) => ({
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: alpha(theme.palette.text.primary, 0.35),
|
color: alpha(theme.palette.text.primary, 0.75),
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 输入区域组件
|
// 输入区域组件
|
||||||
|
|
@ -191,7 +165,6 @@ export const StyledInputWrapper = styled(Stack)(({ theme }) => ({
|
||||||
alignItems: 'flex-end',
|
alignItems: 'flex-end',
|
||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
boxShadow: `0px 20px 40px 0px ${alpha(theme.palette.text.primary, 0.06)}`,
|
|
||||||
transition: 'border-color 0.2s ease-in-out',
|
transition: 'border-color 0.2s ease-in-out',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
|
|
@ -309,36 +282,3 @@ export const StyledHotSearchItem = styled(Box)(({ theme }) => ({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 热门搜索容器
|
|
||||||
export const StyledHotSearchContainer = styled(Box)(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
gap: theme.spacing(2),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 热门搜索列
|
|
||||||
export const StyledHotSearchColumn = styled(Box)(({ theme }) => ({
|
|
||||||
flex: 1,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: theme.spacing(1),
|
|
||||||
paddingLeft: theme.spacing(2),
|
|
||||||
borderLeft: `1px solid ${alpha(theme.palette.text.primary, 0.06)}`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 热门搜索列项目
|
|
||||||
export const StyledHotSearchColumnItem = styled(Box)(({ theme }) => ({
|
|
||||||
paddingRight: theme.spacing(2),
|
|
||||||
borderRadius: '10px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
transition: 'all 0.2s',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 400,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
'&:hover': {
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,286 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
Box,
|
||||||
|
Stack,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/material';
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { ConversationItem as ConversationItemType } from '../types';
|
||||||
|
import { ChunkResultItem } from '@/assets/type';
|
||||||
|
import MarkDown from '@/components/markdown';
|
||||||
|
import MarkDown2 from '@/components/markdown2';
|
||||||
|
import {
|
||||||
|
IconCai,
|
||||||
|
IconCaied,
|
||||||
|
IconCopy,
|
||||||
|
IconZan,
|
||||||
|
IconZaned,
|
||||||
|
} from '@/components/icons';
|
||||||
|
import { copyText } from '@/utils';
|
||||||
|
import aiLoading from '@/assets/images/ai-loading.gif';
|
||||||
|
import { LoadingContent } from './LoadingContent';
|
||||||
|
import { AnswerStatusType } from '../constants';
|
||||||
|
|
||||||
|
interface ConversationItemProps {
|
||||||
|
item: ConversationItemType;
|
||||||
|
index: number;
|
||||||
|
isLast: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
thinking: AnswerStatusType;
|
||||||
|
thinkingContent: string;
|
||||||
|
answer: string;
|
||||||
|
chunkResult: ChunkResultItem[];
|
||||||
|
isChunkResult: boolean;
|
||||||
|
isThinking: boolean;
|
||||||
|
mobile: boolean;
|
||||||
|
themeMode: string;
|
||||||
|
isFeedbackEnabled: boolean;
|
||||||
|
onScoreChange: (message_id: string, score: number) => void;
|
||||||
|
onFeedbackOpen: (item: ConversationItemType) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConversationItemComponent: React.FC<ConversationItemProps> = ({
|
||||||
|
item,
|
||||||
|
index,
|
||||||
|
isLast,
|
||||||
|
loading,
|
||||||
|
thinking,
|
||||||
|
thinkingContent,
|
||||||
|
answer,
|
||||||
|
chunkResult,
|
||||||
|
isChunkResult,
|
||||||
|
isThinking,
|
||||||
|
mobile,
|
||||||
|
themeMode,
|
||||||
|
isFeedbackEnabled,
|
||||||
|
onScoreChange,
|
||||||
|
onFeedbackOpen,
|
||||||
|
}) => {
|
||||||
|
const displayChunkResult = isLast ? chunkResult : item.chunk_result;
|
||||||
|
const displayThinkingContent = isLast
|
||||||
|
? thinkingContent
|
||||||
|
: item.thinking_content;
|
||||||
|
const displayAnswer = isLast ? item.a || answer || '' : item.a;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Accordion
|
||||||
|
defaultExpanded={true}
|
||||||
|
sx={{
|
||||||
|
bgcolor:
|
||||||
|
themeMode === 'dark' ? 'background.default' : 'background.paper3',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon sx={{ fontSize: 24 }} />}
|
||||||
|
sx={{ userSelect: 'text' }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
fontWeight: '700',
|
||||||
|
lineHeight: '24px',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.q}
|
||||||
|
</Box>
|
||||||
|
</AccordionSummary>
|
||||||
|
|
||||||
|
<AccordionDetails sx={{ pt: 2 }}>
|
||||||
|
{/* Chunk结果展示 */}
|
||||||
|
{displayChunkResult.length > 0 && (
|
||||||
|
<Accordion
|
||||||
|
sx={{
|
||||||
|
bgcolor: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
p: 0,
|
||||||
|
pb: 2,
|
||||||
|
}}
|
||||||
|
defaultExpanded
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
||||||
|
sx={{
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
gap: 2,
|
||||||
|
'.MuiAccordionSummary-content': {
|
||||||
|
flexGrow: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant='body2'
|
||||||
|
sx={{ fontSize: 12, color: 'text.tertiary' }}
|
||||||
|
>
|
||||||
|
共找到 {displayChunkResult.length} 个结果
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
|
||||||
|
<AccordionDetails
|
||||||
|
sx={{
|
||||||
|
pt: 0,
|
||||||
|
pl: 2,
|
||||||
|
borderTop: 'none',
|
||||||
|
borderLeft: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack gap={1}>
|
||||||
|
{displayChunkResult.map((chunk, idx) => (
|
||||||
|
<Box
|
||||||
|
key={idx}
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover': {
|
||||||
|
'.hover-primary': {
|
||||||
|
color: 'primary.main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant='body2'
|
||||||
|
className='hover-primary'
|
||||||
|
sx={{ fontSize: 12, color: 'text.tertiary' }}
|
||||||
|
onClick={() => {
|
||||||
|
window.open(`/node/${chunk.node_id}`, '_blank');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{chunk.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 加载状态 */}
|
||||||
|
{isLast && loading && !isChunkResult && !thinkingContent && (
|
||||||
|
<LoadingContent thinking={thinking} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 思考内容展示 */}
|
||||||
|
{displayThinkingContent && (
|
||||||
|
<Accordion
|
||||||
|
sx={{
|
||||||
|
bgcolor: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
p: 0,
|
||||||
|
pb: 2,
|
||||||
|
'&:before': {
|
||||||
|
content: '""',
|
||||||
|
height: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
defaultExpanded
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
||||||
|
sx={{
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
gap: 2,
|
||||||
|
'.MuiAccordionSummary-content': {
|
||||||
|
flexGrow: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack direction='row' alignItems='center' gap={1}>
|
||||||
|
{isThinking && (
|
||||||
|
<Image
|
||||||
|
src={aiLoading}
|
||||||
|
alt='ai-loading'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Typography
|
||||||
|
variant='body2'
|
||||||
|
sx={{ fontSize: 12, color: 'text.tertiary' }}
|
||||||
|
>
|
||||||
|
{isThinking ? '思考中...' : '已思考'}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</AccordionSummary>
|
||||||
|
|
||||||
|
<AccordionDetails
|
||||||
|
sx={{
|
||||||
|
pt: 0,
|
||||||
|
pl: 2,
|
||||||
|
borderTop: 'none',
|
||||||
|
borderLeft: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
'.markdown-body': {
|
||||||
|
opacity: 0.75,
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MarkDown2 content={displayThinkingContent} />
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 答案展示 */}
|
||||||
|
{item.source === 'history' ? (
|
||||||
|
<MarkDown content={item.a} />
|
||||||
|
) : (
|
||||||
|
<MarkDown2 content={displayAnswer} />
|
||||||
|
)}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
{/* 反馈区域 */}
|
||||||
|
{(!isLast || !loading) && (
|
||||||
|
<Stack
|
||||||
|
direction={mobile ? 'column' : 'row'}
|
||||||
|
alignItems={mobile ? 'flex-start' : 'center'}
|
||||||
|
justifyContent='space-between'
|
||||||
|
gap={mobile ? 1 : 3}
|
||||||
|
sx={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: 'text.tertiary',
|
||||||
|
mt: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack direction='row' gap={3} alignItems='center'>
|
||||||
|
<span>生成于 {dayjs(item.update_time).fromNow()}</span>
|
||||||
|
|
||||||
|
<IconCopy
|
||||||
|
sx={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => copyText(item.a)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isFeedbackEnabled && item.source === 'chat' && (
|
||||||
|
<>
|
||||||
|
{item.score === 1 && <IconZaned sx={{ cursor: 'pointer' }} />}
|
||||||
|
{item.score !== 1 && (
|
||||||
|
<IconZan
|
||||||
|
sx={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
if (item.score === 0) onScoreChange(item.message_id, 1);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{item.score !== -1 && (
|
||||||
|
<IconCai
|
||||||
|
sx={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
if (item.score === 0) onFeedbackOpen(item);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{item.score === -1 && <IconCaied sx={{ cursor: 'pointer' }} />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, IconButton, Stack, TextField, Tooltip } from '@mui/material';
|
||||||
|
import { IconTupian, IconFasong } from '@panda-wiki/icons';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import ChatLoading from '@/views/chat/ChatLoading';
|
||||||
|
import { UploadedImage } from '../types';
|
||||||
|
import { AnswerStatusType } from '../constants';
|
||||||
|
|
||||||
|
interface InputAreaProps {
|
||||||
|
input: string;
|
||||||
|
loading: boolean;
|
||||||
|
thinking: AnswerStatusType;
|
||||||
|
placeholder: string;
|
||||||
|
uploadedImages: UploadedImage[];
|
||||||
|
fileInputRef: React.RefObject<HTMLInputElement | null>;
|
||||||
|
inputRef: React.RefObject<HTMLInputElement | null>;
|
||||||
|
onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
onInputFocus: () => void;
|
||||||
|
onInputBlur: () => void;
|
||||||
|
onPaste: (e: React.ClipboardEvent<HTMLDivElement>) => void;
|
||||||
|
onSearch: () => void;
|
||||||
|
onImageUpload: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
onRemoveImage: (id: string) => void;
|
||||||
|
onSearchAbort: () => void;
|
||||||
|
setThinking: (value: AnswerStatusType) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InputArea: React.FC<InputAreaProps> = ({
|
||||||
|
input,
|
||||||
|
loading,
|
||||||
|
thinking,
|
||||||
|
placeholder,
|
||||||
|
uploadedImages,
|
||||||
|
fileInputRef,
|
||||||
|
inputRef,
|
||||||
|
onInputChange,
|
||||||
|
onInputFocus,
|
||||||
|
onInputBlur,
|
||||||
|
onPaste,
|
||||||
|
onSearch,
|
||||||
|
onImageUpload,
|
||||||
|
onRemoveImage,
|
||||||
|
onSearchAbort,
|
||||||
|
setThinking,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
sx={{
|
||||||
|
px: 1.5,
|
||||||
|
py: 1,
|
||||||
|
borderRadius: '10px',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'background.paper3',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
gap: 2,
|
||||||
|
bgcolor: 'background.paper3',
|
||||||
|
transition: 'border-color 0.2s ease-in-out',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: 'primary.main',
|
||||||
|
},
|
||||||
|
'&:focus-within': {
|
||||||
|
borderColor: 'dark.main',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{uploadedImages.length > 0 && (
|
||||||
|
<Stack
|
||||||
|
direction='row'
|
||||||
|
flexWrap='wrap'
|
||||||
|
gap={1}
|
||||||
|
sx={{ width: '100%', zIndex: 1 }}
|
||||||
|
>
|
||||||
|
{uploadedImages.map(image => (
|
||||||
|
<Box
|
||||||
|
key={image.id}
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
borderRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={image.url}
|
||||||
|
alt='uploaded'
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
|
style={{ objectFit: 'cover' }}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
size='small'
|
||||||
|
onClick={() => onRemoveImage(image.id)}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 2,
|
||||||
|
right: 2,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
transition: 'opacity 0.2s',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon sx={{ fontSize: 10 }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={2}
|
||||||
|
disabled={loading}
|
||||||
|
ref={inputRef}
|
||||||
|
sx={{
|
||||||
|
bgcolor: 'background.paper3',
|
||||||
|
'.MuiInputBase-root': {
|
||||||
|
p: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: '52px !important',
|
||||||
|
},
|
||||||
|
textarea: {
|
||||||
|
borderRadius: 0,
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
msOverflowStyle: 'none',
|
||||||
|
p: '2px',
|
||||||
|
},
|
||||||
|
fieldset: {
|
||||||
|
border: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
size='small'
|
||||||
|
value={input}
|
||||||
|
onChange={onInputChange}
|
||||||
|
onFocus={onInputFocus}
|
||||||
|
onBlur={onInputBlur}
|
||||||
|
onPaste={onPaste}
|
||||||
|
onKeyDown={e => {
|
||||||
|
const isComposing =
|
||||||
|
e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
|
||||||
|
if (
|
||||||
|
e.key === 'Enter' &&
|
||||||
|
!e.shiftKey &&
|
||||||
|
input.length > 0 &&
|
||||||
|
!isComposing
|
||||||
|
) {
|
||||||
|
e.preventDefault();
|
||||||
|
onSearch();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder={placeholder}
|
||||||
|
autoComplete='off'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
direction='row'
|
||||||
|
alignItems='center'
|
||||||
|
justifyContent='space-between'
|
||||||
|
sx={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type='file'
|
||||||
|
accept='image/*'
|
||||||
|
multiple
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={onImageUpload}
|
||||||
|
/>
|
||||||
|
<Tooltip title='敬请期待'>
|
||||||
|
<IconButton size='small' disabled={loading} sx={{ flexShrink: 0 }}>
|
||||||
|
<IconTupian sx={{ fontSize: 20, color: 'text.secondary' }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Box sx={{ fontSize: 12, flexShrink: 0, cursor: 'pointer' }}>
|
||||||
|
{loading ? (
|
||||||
|
<ChatLoading
|
||||||
|
thinking={thinking}
|
||||||
|
onClick={() => {
|
||||||
|
setThinking(4);
|
||||||
|
onSearchAbort();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IconButton
|
||||||
|
size='small'
|
||||||
|
onClick={() => {
|
||||||
|
if (input.length > 0) {
|
||||||
|
onSearchAbort();
|
||||||
|
setThinking(1);
|
||||||
|
onSearch();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconFasong
|
||||||
|
sx={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: input.length > 0 ? 'primary.main' : 'text.disabled',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Stack, Typography } from '@mui/material';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import aiLoading from '@/assets/images/ai-loading.gif';
|
||||||
|
import { AnswerStatus, AnswerStatusType } from '../constants';
|
||||||
|
|
||||||
|
interface LoadingContentProps {
|
||||||
|
thinking: AnswerStatusType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadingContent: React.FC<LoadingContentProps> = ({ thinking }) => {
|
||||||
|
if (thinking === 4) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction='row' alignItems='center' gap={1} sx={{ pb: 1 }}>
|
||||||
|
<Image src={aiLoading} alt='ai-loading' width={20} height={20} />
|
||||||
|
<Typography variant='body2' sx={{ fontSize: 12, color: 'text.tertiary' }}>
|
||||||
|
{AnswerStatus[thinking]}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Stack, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
interface SuggestionListProps {
|
||||||
|
hotSearch: string[];
|
||||||
|
fuzzySuggestions: string[];
|
||||||
|
showFuzzySuggestions: boolean;
|
||||||
|
input: string;
|
||||||
|
onSuggestionClick: (text: string) => void;
|
||||||
|
highlightMatch: (text: string, query: string) => React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SuggestionList: React.FC<SuggestionListProps> = ({
|
||||||
|
hotSearch,
|
||||||
|
fuzzySuggestions,
|
||||||
|
showFuzzySuggestions,
|
||||||
|
input,
|
||||||
|
onSuggestionClick,
|
||||||
|
highlightMatch,
|
||||||
|
}) => {
|
||||||
|
// 模糊搜索建议
|
||||||
|
if (showFuzzySuggestions && fuzzySuggestions.length > 0) {
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
sx={{
|
||||||
|
mt: 1,
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1000,
|
||||||
|
}}
|
||||||
|
gap={0.5}
|
||||||
|
>
|
||||||
|
{fuzzySuggestions.map((suggestion, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
onClick={() => onSuggestionClick(suggestion)}
|
||||||
|
sx={{
|
||||||
|
py: 1,
|
||||||
|
px: 2,
|
||||||
|
borderRadius: '6px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
bgcolor: 'transparent',
|
||||||
|
color: 'text.primary',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'action.hover',
|
||||||
|
},
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: 'auto',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 400,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{highlightMatch(suggestion, input)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 热门搜索建议
|
||||||
|
if (!showFuzzySuggestions && hotSearch.length > 0) {
|
||||||
|
return (
|
||||||
|
<Stack sx={{ mt: 2 }} gap={1}>
|
||||||
|
{hotSearch.map((suggestion, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
onClick={() => onSuggestionClick(suggestion)}
|
||||||
|
sx={{
|
||||||
|
py: '6px',
|
||||||
|
px: 2,
|
||||||
|
mb: 1,
|
||||||
|
borderRadius: '10px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
bgcolor: '#F8F9FA',
|
||||||
|
color: 'text.secondary',
|
||||||
|
'&:hover': {
|
||||||
|
color: 'primary.main',
|
||||||
|
},
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant='body2' sx={{ fontSize: 14, flex: 1 }}>
|
||||||
|
{suggestion} →
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export { LoadingContent } from './LoadingContent';
|
||||||
|
export { InputArea } from './InputArea';
|
||||||
|
export { SuggestionList } from './SuggestionList';
|
||||||
|
export { ConversationItemComponent } from './ConversationItem';
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
|
import Logo from '@/assets/images/logo.png';
|
||||||
import { IconZhinengwenda, IconJinsousuo } from '@panda-wiki/icons';
|
import { IconZhinengwenda, IconJinsousuo } from '@panda-wiki/icons';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
|
|
@ -10,10 +11,8 @@ import {
|
||||||
Stack,
|
Stack,
|
||||||
lighten,
|
lighten,
|
||||||
alpha,
|
alpha,
|
||||||
styled,
|
|
||||||
Tabs,
|
|
||||||
Tab,
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import { CusTabs } from '@ctzhian/ui';
|
||||||
import AiQaContent from './AiQaContent';
|
import AiQaContent from './AiQaContent';
|
||||||
import SearchDocContent from './SearchDocContent';
|
import SearchDocContent from './SearchDocContent';
|
||||||
import { useStore } from '@/provider';
|
import { useStore } from '@/provider';
|
||||||
|
|
@ -33,48 +32,6 @@ interface QaModalProps {
|
||||||
defaultSuggestions?: SearchSuggestion[];
|
defaultSuggestions?: SearchSuggestion[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledTabs = styled(Tabs)(({ theme }) => ({
|
|
||||||
minHeight: 'auto',
|
|
||||||
position: 'relative',
|
|
||||||
borderRadius: '10px',
|
|
||||||
padding: theme.spacing(0.5),
|
|
||||||
border: `1px solid ${alpha(theme.palette.text.primary, 0.1)}`,
|
|
||||||
'& .MuiTabs-indicator': {
|
|
||||||
height: '100%',
|
|
||||||
borderRadius: '8px',
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
||||||
zIndex: 0,
|
|
||||||
},
|
|
||||||
'& .MuiTabs-flexContainer': {
|
|
||||||
gap: theme.spacing(0.5),
|
|
||||||
position: 'relative',
|
|
||||||
zIndex: 1,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 样式化的 Tab 组件 - 白色背景,圆角,深灰色文字
|
|
||||||
const StyledTab = styled(Tab)(({ theme }) => ({
|
|
||||||
minHeight: 'auto',
|
|
||||||
padding: theme.spacing(0.75, 2),
|
|
||||||
borderRadius: '6px',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 400,
|
|
||||||
textTransform: 'none',
|
|
||||||
transition: 'color 0.3s ease-in-out',
|
|
||||||
position: 'relative',
|
|
||||||
zIndex: 1,
|
|
||||||
lineHeight: 1,
|
|
||||||
'&:hover': {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
},
|
|
||||||
'&.Mui-selected': {
|
|
||||||
color: theme.palette.primary.contrastText,
|
|
||||||
fontWeight: 500,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const QaModal: React.FC<QaModalProps> = () => {
|
const QaModal: React.FC<QaModalProps> = () => {
|
||||||
const { qaModalOpen, setQaModalOpen, kbDetail, mobile } = useStore();
|
const { qaModalOpen, setQaModalOpen, kbDetail, mobile } = useStore();
|
||||||
const [searchMode, setSearchMode] = useState<'chat' | 'search'>('chat');
|
const [searchMode, setSearchMode] = useState<'chat' | 'search'>('chat');
|
||||||
|
|
@ -112,14 +69,6 @@ const QaModal: React.FC<QaModalProps> = () => {
|
||||||
}
|
}
|
||||||
}, [qaModalOpen, searchMode]);
|
}, [qaModalOpen, searchMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!qaModalOpen) {
|
|
||||||
setTimeout(() => {
|
|
||||||
setSearchMode('chat');
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
}, [qaModalOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cid = searchParams.get('cid');
|
const cid = searchParams.get('cid');
|
||||||
const ask = searchParams.get('ask');
|
const ask = searchParams.get('ask');
|
||||||
|
|
@ -143,6 +92,7 @@ const QaModal: React.FC<QaModalProps> = () => {
|
||||||
sx={theme => ({
|
sx={theme => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
maxWidth: 800,
|
maxWidth: 800,
|
||||||
maxHeight: '100%',
|
maxHeight: '100%',
|
||||||
|
|
@ -151,81 +101,87 @@ const QaModal: React.FC<QaModalProps> = () => {
|
||||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.12)',
|
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.12)',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
pb: 2,
|
'& .Mui-selected': {
|
||||||
|
color: `${theme.palette.background.default} !important`,
|
||||||
|
},
|
||||||
})}
|
})}
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* 顶部标签栏 */}
|
{/* 头部区域 */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
px: 2,
|
mx: 2,
|
||||||
pt: 2,
|
py: 1.5,
|
||||||
pb: 2.5,
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledTabs
|
{/* Logo */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||||
|
<img
|
||||||
|
src={kbDetail?.settings?.icon || Logo.src}
|
||||||
|
style={{
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
variant='h6'
|
||||||
|
sx={{ fontSize: 16, color: 'text.primary' }}
|
||||||
|
>
|
||||||
|
{kbDetail?.settings?.title}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<CusTabs
|
||||||
|
size='small'
|
||||||
|
list={[
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<Stack direction='row' gap={1} alignItems='center'>
|
||||||
|
<IconZhinengwenda sx={{ fontSize: 16 }} />
|
||||||
|
{!mobile && <span>智能问答</span>}
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
value: 'chat',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<Stack direction='row' gap={1} alignItems='center'>
|
||||||
|
<IconJinsousuo sx={{ fontSize: 16 }} />
|
||||||
|
{!mobile && <span>仅搜索文档</span>}
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
value: 'search',
|
||||||
|
},
|
||||||
|
]}
|
||||||
value={searchMode}
|
value={searchMode}
|
||||||
onChange={(_, value) => {
|
onChange={value => setSearchMode(value as 'chat' | 'search')}
|
||||||
setSearchMode(value as 'chat' | 'search');
|
/>
|
||||||
}}
|
|
||||||
variant='scrollable'
|
|
||||||
scrollButtons={false}
|
|
||||||
>
|
|
||||||
<StyledTab
|
|
||||||
label={
|
|
||||||
<Stack direction='row' gap={0.5} alignItems='center'>
|
|
||||||
<IconZhinengwenda sx={{ fontSize: 16 }} />
|
|
||||||
{!mobile && <span>智能问答</span>}
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
value='chat'
|
|
||||||
/>
|
|
||||||
<StyledTab
|
|
||||||
label={
|
|
||||||
<Stack direction='row' gap={0.5} alignItems='center'>
|
|
||||||
<IconJinsousuo sx={{ fontSize: 16 }} />
|
|
||||||
{!mobile && <span>仅搜索文档</span>}
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
value='search'
|
|
||||||
/>
|
|
||||||
</StyledTabs>
|
|
||||||
|
|
||||||
{/* Esc按钮 */}
|
{/* Esc按钮 */}
|
||||||
{!mobile && (
|
{!mobile && (
|
||||||
<Button
|
<Button
|
||||||
variant='outlined'
|
|
||||||
color='primary'
|
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
size='small'
|
size='small'
|
||||||
sx={theme => ({
|
sx={theme => ({
|
||||||
minWidth: 'auto',
|
minWidth: 'auto',
|
||||||
px: 1,
|
px: 1.5,
|
||||||
py: '1px',
|
py: 0.5,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
color: 'text.secondary',
|
|
||||||
borderColor: alpha(theme.palette.text.primary, 0.1),
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Esc
|
Esc
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 主内容区域 - 根据模式切换 */}
|
{/* 主内容区域 - 根据模式切换 */}
|
||||||
<Box
|
<Box sx={{ px: 2, display: searchMode === 'chat' ? 'block' : 'none' }}>
|
||||||
sx={{
|
|
||||||
px: 3,
|
|
||||||
flex: 1,
|
|
||||||
display: searchMode === 'chat' ? 'flex' : 'none',
|
|
||||||
flexDirection: 'column',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AiQaContent
|
<AiQaContent
|
||||||
hotSearch={hotSearch}
|
hotSearch={hotSearch}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
|
@ -233,21 +189,15 @@ const QaModal: React.FC<QaModalProps> = () => {
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{ px: 2, display: searchMode === 'search' ? 'block' : 'none' }}
|
||||||
px: 3,
|
|
||||||
flex: 1,
|
|
||||||
display: searchMode === 'search' ? 'flex' : 'none',
|
|
||||||
flexDirection: 'column',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<SearchDocContent inputRef={inputRef} placeholder={placeholder} />
|
<SearchDocContent inputRef={inputRef} placeholder={placeholder} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 底部AI生成提示 */}
|
{/* 底部AI生成提示 */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
px: 3,
|
px: 3,
|
||||||
pt: kbDetail?.settings?.disclaimer_settings?.content ? 2 : 0,
|
py: kbDetail?.settings?.disclaimer_settings?.content ? 2 : 1,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ const DocContent = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// @ts-ignore
|
||||||
window.CAP_CUSTOM_WASM_URL =
|
window.CAP_CUSTOM_WASM_URL =
|
||||||
window.location.origin + '/cap@0.0.6/cap_wasm.min.js';
|
window.location.origin + '/cap@0.0.6/cap_wasm.min.js';
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Next.js 特有配置 */
|
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "next"
|
"name": "next"
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
|
|
||||||
|
|
||||||
const IconChuangjian = (props: SvgIconProps) => (
|
|
||||||
<SvgIcon
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
viewBox='0 0 1024 1024'
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path d='M512.36408889 55.18222223C259.83431111 55.18222223 54.38577778 260.59662223 54.38577778 513.12640001S259.83431111 971.09333334 512.36408889 971.09333334s457.96693333-205.44853333 457.96693333-457.96693333S764.88248889 55.18222223 512.36408889 55.18222223z m0 846.81386666c-214.41422222 0-388.84693333-174.43271111-388.84693334-388.84693333s174.43271111-388.86968889 388.84693334-388.86968889S901.19964445 298.66666667 901.19964445 513.12640001 726.76693333 901.96195556 512.36408889 901.96195556z'></path>
|
|
||||||
<path d='M546.92977778 291.25973334h-69.13137778v187.30097778H290.49742222v69.12h187.30097778v187.30097777h69.13137778V547.68071112h187.2896v-69.12H546.92977778V291.25973334z'></path>
|
|
||||||
</SvgIcon>
|
|
||||||
);
|
|
||||||
|
|
||||||
IconChuangjian.displayName = 'icon-chuangjian';
|
|
||||||
|
|
||||||
export default IconChuangjian;
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
|
|
||||||
|
|
||||||
const IconFabu = (props: SvgIconProps) => (
|
|
||||||
<SvgIcon
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
viewBox='0 0 1024 1024'
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path d='M766.1 880.9L539.5 769.8c-16.9-8.3-24-28.7-15.7-45.7 4-8.1 11-14.3 19.5-17.3 8.6-2.9 17.9-2.4 26.1 1.6l185.8 91.2 80.2-549.1-361.2 433.4v201.4c0 18.8-15.3 34.1-34.1 34.1S406 904.1 406 885.3V672.7c0-1.8 0.1-3.5 0.4-5.3 0.8-6.6 3.5-12.9 7.7-18L754.4 241 208.8 524.1l128.6 66.7c16.9 8.9 23.6 29.5 15.3 46.6-3.8 7.9-10.7 13.9-19 16.8-8.3 2.9-17.4 2.3-25.3-1.5l-186-96.8c-7.1-3.7-12.7-9.7-15.9-17-4.2-7.9-5.1-17.2-2.5-25.7 2.6-8.6 8.5-15.8 16.5-20l749.6-388.9c5-2.6 10.6-3.9 16.2-3.9 16.7-0.3 31.1 11.5 34.1 27.9 1.1 5.5 0.9 11.1-0.6 16.5L816.1 854.9c-2.2 15.4-14.3 26.8-28.9 29-7.2 1.3-14.6 0.3-21.1-3z m0 0'></path>
|
|
||||||
</SvgIcon>
|
|
||||||
);
|
|
||||||
|
|
||||||
IconFabu.displayName = 'icon-fabu';
|
|
||||||
|
|
||||||
export default IconFabu;
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
|
|
||||||
|
|
||||||
const IconXinduihua = (props: SvgIconProps) => (
|
|
||||||
<SvgIcon
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
viewBox='0 0 1024 1024'
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path d='M333.184 987.456a52.928 52.928 0 0 1-18.688-3.712 47.424 47.424 0 0 1-16.064-10.56 44.544 44.544 0 0 1-10.624-15.36 45.76 45.76 0 0 1-4.032-18.304l-1.088-96.896a256.896 256.896 0 0 1-82.304-27.456 239.808 239.808 0 0 1-36.16-24.128 219.008 219.008 0 0 1-31.488-29.632 265.728 265.728 0 0 1-25.6-34.752 240.384 240.384 0 0 1-30.336-79.744 237.44 237.44 0 0 1-3.648-42.368V346.24c0-15.68 1.472-31.04 4.352-46.4 3.328-15.36 8.064-30.4 13.952-44.992a248.32 248.32 0 0 1 52.992-77.184A242.432 242.432 0 0 1 269.504 112.64a230.4 230.4 0 0 1 47.552-4.736H486.4a44.48 44.48 0 0 1 30.72 12.416 42.176 42.176 0 0 1 0 59.584 43.712 43.712 0 0 1-30.72 12.48H317.056c-10.56 0-20.8 0.704-30.72 2.88-10.24 1.856-20.096 4.8-29.632 8.832a139.904 139.904 0 0 0-27.392 14.208 169.344 169.344 0 0 0-23.808 19.072 171.584 171.584 0 0 0-19.712 23.36 171.008 171.008 0 0 0-14.656 26.688 155.264 155.264 0 0 0-11.712 58.88v258.24c0 10.24 0.768 20.096 2.944 30.336 2.176 9.856 5.12 19.776 9.152 29.248a148.48 148.48 0 0 0 34.752 50.816 155.52 155.52 0 0 0 51.904 33.664c9.536 4.032 19.776 6.976 30.016 9.152 10.24 1.856 20.48 2.944 31.104 2.944a48.896 48.896 0 0 1 34.688 13.888 50.88 50.88 0 0 1 11.008 15.744c2.56 5.824 3.648 12.032 4.032 18.24l0.32 59.648 108.992-77.888c27.84-19.776 58.88-29.632 93.248-29.632h134.976c10.624 0 20.864-1.088 30.72-2.944 10.24-1.856 20.096-4.736 29.632-8.768 9.472-4.032 18.624-8.768 27.392-14.272 8.448-5.504 16.512-12.096 23.808-19.008 7.296-7.296 13.888-14.976 19.712-23.424a145.344 145.344 0 0 0 23.424-55.552c2.176-9.92 2.944-19.776 2.944-30.016V473.216a40.896 40.896 0 0 1 12.8-29.632 44.48 44.48 0 0 1 47.168-9.152c5.12 1.92 9.856 5.12 13.888 9.152a40.896 40.896 0 0 1 12.8 29.632V606.72a233.92 233.92 0 0 1-41.344 132.416 221.184 221.184 0 0 1-30.336 36.16 262.208 262.208 0 0 1-36.928 29.632 229.952 229.952 0 0 1-42.048 21.952 251.712 251.712 0 0 1-93.632 18.304H599.744c-33.984 0-65.088 9.856-92.864 29.632L362.432 977.92a49.92 49.92 0 0 1-29.248 9.536z'></path>
|
|
||||||
<path d='M902.592 188.16h-237.44a42.176 42.176 0 0 0 0 84.352h237.44a42.176 42.176 0 1 0 0-84.352z'></path>
|
|
||||||
<path d='M827.008 116.288a43.2 43.2 0 0 0-86.336 0v228.096a43.2 43.2 0 0 0 86.4 0V116.288z'></path>
|
|
||||||
</SvgIcon>
|
|
||||||
);
|
|
||||||
|
|
||||||
IconXinduihua.displayName = 'icon-xinduihua';
|
|
||||||
|
|
||||||
export default IconXinduihua;
|
|
||||||
|
|
@ -40,7 +40,6 @@ export { default as IconChakan } from './IconChakan';
|
||||||
export { default as IconChangjianwenti } from './IconChangjianwenti';
|
export { default as IconChangjianwenti } from './IconChangjianwenti';
|
||||||
export { default as IconChatgpt } from './IconChatgpt';
|
export { default as IconChatgpt } from './IconChatgpt';
|
||||||
export { default as IconChilun } from './IconChilun';
|
export { default as IconChilun } from './IconChilun';
|
||||||
export { default as IconChuangjian } from './IconChuangjian';
|
|
||||||
export { default as IconCohere } from './IconCohere';
|
export { default as IconCohere } from './IconCohere';
|
||||||
export { default as IconCorrection } from './IconCorrection';
|
export { default as IconCorrection } from './IconCorrection';
|
||||||
export { default as IconDJzhinengzhaiyao } from './IconDJzhinengzhaiyao';
|
export { default as IconDJzhinengzhaiyao } from './IconDJzhinengzhaiyao';
|
||||||
|
|
@ -72,7 +71,6 @@ export { default as IconDuihao } from './IconDuihao';
|
||||||
export { default as IconDuihao1 } from './IconDuihao1';
|
export { default as IconDuihao1 } from './IconDuihao1';
|
||||||
export { default as IconDuihualishi1 } from './IconDuihualishi1';
|
export { default as IconDuihualishi1 } from './IconDuihualishi1';
|
||||||
export { default as IconExcel1 } from './IconExcel1';
|
export { default as IconExcel1 } from './IconExcel1';
|
||||||
export { default as IconFabu } from './IconFabu';
|
|
||||||
export { default as IconFankui } from './IconFankui';
|
export { default as IconFankui } from './IconFankui';
|
||||||
export { default as IconFankuiwenti } from './IconFankuiwenti';
|
export { default as IconFankuiwenti } from './IconFankuiwenti';
|
||||||
export { default as IconFasong } from './IconFasong';
|
export { default as IconFasong } from './IconFasong';
|
||||||
|
|
@ -218,7 +216,6 @@ export { default as IconXiala1 } from './IconXiala1';
|
||||||
export { default as IconXialaCopy } from './IconXialaCopy';
|
export { default as IconXialaCopy } from './IconXialaCopy';
|
||||||
export { default as IconXiaohongshu } from './IconXiaohongshu';
|
export { default as IconXiaohongshu } from './IconXiaohongshu';
|
||||||
export { default as IconXiaohongshuHui } from './IconXiaohongshuHui';
|
export { default as IconXiaohongshuHui } from './IconXiaohongshuHui';
|
||||||
export { default as IconXinduihua } from './IconXinduihua';
|
|
||||||
export { default as IconXinference } from './IconXinference';
|
export { default as IconXinference } from './IconXinference';
|
||||||
export { default as IconXingxing } from './IconXingxing';
|
export { default as IconXingxing } from './IconXingxing';
|
||||||
export { default as IconYanzhengma } from './IconYanzhengma';
|
export { default as IconYanzhengma } from './IconYanzhengma';
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
/* Icons 图标库特有配置 */
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["src/*"]
|
|
||||||
},
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
|
||||||
"target": "ES2020",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"allowJs": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"resolveJsonModule": true
|
|
||||||
},
|
|
||||||
"include": ["src", "scripts"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
||||||
|
|
@ -7,8 +7,7 @@
|
||||||
"types": "./theme.d.ts",
|
"types": "./theme.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
"./*": "./src/*.ts",
|
"./*": "./src/*.ts"
|
||||||
"./types": "./theme.d.ts"
|
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const blackPalette: PaletteOptions = {
|
const blackPalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const bluePalette: PaletteOptions = {
|
const bluePalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const darkPalette: PaletteOptions = {
|
const darkPalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const darkDeepForestPalette: PaletteOptions = {
|
const darkDeepForestPalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const blackPalette: PaletteOptions = {
|
const blackPalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const deepTealPalette: PaletteOptions = {
|
const deepTealPalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const electricBluePalette: PaletteOptions = {
|
const electricBluePalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const greenPalette: PaletteOptions = {
|
const greenPalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const lightPalette: PaletteOptions = {
|
const lightPalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const orangePalette: PaletteOptions = {
|
const orangePalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const bluePalette: PaletteOptions = {
|
const bluePalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="../theme.d.ts" />
|
||||||
import { PaletteOptions } from '@mui/material';
|
import { PaletteOptions } from '@mui/material';
|
||||||
|
|
||||||
const redPalette: PaletteOptions = {
|
const redPalette: PaletteOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
/* Themes 主题库特有配置 */
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["src/*"]
|
|
||||||
},
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
|
||||||
"target": "ES2020",
|
|
||||||
"lib": ["ES2020"],
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true
|
|
||||||
},
|
|
||||||
"include": ["src", "theme.d.ts"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
/// <reference types="@panda-wiki/themes/types" />
|
|
||||||
|
|
||||||
declare module '*.png' {
|
declare module '*.png' {
|
||||||
const value: string;
|
const value: string;
|
||||||
export default value;
|
export default value;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
boxShadow: '0px 1px 2px 0px rgba(145,158,171,0.16)',
|
boxShadow: '0px 1px 2px 0px rgba(145,158,171,0.16)',
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
color: theme.palette.text.primary,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 检测平台类型
|
// 检测平台类型
|
||||||
|
|
@ -247,7 +246,12 @@ const Header = React.memo(
|
||||||
>
|
>
|
||||||
{ctrlKShortcut}
|
{ctrlKShortcut}
|
||||||
</Box>
|
</Box>
|
||||||
<StyledButton variant='contained'>
|
<StyledButton
|
||||||
|
variant='contained'
|
||||||
|
// @ts-ignore
|
||||||
|
color='light'
|
||||||
|
sx={{}}
|
||||||
|
>
|
||||||
智能问答
|
智能问答
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,30 @@
|
||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* UI 组件库特有配置 */
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
},
|
},
|
||||||
|
"incremental": true,
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
|
||||||
"compilerOptions": {
|
|
||||||
/* 所有项目共享的基础选项 */
|
|
||||||
"incremental": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue