Compare commits

..

1 Commits

Author SHA1 Message Date
Gavan c9a6b6e403 feat: 常见问题,九宫格 2025-11-04 18:11:55 +08:00
26 changed files with 762 additions and 1441 deletions

View File

@ -4964,34 +4964,6 @@ const docTemplate = `{
} }
} }
}, },
"domain.BlockGridConfig": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
}
}
},
"title": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"domain.BrandGroup": { "domain.BrandGroup": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -6778,31 +6750,6 @@ const docTemplate = `{
} }
} }
}, },
"domain.QuestionConfig": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"question": {
"type": "string"
}
}
}
},
"title": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"domain.RagInfo": { "domain.RagInfo": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -7283,9 +7230,6 @@ const docTemplate = `{
"basic_doc_config": { "basic_doc_config": {
"$ref": "#/definitions/domain.BasicDocConfig" "$ref": "#/definitions/domain.BasicDocConfig"
}, },
"block_grid_config": {
"$ref": "#/definitions/domain.BlockGridConfig"
},
"carousel_config": { "carousel_config": {
"$ref": "#/definitions/domain.CarouselConfig" "$ref": "#/definitions/domain.CarouselConfig"
}, },
@ -7322,9 +7266,6 @@ const docTemplate = `{
"type": "string" "type": "string"
} }
}, },
"question_config": {
"$ref": "#/definitions/domain.QuestionConfig"
},
"simple_doc_config": { "simple_doc_config": {
"$ref": "#/definitions/domain.SimpleDocConfig" "$ref": "#/definitions/domain.SimpleDocConfig"
}, },
@ -7348,9 +7289,6 @@ const docTemplate = `{
"basic_doc_config": { "basic_doc_config": {
"$ref": "#/definitions/domain.BasicDocConfig" "$ref": "#/definitions/domain.BasicDocConfig"
}, },
"block_grid_config": {
"$ref": "#/definitions/domain.BlockGridConfig"
},
"carousel_config": { "carousel_config": {
"$ref": "#/definitions/domain.CarouselConfig" "$ref": "#/definitions/domain.CarouselConfig"
}, },
@ -7393,9 +7331,6 @@ const docTemplate = `{
"$ref": "#/definitions/domain.RecommendNodeListResp" "$ref": "#/definitions/domain.RecommendNodeListResp"
} }
}, },
"question_config": {
"$ref": "#/definitions/domain.QuestionConfig"
},
"simple_doc_config": { "simple_doc_config": {
"$ref": "#/definitions/domain.SimpleDocConfig" "$ref": "#/definitions/domain.SimpleDocConfig"
}, },

View File

@ -4957,34 +4957,6 @@
} }
} }
}, },
"domain.BlockGridConfig": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"url": {
"type": "string"
}
}
}
},
"title": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"domain.BrandGroup": { "domain.BrandGroup": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -6771,31 +6743,6 @@
} }
} }
}, },
"domain.QuestionConfig": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"question": {
"type": "string"
}
}
}
},
"title": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"domain.RagInfo": { "domain.RagInfo": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -7276,9 +7223,6 @@
"basic_doc_config": { "basic_doc_config": {
"$ref": "#/definitions/domain.BasicDocConfig" "$ref": "#/definitions/domain.BasicDocConfig"
}, },
"block_grid_config": {
"$ref": "#/definitions/domain.BlockGridConfig"
},
"carousel_config": { "carousel_config": {
"$ref": "#/definitions/domain.CarouselConfig" "$ref": "#/definitions/domain.CarouselConfig"
}, },
@ -7315,9 +7259,6 @@
"type": "string" "type": "string"
} }
}, },
"question_config": {
"$ref": "#/definitions/domain.QuestionConfig"
},
"simple_doc_config": { "simple_doc_config": {
"$ref": "#/definitions/domain.SimpleDocConfig" "$ref": "#/definitions/domain.SimpleDocConfig"
}, },
@ -7341,9 +7282,6 @@
"basic_doc_config": { "basic_doc_config": {
"$ref": "#/definitions/domain.BasicDocConfig" "$ref": "#/definitions/domain.BasicDocConfig"
}, },
"block_grid_config": {
"$ref": "#/definitions/domain.BlockGridConfig"
},
"carousel_config": { "carousel_config": {
"$ref": "#/definitions/domain.CarouselConfig" "$ref": "#/definitions/domain.CarouselConfig"
}, },
@ -7386,9 +7324,6 @@
"$ref": "#/definitions/domain.RecommendNodeListResp" "$ref": "#/definitions/domain.RecommendNodeListResp"
} }
}, },
"question_config": {
"$ref": "#/definitions/domain.QuestionConfig"
},
"simple_doc_config": { "simple_doc_config": {
"$ref": "#/definitions/domain.SimpleDocConfig" "$ref": "#/definitions/domain.SimpleDocConfig"
}, },

View File

@ -833,24 +833,6 @@ definitions:
- ids - ids
- kb_id - kb_id
type: object type: object
domain.BlockGridConfig:
properties:
list:
items:
properties:
id:
type: string
name:
type: string
url:
type: string
type: object
type: array
title:
type: string
type:
type: string
type: object
domain.BrandGroup: domain.BrandGroup:
properties: properties:
links: links:
@ -2011,22 +1993,6 @@ definitions:
model: model:
type: string type: string
type: object type: object
domain.QuestionConfig:
properties:
list:
items:
properties:
id:
type: string
question:
type: string
type: object
type: array
title:
type: string
type:
type: string
type: object
domain.RagInfo: domain.RagInfo:
properties: properties:
message: message:
@ -2343,8 +2309,6 @@ definitions:
$ref: '#/definitions/domain.BannerConfig' $ref: '#/definitions/domain.BannerConfig'
basic_doc_config: basic_doc_config:
$ref: '#/definitions/domain.BasicDocConfig' $ref: '#/definitions/domain.BasicDocConfig'
block_grid_config:
$ref: '#/definitions/domain.BlockGridConfig'
carousel_config: carousel_config:
$ref: '#/definitions/domain.CarouselConfig' $ref: '#/definitions/domain.CarouselConfig'
case_config: case_config:
@ -2369,8 +2333,6 @@ definitions:
items: items:
type: string type: string
type: array type: array
question_config:
$ref: '#/definitions/domain.QuestionConfig'
simple_doc_config: simple_doc_config:
$ref: '#/definitions/domain.SimpleDocConfig' $ref: '#/definitions/domain.SimpleDocConfig'
text_config: text_config:
@ -2386,8 +2348,6 @@ definitions:
$ref: '#/definitions/domain.BannerConfig' $ref: '#/definitions/domain.BannerConfig'
basic_doc_config: basic_doc_config:
$ref: '#/definitions/domain.BasicDocConfig' $ref: '#/definitions/domain.BasicDocConfig'
block_grid_config:
$ref: '#/definitions/domain.BlockGridConfig'
carousel_config: carousel_config:
$ref: '#/definitions/domain.CarouselConfig' $ref: '#/definitions/domain.CarouselConfig'
case_config: case_config:
@ -2416,8 +2376,6 @@ definitions:
items: items:
$ref: '#/definitions/domain.RecommendNodeListResp' $ref: '#/definitions/domain.RecommendNodeListResp'
type: array type: array
question_config:
$ref: '#/definitions/domain.QuestionConfig'
simple_doc_config: simple_doc_config:
$ref: '#/definitions/domain.SimpleDocConfig' $ref: '#/definitions/domain.SimpleDocConfig'
text_config: text_config:

View File

@ -293,23 +293,6 @@ type TextImgConfig struct {
Desc string `json:"desc"` Desc string `json:"desc"`
} `json:"item"` } `json:"item"`
} }
type QuestionConfig struct {
Type string `json:"type"`
Title string `json:"title"`
List []struct {
ID string `json:"id"`
Question string `json:"question"`
} `json:"list"`
}
type BlockGridConfig struct {
Type string `json:"type"`
Title string `json:"title"`
List []struct {
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
} `json:"list"`
}
type WebAppLandingConfig struct { type WebAppLandingConfig struct {
Type string `json:"type"` Type string `json:"type"`
@ -327,8 +310,6 @@ type WebAppLandingConfig struct {
FeatureConfig *FeatureConfig `json:"feature_config,omitempty"` FeatureConfig *FeatureConfig `json:"feature_config,omitempty"`
ImgTextConfig *ImgTextConfig `json:"img_text_config,omitempty"` ImgTextConfig *ImgTextConfig `json:"img_text_config,omitempty"`
TextImgConfig *TextImgConfig `json:"text_img_config,omitempty"` TextImgConfig *TextImgConfig `json:"text_img_config,omitempty"`
QuestionConfig *QuestionConfig `json:"question_config,omitempty"`
BlockGridConfig *BlockGridConfig `json:"block_grid_config,omitempty"`
ComConfigOrder []string `json:"com_config_order"` ComConfigOrder []string `json:"com_config_order"`
} }
@ -543,8 +524,6 @@ type WebAppLandingConfigResp struct {
FeatureConfig *FeatureConfig `json:"feature_config,omitempty"` FeatureConfig *FeatureConfig `json:"feature_config,omitempty"`
ImgTextConfig *ImgTextConfig `json:"img_text_config,omitempty"` ImgTextConfig *ImgTextConfig `json:"img_text_config,omitempty"`
TextImgConfig *TextImgConfig `json:"text_img_config,omitempty"` TextImgConfig *TextImgConfig `json:"text_img_config,omitempty"`
QuestionConfig *QuestionConfig `json:"question_config,omitempty"`
BlockGridConfig *BlockGridConfig `json:"block_grid_config,omitempty"`
ComConfigOrder []string `json:"com_config_order"` ComConfigOrder []string `json:"com_config_order"`
NodeIds []string `json:"node_ids"` NodeIds []string `json:"node_ids"`
Nodes []*RecommendNodeListResp `json:"nodes" gorm:"-"` Nodes []*RecommendNodeListResp `json:"nodes" gorm:"-"`

@ -1 +1 @@
Subproject commit b6e6fb64429a827fb4a95fa9ee3bb4379ac18cd9 Subproject commit e7bf0a030242425c83291b369770c113fd503a5a

View File

@ -422,8 +422,6 @@ func (u *AppUsecase) GetAppDetailByKBIDAndAppType(ctx context.Context, kbID stri
FeatureConfig: app.Settings.WebAppLandingConfigs[i].FeatureConfig, FeatureConfig: app.Settings.WebAppLandingConfigs[i].FeatureConfig,
ImgTextConfig: app.Settings.WebAppLandingConfigs[i].ImgTextConfig, ImgTextConfig: app.Settings.WebAppLandingConfigs[i].ImgTextConfig,
TextImgConfig: app.Settings.WebAppLandingConfigs[i].TextImgConfig, TextImgConfig: app.Settings.WebAppLandingConfigs[i].TextImgConfig,
QuestionConfig: app.Settings.WebAppLandingConfigs[i].QuestionConfig,
BlockGridConfig: app.Settings.WebAppLandingConfigs[i].BlockGridConfig,
ComConfigOrder: app.Settings.WebAppLandingConfigs[i].ComConfigOrder, ComConfigOrder: app.Settings.WebAppLandingConfigs[i].ComConfigOrder,
NodeIds: app.Settings.WebAppLandingConfigs[i].NodeIds, NodeIds: app.Settings.WebAppLandingConfigs[i].NodeIds,
} }
@ -554,8 +552,6 @@ func (u *AppUsecase) ShareGetWebAppInfo(ctx context.Context, kbID string, authId
ImgTextConfig: app.Settings.WebAppLandingConfigs[i].ImgTextConfig, ImgTextConfig: app.Settings.WebAppLandingConfigs[i].ImgTextConfig,
TextImgConfig: app.Settings.WebAppLandingConfigs[i].TextImgConfig, TextImgConfig: app.Settings.WebAppLandingConfigs[i].TextImgConfig,
MetricsConfig: app.Settings.WebAppLandingConfigs[i].MetricsConfig, MetricsConfig: app.Settings.WebAppLandingConfigs[i].MetricsConfig,
QuestionConfig: app.Settings.WebAppLandingConfigs[i].QuestionConfig,
BlockGridConfig: app.Settings.WebAppLandingConfigs[i].BlockGridConfig,
ComConfigOrder: app.Settings.WebAppLandingConfigs[i].ComConfigOrder, ComConfigOrder: app.Settings.WebAppLandingConfigs[i].ComConfigOrder,
NodeIds: app.Settings.WebAppLandingConfigs[i].NodeIds, NodeIds: app.Settings.WebAppLandingConfigs[i].NodeIds,
} }

View File

@ -19,6 +19,9 @@
"@emoji-mart/data": "^1.2.1", "@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1", "@emoji-mart/react": "^1.1.1",
"@reduxjs/toolkit": "^2.5.0", "@reduxjs/toolkit": "^2.5.0",
"@tiptap/extension-collaboration": "^3.3.0",
"@tiptap/extension-collaboration-caret": "^3.3.0",
"ace-builds": "^1.43.4",
"axios": "^1.7.9", "axios": "^1.7.9",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"echarts": "^5.6.0", "echarts": "^5.6.0",
@ -29,9 +32,9 @@
"lottie-react": "^2.4.1", "lottie-react": "^2.4.1",
"lowlight": "^3.3.0", "lowlight": "^3.3.0",
"prosemirror-state": "^1.4.3", "prosemirror-state": "^1.4.3",
"react-ace": "^14.0.1",
"react-color-palette": "^7.3.1", "react-color-palette": "^7.3.1",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
"react-diff-viewer": "^3.1.1",
"react-dropzone": "^14.3.8", "react-dropzone": "^14.3.8",
"react-image-crop": "^11.0.10", "react-image-crop": "^11.0.10",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",

View File

@ -30,7 +30,7 @@ const Config = ({ setIsEdit, id }: ConfigProps) => {
}; };
const handleListChange = ( const handleListChange = (
newList: (typeof DEFAULT_DATA.block_grid)['list'], newList: (typeof DEFAULT_DATA.blockGrid)['list'],
) => { ) => {
setValue('list', newList); setValue('list', newList);
setIsEdit(true); setIsEdit(true);

View File

@ -96,7 +96,7 @@ const FaqDragList: FC<FaqDragListProps> = ({ data, onChange, setIsEdit }) => {
))} ))}
</Stack> </Stack>
</SortableContext> </SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: '0 0' }}> {/* <DragOverlay adjustScale style={{ transformOrigin: '0 0' }}>
{activeId ? ( {activeId ? (
<Item <Item
isDragging isDragging
@ -105,7 +105,7 @@ const FaqDragList: FC<FaqDragListProps> = ({ data, onChange, setIsEdit }) => {
handleUpdateItem={handleUpdateItem} handleUpdateItem={handleUpdateItem}
/> />
) : null} ) : null}
</DragOverlay> </DragOverlay> */}
</DndContext> </DndContext>
); );
}; };

View File

@ -1,142 +0,0 @@
import {
ConstsContributeStatus,
ConstsContributeType,
getApiProV1ContributeDetail,
GithubComChaitinPandaWikiProApiContributeV1ContributeDetailResp,
GithubComChaitinPandaWikiProApiContributeV1ContributeItem,
} from '@/request/pro';
import { useAppSelector } from '@/store';
import { Modal } from '@ctzhian/ui';
import { Box, Button, Stack } from '@mui/material';
import { IconWenjian } from '@panda-wiki/icons';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import ReactDiffViewer from 'react-diff-viewer';
type MarkdownPreviewModalProps = {
open: boolean;
row: GithubComChaitinPandaWikiProApiContributeV1ContributeItem | null;
onClose: () => void;
onAccept: () => void;
onReject: () => void;
};
const MarkdownPreviewModal = ({
open,
row,
onClose,
onAccept,
onReject,
}: MarkdownPreviewModalProps) => {
const { kb_id = '' } = useAppSelector(state => state.config);
const [data, setData] =
useState<GithubComChaitinPandaWikiProApiContributeV1ContributeDetailResp | null>(
null,
);
useEffect(() => {
if (open && row) {
getApiProV1ContributeDetail({ id: row.id!, kb_id }).then(res => {
setData(res);
});
}
}, [open, row, kb_id]);
return (
<Modal
open={open}
onCancel={onClose}
width={'1200px'}
sx={{
'.MuiDialogContent-root': {
display: 'flex',
flexDirection: 'column',
},
}}
title={
<Stack direction='row' alignItems='center' gap={2}>
<Box>
{row?.auth_name || '匿名用户'}
{row?.type === ConstsContributeType.ContributeTypeAdd
? '新增'
: '修改'}
</Box>
<Box sx={{ fontSize: 14, color: 'text.auxiliary', fontWeight: 400 }}>
{dayjs(row?.created_at).fromNow()}
</Box>
</Stack>
}
footer={
row?.status === ConstsContributeStatus.ContributeStatusPending ||
row?.status === ConstsContributeStatus.ContributeStatusRejected ? (
<Stack
direction='row'
gap={1}
justifyContent='flex-end'
sx={{ p: 3, pt: 0 }}
>
{row?.status === ConstsContributeStatus.ContributeStatusPending ? (
<>
<Button
size='small'
variant='outlined'
color='error'
onClick={onReject}
>
</Button>
<Button size='small' variant='contained' onClick={onAccept}>
</Button>
</>
) : (
<Button onClick={onClose} size='small' variant='contained'>
</Button>
)}
</Stack>
) : null
}
>
<Stack direction='row'>
<Stack
spacing={2}
sx={{
overflow: 'auto',
flex: 1,
}}
>
<Stack
direction='row'
gap={1}
sx={{ bgcolor: 'background.paper2', p: 1, borderRadius: '10px' }}
>
<Box sx={{ fontSize: 14, fontWeight: 'bold', flexShrink: 0 }}>
</Box>
<Box sx={{ fontSize: 14, color: 'text.tertiary' }}>
{data?.reason || '-'}
</Box>
</Stack>
<Stack
direction='row'
alignItems='center'
gap={1}
sx={{ fontSize: 24, fontWeight: 700, pb: 2 }}
>
<IconWenjian /> {row?.node_name || '-'}
</Stack>
<Box sx={{ overflowY: 'auto', maxHeight: 'calc(100vh - 400px)' }}>
<ReactDiffViewer
oldValue={data?.original_node?.content || ''}
newValue={data?.content || ''}
/>
</Box>
</Stack>
</Stack>
</Modal>
);
};
export default MarkdownPreviewModal;

View File

@ -1,27 +1,26 @@
import { useState, useEffect } from 'react';
import { styled } from '@mui/material/styles';
import Logo from '@/assets/images/logo.png'; import Logo from '@/assets/images/logo.png';
import { Box, Chip, Stack, TextField } from '@mui/material';
import Card from '@/components/Card'; import Card from '@/components/Card';
import { tableSx } from '@/constant/styles'; import { tableSx } from '@/constant/styles';
import { Ellipsis, message, Modal, Table } from '@ctzhian/ui';
import type { ColumnType } from '@ctzhian/ui/dist/Table';
import { Box, Chip, Stack, TextField } from '@mui/material';
import { styled } from '@mui/material/styles';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useEffect, useState } from 'react'; import { Table, Ellipsis, message, Modal } from '@ctzhian/ui';
import type { ColumnType } from '@ctzhian/ui/dist/Table';
import DocModal from './DocModal'; import DocModal from './DocModal';
import { useURLSearchParams } from '@/hooks';
import { import {
getApiProV1ContributeList, getApiProV1ContributeList,
postApiProV1ContributeAudit, postApiProV1ContributeAudit,
} from '@/request/pro/Contribute'; } from '@/request/pro/Contribute';
import { import {
GithubComChaitinPandaWikiProApiContributeV1ContributeItem,
ConstsContributeStatus, ConstsContributeStatus,
ConstsContributeType, ConstsContributeType,
GithubComChaitinPandaWikiProApiContributeV1ContributeItem,
} from '@/request/pro/types'; } from '@/request/pro/types';
import { useURLSearchParams } from '@/hooks';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import ContributePreviewModal from './ContributePreviewModal'; import ContributePreviewModal from './ContributePreviewModal';
import MarkdownPreviewModal from './MarkdownPreviewModal';
const StyledSearchRow = styled(Stack)(({ theme }) => ({ const StyledSearchRow = styled(Stack)(({ theme }) => ({
padding: theme.spacing(2), padding: theme.spacing(2),
@ -366,15 +365,6 @@ export default function ContributionPage() {
}} }}
/> />
{previewRow?.meta?.content_type === 'md' ? (
<MarkdownPreviewModal
open={open}
row={previewRow}
onClose={closeDialog}
onAccept={handleAccept}
onReject={handleReject}
/>
) : (
<ContributePreviewModal <ContributePreviewModal
open={open} open={open}
row={previewRow} row={previewRow}
@ -382,7 +372,6 @@ export default function ContributionPage() {
onAccept={handleAccept} onAccept={handleAccept}
onReject={handleReject} onReject={handleReject}
/> />
)}
<DocModal <DocModal
open={docModalOpen} open={docModalOpen}
onClose={() => setDocModalOpen(false)} onClose={() => setDocModalOpen(false)}

View File

@ -9,6 +9,7 @@ import { DomainNodeListItemResp, V1NodeDetailResp } from '@/request/types';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import { addOpacityToColor } from '@/utils'; import { addOpacityToColor } from '@/utils';
import { convertToTree } from '@/utils/drag'; import { convertToTree } from '@/utils/drag';
import { filterEmptyFolders } from '@/utils/tree';
import { Ellipsis, Icon } from '@ctzhian/ui'; import { Ellipsis, Icon } from '@ctzhian/ui';
import { alpha, Box, IconButton, Stack, useTheme } from '@mui/material'; import { alpha, Box, IconButton, Stack, useTheme } from '@mui/material';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
@ -65,7 +66,7 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
kb_id: kb_id || localStorage.getItem('kb_id') || '', kb_id: kb_id || localStorage.getItem('kb_id') || '',
}; };
getApiV1NodeList(params).then(res => { getApiV1NodeList(params).then(res => {
const v = convertToTree(res || []); const v = filterEmptyFolders(convertToTree(res || []));
setData(v); setData(v);
// 计算当前文档的所有父级文件夹,并默认展开 // 计算当前文档的所有父级文件夹,并默认展开
try { try {
@ -109,75 +110,6 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
}); });
}; };
const renderAdd = (parentId: string) => {
return (
<Box sx={{ flexShrink: 0 }} onClick={e => e.stopPropagation()}>
<Cascader
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
context={
<IconButton>
<Icon
className='catalog-folder-add-icon'
type='icon-icon_tool_close'
sx={{
fontSize: 16,
color: 'action.selected',
transform: 'rotate(45deg)',
}}
/>
</IconButton>
}
list={Object.entries(ImportContentWays).map(([key, value]) => ({
key,
label: (
<Box key={key}>
<Stack
direction={'row'}
alignItems={'center'}
gap={1}
sx={{
fontSize: 14,
px: 2,
lineHeight: '40px',
height: 40,
width: 180,
borderRadius: '5px',
cursor: 'pointer',
':hover': {
bgcolor: addOpacityToColor(
theme.palette.primary.main,
0.1,
),
},
}}
onClick={() => value.onClick(parentId)}
>
{value.label}
</Stack>
{key === 'OfflineFile' && (
<Box
sx={{
borderTop: '1px solid',
borderColor: theme.palette.divider,
my: 0.5,
}}
/>
)}
</Box>
),
}))}
/>
</Box>
);
};
const renderTree = (items: ITreeItem[], pl = 2.5, depth = 1) => { const renderTree = (items: ITreeItem[], pl = 2.5, depth = 1) => {
const sortedItems = [...items].sort( const sortedItems = [...items].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0), (a, b) => (a.order ?? 0) - (b.order ?? 0),
@ -278,7 +210,72 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
MD MD
</Box> </Box>
)} )}
{item.type === 1 && renderAdd(item.id)} {item.type === 1 && (
<Box sx={{ flexShrink: 0 }} onClick={e => e.stopPropagation()}>
<Cascader
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
context={
<IconButton>
<Icon
className='catalog-folder-add-icon'
type='icon-icon_tool_close'
sx={{
fontSize: 16,
color: 'action.selected',
transform: 'rotate(45deg)',
}}
/>
</IconButton>
}
list={Object.entries(ImportContentWays).map(([key, value]) => ({
key,
label: (
<Box key={key}>
<Stack
direction={'row'}
alignItems={'center'}
gap={1}
sx={{
fontSize: 14,
px: 2,
lineHeight: '40px',
height: 40,
width: 180,
borderRadius: '5px',
cursor: 'pointer',
':hover': {
bgcolor: addOpacityToColor(
theme.palette.primary.main,
0.1,
),
},
}}
onClick={() => value.onClick(item.id)}
>
{value.label}
</Stack>
{key === 'OfflineFile' && (
<Box
sx={{
borderTop: '1px solid',
borderColor: theme.palette.divider,
my: 0.5,
}}
/>
)}
</Box>
),
}))}
/>
</Box>
)}
</Stack> </Stack>
{item.children && {item.children &&
item.children.length > 0 && item.children.length > 0 &&
@ -344,12 +341,6 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
/> />
</Stack> </Stack>
</Stack> </Stack>
<Stack
direction={'row'}
alignItems={'center'}
justifyContent={'space-between'}
sx={{ pr: 1 }}
>
<Box <Box
sx={{ sx={{
px: 2, px: 2,
@ -360,8 +351,6 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
> >
</Box> </Box>
{renderAdd('')}
</Stack>
<Stack <Stack
sx={{ sx={{
my: 1, my: 1,
@ -379,13 +368,11 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
open={customDocOpen} open={customDocOpen}
parentId={opraParentId} parentId={opraParentId}
onCreated={node => { onCreated={node => {
if (opraParentId) {
// 复用工具方法findItemDeep / setProperty // 复用工具方法findItemDeep / setProperty
setData(prev => { setData(prev => {
const parent = findItemDeep(prev, opraParentId); const parent = findItemDeep(prev, opraParentId);
if (!parent) return prev; if (!parent) return prev;
const children = const children = (parent.children as ITreeItem[] | undefined) ?? [];
(parent.children as ITreeItem[] | undefined) ?? [];
const lastOrder = children.length const lastOrder = children.length
? (children[children.length - 1].order ?? children.length - 1) ? (children[children.length - 1].order ?? children.length - 1)
: -1; : -1;
@ -413,25 +400,6 @@ const Catalog = ({ curNode, setCatalogOpen }: CatalogProps) => {
if (opraParentId) ns.add(opraParentId); if (opraParentId) ns.add(opraParentId);
return ns; return ns;
}); });
} else {
const newChild: ITreeItem = {
id: node.id,
name: node.name,
content_type: node.content_type,
type: node.type,
emoji: node.emoji,
parentId: '',
level: 1,
order: data.length
? (data[data.length - 1].order ?? data.length - 1)
: -1,
status: 1,
children: node.type === 1 ? [] : undefined,
};
setData(prev => {
return [...prev, newChild];
});
}
}} }}
onClose={() => setCustomDocOpen(false)} onClose={() => setCustomDocOpen(false)}
/> />

View File

@ -1,10 +1,11 @@
import { import { Editor, UseTiptapReturn } from '@ctzhian/tiptap';
EditorMarkdown, import { alpha, Box, Divider, Stack, useTheme } from '@mui/material';
MarkdownEditorRef, import { useState } from 'react';
UseTiptapReturn, import AceEditor from 'react-ace';
} from '@ctzhian/tiptap';
import { Box } from '@mui/material'; import 'ace-builds/src-noconflict/ext-language_tools';
import { forwardRef } from 'react'; import 'ace-builds/src-noconflict/mode-markdown';
import 'ace-builds/src-noconflict/theme-github';
interface MarkdownEditorProps { interface MarkdownEditorProps {
editor: UseTiptapReturn['editor']; editor: UseTiptapReturn['editor'];
@ -13,8 +14,17 @@ interface MarkdownEditorProps {
header: React.ReactNode; header: React.ReactNode;
} }
const MarkdownEditor = forwardRef<MarkdownEditorRef, MarkdownEditorProps>( const MarkdownEditor = ({
({ editor, value, onChange, header }, ref) => { editor,
value,
onChange,
header,
}: MarkdownEditorProps) => {
const theme = useTheme();
const [displayMode, setDisplayMode] = useState<'edit' | 'preview' | 'split'>(
'split',
);
return ( return (
<Box <Box
sx={{ sx={{
@ -25,17 +35,111 @@ const MarkdownEditor = forwardRef<MarkdownEditorRef, MarkdownEditorProps>(
}} }}
> >
<Box sx={{}}>{header}</Box> <Box sx={{}}>{header}</Box>
<EditorMarkdown <Stack
editor={editor} direction={'row'}
alignItems={'stretch'}
sx={{
position: 'relative',
flex: 1,
border: '1px solid',
borderColor: 'divider',
}}
>
<Stack
direction='row'
sx={{
position: 'absolute',
p: 0.5,
top: -32,
left: -1,
border: '1px solid',
borderColor: 'divider',
borderBottom: 'none',
borderRadius: '4px 4px 0 0',
fontSize: 12,
color: 'text.tertiary',
'.md-display-mode-active': {
color: 'primary.main',
bgcolor: alpha(theme.palette.primary.main, 0.1),
},
'& :hover:not(.md-display-mode-active)': {
borderRadius: '4px',
bgcolor: 'background.paper3',
},
}}
>
<Box
className={displayMode === 'split' ? 'md-display-mode-active' : ''}
sx={{ px: 1, py: 0.25, cursor: 'pointer', borderRadius: '4px' }}
onClick={() => setDisplayMode('split')}
>
</Box>
<Box
className={displayMode === 'edit' ? 'md-display-mode-active' : ''}
sx={{ px: 1, py: 0.25, cursor: 'pointer', borderRadius: '4px' }}
onClick={() => setDisplayMode('edit')}
>
</Box>
<Box
className={
displayMode === 'preview' ? 'md-display-mode-active' : ''
}
sx={{ px: 1, py: 0.25, cursor: 'pointer', borderRadius: '4px' }}
onClick={() => setDisplayMode('preview')}
>
</Box>
</Stack>
{['edit', 'split'].includes(displayMode) && (
<Stack
direction='column'
sx={{
flex: 1,
fontFamily: 'monospace',
}}
>
<AceEditor
mode='markdown'
theme='twilight'
value={value} value={value}
onAceChange={onChange} onChange={onChange}
height='calc(100vh - 340px)' name='project-doc-editor'
wrapEnabled={true}
showPrintMargin={false}
fontSize={16}
editorProps={{ $blockScrolling: true }}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
showLineNumbers: true,
tabSize: 2,
}}
style={{
width: '100%',
height: 'calc(100vh - 56px)',
}}
/> />
</Stack>
)}
{displayMode === 'split' && <Divider orientation='vertical' flexItem />}
{['split', 'preview'].includes(displayMode) && (
<Box
id='markdown-preview-container'
sx={{
overflowY: 'scroll',
flex: 1,
p: 2,
height: 'calc(100vh - 56px)',
}}
>
<Editor editor={editor} />
</Box>
)}
</Stack>
</Box> </Box>
); );
}, };
);
MarkdownEditor.displayName = 'MarkdownEditor';
export default MarkdownEditor; export default MarkdownEditor;

View File

@ -17,7 +17,6 @@ interface TocProps {
setFixed: (fixed: boolean) => void; setFixed: (fixed: boolean) => void;
setShowSummary: (showSummary: boolean) => void; setShowSummary: (showSummary: boolean) => void;
isMarkdown: boolean; isMarkdown: boolean;
scrollToHeading?: (headingText: string) => void;
} }
const HeadingIcon = [ const HeadingIcon = [
@ -35,13 +34,7 @@ const HeadingSx = [
{ fontSize: 14, fontWeight: 400, color: 'text.disabled' }, { fontSize: 14, fontWeight: 400, color: 'text.disabled' },
]; ];
const Toc = ({ const Toc = ({ headings, fixed, setFixed, isMarkdown }: TocProps) => {
headings,
fixed,
setFixed,
isMarkdown,
scrollToHeading,
}: TocProps) => {
const storageTocOpen = localStorage.getItem('toc-open'); const storageTocOpen = localStorage.getItem('toc-open');
const [open, setOpen] = useState(!!storageTocOpen); const [open, setOpen] = useState(!!storageTocOpen);
const levels = Array.from( const levels = Array.from(
@ -198,10 +191,6 @@ const Toc = ({
behavior: 'smooth', behavior: 'smooth',
}); });
} }
// 同时滚动 AceEditor
if (scrollToHeading) {
scrollToHeading(it.textContent);
}
} else { } else {
// 在富文本编辑器模式下,滚动整个窗口 // 在富文本编辑器模式下,滚动整个窗口
const offset = 100; const offset = 100;

View File

@ -3,13 +3,7 @@ import Emoji from '@/components/Emoji';
import { postApiV1CreationTabComplete, putApiV1NodeDetail } from '@/request'; import { postApiV1CreationTabComplete, putApiV1NodeDetail } from '@/request';
import { V1NodeDetailResp } from '@/request/types'; import { V1NodeDetailResp } from '@/request/types';
import { useAppSelector } from '@/store'; import { useAppSelector } from '@/store';
import { import { TocList, useTiptap, UseTiptapReturn } from '@ctzhian/tiptap';
EditorMarkdown,
MarkdownEditorRef,
TocList,
useTiptap,
UseTiptapReturn,
} from '@ctzhian/tiptap';
import { Icon, message } from '@ctzhian/ui'; import { Icon, message } from '@ctzhian/ui';
import { Box, Stack, TextField, Tooltip } from '@mui/material'; import { Box, Stack, TextField, Tooltip } from '@mui/material';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -25,6 +19,7 @@ import { WrapContext } from '..';
import AIGenerate from './AIGenerate'; import AIGenerate from './AIGenerate';
import FullTextEditor from './FullTextEditor'; import FullTextEditor from './FullTextEditor';
import Header from './Header'; import Header from './Header';
import MarkdownEditor from './MarkdownEditor';
import Summary from './Summary'; import Summary from './Summary';
import Toc from './Toc'; import Toc from './Toc';
import Toolbar from './Toolbar'; import Toolbar from './Toolbar';
@ -48,8 +43,6 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
null, null,
); );
const markdownEditorRef = useRef<MarkdownEditorRef>(null);
const isMarkdown = useMemo(() => { const isMarkdown = useMemo(() => {
return defaultDetail.meta?.content_type === 'md'; return defaultDetail.meta?.content_type === 'md';
}, [defaultDetail.meta?.content_type]); }, [defaultDetail.meta?.content_type]);
@ -192,7 +185,7 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
editable: !isMarkdown, editable: !isMarkdown,
contentType: isMarkdown ? 'markdown' : 'html', contentType: isMarkdown ? 'markdown' : 'html',
immediatelyRender: true, immediatelyRender: true,
content: defaultDetail.content, content: defaultDetail.content || '',
exclude: ['invisibleCharacters', 'youtube', 'mention'], exclude: ['invisibleCharacters', 'youtube', 'mention'],
onCreate: ({ editor: tiptapEditor }) => { onCreate: ({ editor: tiptapEditor }) => {
const characterCount = ( const characterCount = (
@ -591,27 +584,17 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
</Box> </Box>
<Box sx={{ ...(fixedToc && { display: 'flex' }) }}> <Box sx={{ ...(fixedToc && { display: 'flex' }) }}>
{isMarkdown ? ( {isMarkdown ? (
<Box <MarkdownEditor
sx={{
mt: '56px',
px: 10,
pt: 4,
flex: 1,
}}
>
<Box sx={{}}>{renderEditorTitleEmojiSummary()}</Box>
<EditorMarkdown
ref={markdownEditorRef}
editor={editorRef.editor} editor={editorRef.editor}
value={nodeDetail?.content || ''} value={nodeDetail?.content || ''}
onAceChange={value => { onChange={value => {
updateDetail({ updateDetail({
content: value, content: value,
}); });
editorRef.setContent(value);
}} }}
height='calc(100vh - 340px)' header={renderEditorTitleEmojiSummary()}
/> />
</Box>
) : ( ) : (
<FullTextEditor <FullTextEditor
editor={editorRef.editor} editor={editorRef.editor}
@ -626,12 +609,6 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
isMarkdown={isMarkdown} isMarkdown={isMarkdown}
setFixed={setFixedToc} setFixed={setFixedToc}
setShowSummary={setShowSummary} setShowSummary={setShowSummary}
scrollToHeading={
isMarkdown
? headingText =>
markdownEditorRef.current?.scrollToHeading(headingText)
: undefined
}
/> />
<AIGenerate <AIGenerate
open={aiGenerateOpen} open={aiGenerateOpen}

View File

@ -376,7 +376,6 @@ export interface GithubComChaitinPandaWikiProApiContributeV1ContributeListResp {
} }
export interface GithubComChaitinPandaWikiProApiContributeV1NodeMeta { export interface GithubComChaitinPandaWikiProApiContributeV1NodeMeta {
content_type?: string;
doc_width?: string; doc_width?: string;
emoji?: string; emoji?: string;
} }
@ -493,7 +492,6 @@ export type GithubComChaitinPandaWikiProApiShareV1OAuthCallbackResp = Record<
export interface GithubComChaitinPandaWikiProApiShareV1SubmitContributeReq { export interface GithubComChaitinPandaWikiProApiShareV1SubmitContributeReq {
captcha_token: string; captcha_token: string;
content?: string; content?: string;
content_type: "html" | "md";
emoji?: string; emoji?: string;
name?: string; name?: string;
node_id?: string; node_id?: string;

View File

@ -134,25 +134,6 @@ export enum ConstsSourceType {
SourceTypeOpenAIAPI = "openai_api", SourceTypeOpenAIAPI = "openai_api",
} }
export enum ConstsNodeRagInfoStatus {
/** 等待基础处理 */
NodeRagStatusBasicPending = "BASIC_PENDING",
/** 正在进行基础处理(文本分割、向量化等) */
NodeRagStatusBasicRunning = "BASIC_RUNNING",
/** 基础处理失败 */
NodeRagStatusBasicFailed = "BASIC_FAILED",
/** 基础处理成功 */
NodeRagStatusBasicSucceeded = "BASIC_SUCCEEDED",
/** 基础处理完成,等待增强处理 */
NodeRagStatusEnhancePending = "ENHANCE_PENDING",
/** 正在进行增强处理(关键词提取等) */
NodeRagStatusEnhanceRunning = "ENHANCE_RUNNING",
/** 增强处理失败 */
NodeRagStatusEnhanceFailed = "ENHANCE_FAILED",
/** 增强处理成功 */
NodeRagStatusEnhanceSucceeded = "ENHANCE_SUCCEEDED",
}
export enum ConstsNodePermName { export enum ConstsNodePermName {
/** 导航内可见 */ /** 导航内可见 */
NodePermNameVisible = "visible", NodePermNameVisible = "visible",
@ -493,16 +474,6 @@ export interface DomainBatchMoveReq {
parent_id?: string; parent_id?: string;
} }
export interface DomainBlockGridConfig {
list?: {
id?: string;
name?: string;
url?: string;
}[];
title?: string;
type?: string;
}
export interface DomainBrandGroup { export interface DomainBrandGroup {
links?: DomainLink[]; links?: DomainLink[];
name?: string; name?: string;
@ -966,7 +937,6 @@ export interface DomainNodeListItemResp {
parent_id?: string; parent_id?: string;
permissions?: DomainNodePermissions; permissions?: DomainNodePermissions;
position?: number; position?: number;
rag_info?: DomainRagInfo;
status?: DomainNodeStatus; status?: DomainNodeStatus;
summary?: string; summary?: string;
type?: DomainNodeType; type?: DomainNodeType;
@ -1112,20 +1082,6 @@ export interface DomainProviderModelListItem {
model?: string; model?: string;
} }
export interface DomainQuestionConfig {
list?: {
id?: string;
question?: string;
}[];
title?: string;
type?: string;
}
export interface DomainRagInfo {
message?: string;
status?: ConstsNodeRagInfoStatus;
}
export interface DomainRecommendNodeListResp { export interface DomainRecommendNodeListResp {
emoji?: string; emoji?: string;
id?: string; id?: string;
@ -1285,7 +1241,6 @@ export interface DomainWebAppCustomSettings {
export interface DomainWebAppLandingConfig { export interface DomainWebAppLandingConfig {
banner_config?: DomainBannerConfig; banner_config?: DomainBannerConfig;
basic_doc_config?: DomainBasicDocConfig; basic_doc_config?: DomainBasicDocConfig;
block_grid_config?: DomainBlockGridConfig;
carousel_config?: DomainCarouselConfig; carousel_config?: DomainCarouselConfig;
case_config?: DomainCaseConfig; case_config?: DomainCaseConfig;
com_config_order?: string[]; com_config_order?: string[];
@ -1296,7 +1251,6 @@ export interface DomainWebAppLandingConfig {
img_text_config?: DomainImgTextConfig; img_text_config?: DomainImgTextConfig;
metrics_config?: DomainMetricsConfig; metrics_config?: DomainMetricsConfig;
node_ids?: string[]; node_ids?: string[];
question_config?: DomainQuestionConfig;
simple_doc_config?: DomainSimpleDocConfig; simple_doc_config?: DomainSimpleDocConfig;
text_config?: DomainTextConfig; text_config?: DomainTextConfig;
text_img_config?: DomainTextImgConfig; text_img_config?: DomainTextImgConfig;
@ -1306,7 +1260,6 @@ export interface DomainWebAppLandingConfig {
export interface DomainWebAppLandingConfigResp { export interface DomainWebAppLandingConfigResp {
banner_config?: DomainBannerConfig; banner_config?: DomainBannerConfig;
basic_doc_config?: DomainBasicDocConfig; basic_doc_config?: DomainBasicDocConfig;
block_grid_config?: DomainBlockGridConfig;
carousel_config?: DomainCarouselConfig; carousel_config?: DomainCarouselConfig;
case_config?: DomainCaseConfig; case_config?: DomainCaseConfig;
com_config_order?: string[]; com_config_order?: string[];
@ -1318,7 +1271,6 @@ export interface DomainWebAppLandingConfigResp {
metrics_config?: DomainMetricsConfig; metrics_config?: DomainMetricsConfig;
node_ids?: string[]; node_ids?: string[];
nodes?: DomainRecommendNodeListResp[]; nodes?: DomainRecommendNodeListResp[];
question_config?: DomainQuestionConfig;
simple_doc_config?: DomainSimpleDocConfig; simple_doc_config?: DomainSimpleDocConfig;
text_config?: DomainTextConfig; text_config?: DomainTextConfig;
text_img_config?: DomainTextImgConfig; text_img_config?: DomainTextImgConfig;

View File

@ -1,62 +1,21 @@
'use client'; 'use client';
import React, { useState } from 'react';
import { useStore } from '@/provider'; import { useStore } from '@/provider';
import { Modal } from '@ctzhian/ui'; import { Stack, Tooltip, Fab, Zoom } from '@mui/material';
import { usePathname, useParams } from 'next/navigation';
import MenuIcon from '@mui/icons-material/Menu';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import MenuIcon from '@mui/icons-material/Menu';
import {
Fab,
FormControlLabel,
Radio,
RadioGroup,
Stack,
Tooltip,
Zoom,
} from '@mui/material';
import { useParams, usePathname } from 'next/navigation';
import { useState } from 'react';
const DocFab = () => { const DocFab = () => {
const pathname = usePathname(); const pathname = usePathname();
const { id: docId } = useParams() || {}; const { id: docId } = useParams() || {};
const { kbDetail, mobile } = useStore(); const { kbDetail, mobile } = useStore();
const [showActions, setShowActions] = useState(false); const [showActions, setShowActions] = useState(false);
const [contentType, setContentType] = useState<'html' | 'md'>('html');
const [openSelectContentTypeModal, setOpenSelectContentTypeModal] =
useState(false);
if (mobile) return null; if (mobile) return null;
return ( return (
<>
<Modal
title='新建文档类型'
open={openSelectContentTypeModal}
onCancel={() => {
setOpenSelectContentTypeModal(false);
setContentType('html');
}}
onOk={() => {
setOpenSelectContentTypeModal(false);
window.open(`/editor?contentType=${contentType}`, '_blank');
}}
>
<RadioGroup
value={contentType}
onChange={e => setContentType(e.target.value as 'html' | 'md')}
>
<FormControlLabel
value='html'
control={<Radio size='small' />}
label='富文本'
/>
<FormControlLabel
value='md'
control={<Radio size='small' />}
label='Markdown'
/>
</RadioGroup>
</Modal>
<Stack <Stack
gap={1} gap={1}
sx={{ sx={{
@ -78,7 +37,7 @@ const DocFab = () => {
color='primary' color='primary'
size='small' size='small'
onClick={() => { onClick={() => {
setOpenSelectContentTypeModal(true); window.open(`/editor`, '_blank');
}} }}
> >
<AddIcon /> <AddIcon />
@ -122,7 +81,6 @@ const DocFab = () => {
</> </>
)} )}
</Stack> </Stack>
</>
); );
}; };

View File

@ -376,7 +376,6 @@ export interface GithubComChaitinPandaWikiProApiContributeV1ContributeListResp {
} }
export interface GithubComChaitinPandaWikiProApiContributeV1NodeMeta { export interface GithubComChaitinPandaWikiProApiContributeV1NodeMeta {
content_type?: string;
doc_width?: string; doc_width?: string;
emoji?: string; emoji?: string;
} }
@ -493,7 +492,6 @@ export type GithubComChaitinPandaWikiProApiShareV1OAuthCallbackResp = Record<
export interface GithubComChaitinPandaWikiProApiShareV1SubmitContributeReq { export interface GithubComChaitinPandaWikiProApiShareV1SubmitContributeReq {
captcha_token: string; captcha_token: string;
content?: string; content?: string;
content_type: "html" | "md";
emoji?: string; emoji?: string;
name?: string; name?: string;
node_id?: string; node_id?: string;

View File

@ -134,25 +134,6 @@ export enum ConstsSourceType {
SourceTypeOpenAIAPI = "openai_api", SourceTypeOpenAIAPI = "openai_api",
} }
export enum ConstsNodeRagInfoStatus {
/** 等待基础处理 */
NodeRagStatusBasicPending = "BASIC_PENDING",
/** 正在进行基础处理(文本分割、向量化等) */
NodeRagStatusBasicRunning = "BASIC_RUNNING",
/** 基础处理失败 */
NodeRagStatusBasicFailed = "BASIC_FAILED",
/** 基础处理成功 */
NodeRagStatusBasicSucceeded = "BASIC_SUCCEEDED",
/** 基础处理完成,等待增强处理 */
NodeRagStatusEnhancePending = "ENHANCE_PENDING",
/** 正在进行增强处理(关键词提取等) */
NodeRagStatusEnhanceRunning = "ENHANCE_RUNNING",
/** 增强处理失败 */
NodeRagStatusEnhanceFailed = "ENHANCE_FAILED",
/** 增强处理成功 */
NodeRagStatusEnhanceSucceeded = "ENHANCE_SUCCEEDED",
}
export enum ConstsNodePermName { export enum ConstsNodePermName {
/** 导航内可见 */ /** 导航内可见 */
NodePermNameVisible = "visible", NodePermNameVisible = "visible",
@ -493,16 +474,6 @@ export interface DomainBatchMoveReq {
parent_id?: string; parent_id?: string;
} }
export interface DomainBlockGridConfig {
list?: {
id?: string;
name?: string;
url?: string;
}[];
title?: string;
type?: string;
}
export interface DomainBrandGroup { export interface DomainBrandGroup {
links?: DomainLink[]; links?: DomainLink[];
name?: string; name?: string;
@ -966,7 +937,6 @@ export interface DomainNodeListItemResp {
parent_id?: string; parent_id?: string;
permissions?: DomainNodePermissions; permissions?: DomainNodePermissions;
position?: number; position?: number;
rag_info?: DomainRagInfo;
status?: DomainNodeStatus; status?: DomainNodeStatus;
summary?: string; summary?: string;
type?: DomainNodeType; type?: DomainNodeType;
@ -1112,20 +1082,6 @@ export interface DomainProviderModelListItem {
model?: string; model?: string;
} }
export interface DomainQuestionConfig {
list?: {
id?: string;
question?: string;
}[];
title?: string;
type?: string;
}
export interface DomainRagInfo {
message?: string;
status?: ConstsNodeRagInfoStatus;
}
export interface DomainRecommendNodeListResp { export interface DomainRecommendNodeListResp {
emoji?: string; emoji?: string;
id?: string; id?: string;
@ -1285,7 +1241,6 @@ export interface DomainWebAppCustomSettings {
export interface DomainWebAppLandingConfig { export interface DomainWebAppLandingConfig {
banner_config?: DomainBannerConfig; banner_config?: DomainBannerConfig;
basic_doc_config?: DomainBasicDocConfig; basic_doc_config?: DomainBasicDocConfig;
block_grid_config?: DomainBlockGridConfig;
carousel_config?: DomainCarouselConfig; carousel_config?: DomainCarouselConfig;
case_config?: DomainCaseConfig; case_config?: DomainCaseConfig;
com_config_order?: string[]; com_config_order?: string[];
@ -1296,7 +1251,6 @@ export interface DomainWebAppLandingConfig {
img_text_config?: DomainImgTextConfig; img_text_config?: DomainImgTextConfig;
metrics_config?: DomainMetricsConfig; metrics_config?: DomainMetricsConfig;
node_ids?: string[]; node_ids?: string[];
question_config?: DomainQuestionConfig;
simple_doc_config?: DomainSimpleDocConfig; simple_doc_config?: DomainSimpleDocConfig;
text_config?: DomainTextConfig; text_config?: DomainTextConfig;
text_img_config?: DomainTextImgConfig; text_img_config?: DomainTextImgConfig;
@ -1306,7 +1260,6 @@ export interface DomainWebAppLandingConfig {
export interface DomainWebAppLandingConfigResp { export interface DomainWebAppLandingConfigResp {
banner_config?: DomainBannerConfig; banner_config?: DomainBannerConfig;
basic_doc_config?: DomainBasicDocConfig; basic_doc_config?: DomainBasicDocConfig;
block_grid_config?: DomainBlockGridConfig;
carousel_config?: DomainCarouselConfig; carousel_config?: DomainCarouselConfig;
case_config?: DomainCaseConfig; case_config?: DomainCaseConfig;
com_config_order?: string[]; com_config_order?: string[];
@ -1318,7 +1271,6 @@ export interface DomainWebAppLandingConfigResp {
metrics_config?: DomainMetricsConfig; metrics_config?: DomainMetricsConfig;
node_ids?: string[]; node_ids?: string[];
nodes?: DomainRecommendNodeListResp[]; nodes?: DomainRecommendNodeListResp[];
question_config?: DomainQuestionConfig;
simple_doc_config?: DomainSimpleDocConfig; simple_doc_config?: DomainSimpleDocConfig;
text_config?: DomainTextConfig; text_config?: DomainTextConfig;
text_img_config?: DomainTextImgConfig; text_img_config?: DomainTextImgConfig;

View File

@ -1,3 +1,4 @@
import { Box, Drawer, IconButton, Stack } from '@mui/material';
import { import {
H1Icon, H1Icon,
H2Icon, H2Icon,
@ -8,15 +9,12 @@ import {
TocList, TocList,
} from '@ctzhian/tiptap'; } from '@ctzhian/tiptap';
import { Ellipsis, Icon } from '@ctzhian/ui'; import { Ellipsis, Icon } from '@ctzhian/ui';
import { Box, Drawer, IconButton, Stack } from '@mui/material';
import { useState } from 'react'; import { useState } from 'react';
interface TocProps { interface TocProps {
headings: TocList; headings: TocList;
fixed: boolean; fixed: boolean;
setFixed: (fixed: boolean) => void; setFixed: (fixed: boolean) => void;
isMarkdown: boolean;
scrollToHeading?: (headingText: string) => void;
} }
const HeadingIcon = [ const HeadingIcon = [
@ -34,13 +32,7 @@ const HeadingSx = [
{ fontSize: 14, fontWeight: 400, color: 'text.disabled' }, { fontSize: 14, fontWeight: 400, color: 'text.disabled' },
]; ];
const Toc = ({ const Toc = ({ headings, fixed, setFixed }: TocProps) => {
headings,
fixed,
setFixed,
scrollToHeading,
isMarkdown,
}: TocProps) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const levels = Array.from( const levels = Array.from(
new Set(headings.map(it => it.level).sort((a, b) => a - b)), new Set(headings.map(it => it.level).sort((a, b) => a - b)),
@ -172,30 +164,6 @@ const Toc = ({
onClick={() => { onClick={() => {
const element = document.getElementById(it.id); const element = document.getElementById(it.id);
if (element) { if (element) {
if (isMarkdown) {
const container = document.getElementById(
'markdown-preview-container',
);
if (container) {
const containerRect =
container.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
const offset = 20; // 顶部偏移
const scrollTop =
container.scrollTop +
elementRect.top -
containerRect.top -
offset;
container.scrollTo({
top: scrollTop,
behavior: 'smooth',
});
}
// 同时滚动 AceEditor
if (scrollToHeading) {
scrollToHeading(it.textContent);
}
} else {
const offset = 100; const offset = 100;
const elementPosition = const elementPosition =
element.getBoundingClientRect().top; element.getBoundingClientRect().top;
@ -206,7 +174,6 @@ const Toc = ({
behavior: 'smooth', behavior: 'smooth',
}); });
} }
}
}} }}
> >
<Box <Box

View File

@ -2,20 +2,13 @@
import Emoji from '@/components/emoji'; import Emoji from '@/components/emoji';
import { postShareProV1FileUploadWithProgress } from '@/request/pro/otherCustomer'; import { postShareProV1FileUploadWithProgress } from '@/request/pro/otherCustomer';
import { V1NodeDetailResp } from '@/request/types'; import { V1NodeDetailResp } from '@/request/types';
import { import { Editor, TocList, useTiptap, UseTiptapReturn } from '@ctzhian/tiptap';
Editor,
EditorMarkdown,
MarkdownEditorRef,
TocList,
useTiptap,
UseTiptapReturn,
} from '@ctzhian/tiptap';
import { message } from '@ctzhian/ui'; import { message } from '@ctzhian/ui';
import { Box, Stack, TextField } from '@mui/material'; import { Box, Stack, TextField } from '@mui/material';
import { IconAShijian2, IconZiti } from '@panda-wiki/icons'; import { IconAShijian2, IconZiti } from '@panda-wiki/icons';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useParams, useSearchParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useWrapContext } from '..'; import { useWrapContext } from '..';
import AIGenerate from './AIGenerate'; import AIGenerate from './AIGenerate';
import ConfirmModal from './ConfirmModal'; import ConfirmModal from './ConfirmModal';
@ -28,12 +21,9 @@ interface WrapProps {
} }
const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => { const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
const searchParams = useSearchParams(); const { catalogOpen, nodeDetail, setNodeDetail, onSave } = useWrapContext();
const contentType = searchParams.get('contentType') || 'html';
const { nodeDetail, setNodeDetail, onSave } = useWrapContext();
const { id } = useParams(); const { id } = useParams();
const markdownEditorRef = useRef<MarkdownEditorRef>(null);
const [characterCount, setCharacterCount] = useState(0); const [characterCount, setCharacterCount] = useState(0);
const [headings, setHeadings] = useState<TocList>([]); const [headings, setHeadings] = useState<TocList>([]);
const [fixedToc, setFixedToc] = useState(false); const [fixedToc, setFixedToc] = useState(false);
@ -41,14 +31,6 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
const [aiGenerateOpen, setAiGenerateOpen] = useState(false); const [aiGenerateOpen, setAiGenerateOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [confirmModalOpen, setConfirmModalOpen] = useState(false); const [confirmModalOpen, setConfirmModalOpen] = useState(false);
const isMarkdown = useMemo(() => {
if (!id && contentType === 'md') {
return true;
}
return defaultDetail.meta?.content_type === 'md';
}, [defaultDetail.meta?.content_type, contentType]);
const updateDetail = (value: V1NodeDetailResp) => { const updateDetail = (value: V1NodeDetailResp) => {
setNodeDetail({ setNodeDetail({
...nodeDetail, ...nodeDetail,
@ -91,9 +73,8 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
}; };
const editorRef = useTiptap({ const editorRef = useTiptap({
editable: true,
immediatelyRender: false, immediatelyRender: false,
editable: !isMarkdown,
contentType: isMarkdown ? 'markdown' : 'html',
content: defaultDetail?.content || '', content: defaultDetail?.content || '',
exclude: ['invisibleCharacters', 'youtube', 'mention'], exclude: ['invisibleCharacters', 'youtube', 'mention'],
onCreate: ({ editor: tiptapEditor }) => { onCreate: ({ editor: tiptapEditor }) => {
@ -125,33 +106,17 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
if ((event.ctrlKey || event.metaKey) && event.key === 's') { if ((event.ctrlKey || event.metaKey) && event.key === 's') {
event.preventDefault(); event.preventDefault();
if (editorRef && editorRef.editor) { if (editorRef && editorRef.editor) {
const value = editorRef.getContent(); const html = editorRef.getHTML();
updateDetail({ updateDetail({
content: value, content: html,
}); });
setTimeout(() => {
if (checkRequiredFields()) {
setConfirmModalOpen(true); setConfirmModalOpen(true);
} }
}, 10);
}
} }
}, },
[editorRef], [editorRef, onSave],
); );
const checkRequiredFields = useCallback(() => {
if (!nodeDetail?.name?.trim()) {
message.error('请先输入文档名称');
return false;
}
if (!nodeDetail?.content?.trim()) {
message.error('请先输入文档内容');
return false;
}
return true;
}, [nodeDetail]);
useEffect(() => { useEffect(() => {
document.addEventListener('keydown', handleGlobalSave); document.addEventListener('keydown', handleGlobalSave);
return () => { return () => {
@ -183,14 +148,10 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
detail={nodeDetail!} detail={nodeDetail!}
updateDetail={updateDetail} updateDetail={updateDetail}
handleSave={async () => { handleSave={async () => {
if (checkRequiredFields()) {
setConfirmModalOpen(true); setConfirmModalOpen(true);
}
}} }}
/> />
{!isMarkdown && (
<Toolbar editorRef={editorRef} handleAiGenerate={handleAiGenerate} /> <Toolbar editorRef={editorRef} handleAiGenerate={handleAiGenerate} />
)}
</Box> </Box>
<Box <Box
sx={{ sx={{
@ -202,8 +163,8 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
<Box <Box
sx={{ sx={{
width: `calc(100vw - 160px - ${fixedToc ? 292 : 0}px)`, width: `calc(100vw - 160px - ${fixedToc ? 292 : 0}px)`,
p: isMarkdown ? '72px 80px' : '72px 80px 150px', p: '72px 80px 150px',
mt: isMarkdown ? '56px' : '102px', mt: '102px',
mx: 'auto', mx: 'auto',
}} }}
> >
@ -292,21 +253,7 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
{characterCount} {characterCount}
</Stack> </Stack>
</Stack> </Stack>
{editorRef.editor && (
<Box sx={{ ...(fixedToc && { display: 'flex' }) }}>
{isMarkdown ? (
<EditorMarkdown
ref={markdownEditorRef}
editor={editorRef.editor}
value={nodeDetail?.content || defaultDetail?.content || ''}
onAceChange={value => {
updateDetail({
content: value,
});
}}
height='calc(100vh - 340px)'
/>
) : (
<Box <Box
sx={{ sx={{
wordBreak: 'break-all', wordBreak: 'break-all',
@ -320,24 +267,10 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
}, },
}} }}
> >
<Editor editor={editorRef.editor} /> {editorRef.editor && <Editor editor={editorRef.editor} />}
</Box> </Box>
)}
</Box> </Box>
)} <Toc headings={headings} fixed={fixedToc} setFixed={setFixedToc} />
</Box>
<Toc
headings={headings}
fixed={fixedToc}
setFixed={setFixedToc}
isMarkdown={isMarkdown}
scrollToHeading={
isMarkdown
? headingText =>
markdownEditorRef.current?.scrollToHeading(headingText)
: undefined
}
/>
</Box> </Box>
<AIGenerate <AIGenerate
@ -351,11 +284,11 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
open={confirmModalOpen} open={confirmModalOpen}
onCancel={() => setConfirmModalOpen(false)} onCancel={() => setConfirmModalOpen(false)}
onOk={async (reason: string, token: string) => { onOk={async (reason: string, token: string) => {
const value = editorRef.getContent(); const value = editorRef.getHTML();
updateDetail({ updateDetail({
content: value, content: value,
}); });
await onSave(value, reason, token, isMarkdown ? 'md' : 'html'); await onSave(value, reason, token);
setConfirmModalOpen(false); setConfirmModalOpen(false);
}} }}
/> />

View File

@ -1,10 +1,11 @@
'use client'; 'use client';
import { createContext, useContext } from 'react';
import { postShareProV1ContributeSubmit } from '@/request/pro/ShareContribute'; import { postShareProV1ContributeSubmit } from '@/request/pro/ShareContribute';
import { V1NodeDetailResp } from '@/request/types'; import { V1NodeDetailResp } from '@/request/types';
import { message } from '@ctzhian/ui'; import { useParams, useRouter } from 'next/navigation';
import { Box, Stack, useMediaQuery } from '@mui/material'; import { Box, Stack, useMediaQuery } from '@mui/material';
import { useParams } from 'next/navigation'; import { message } from '@ctzhian/ui';
import { createContext, useContext, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Edit from './edit'; import Edit from './edit';
export interface WrapContext { export interface WrapContext {
@ -12,12 +13,7 @@ export interface WrapContext {
setCatalogOpen: (open: boolean) => void; setCatalogOpen: (open: boolean) => void;
nodeDetail: V1NodeDetailResp | null; nodeDetail: V1NodeDetailResp | null;
setNodeDetail: (detail: V1NodeDetailResp) => void; setNodeDetail: (detail: V1NodeDetailResp) => void;
onSave: ( onSave: (content: string, reason: string, token: string) => void;
content: string,
reason: string,
token: string,
contentType?: 'html' | 'md',
) => void;
saveLoading: boolean; saveLoading: boolean;
} }
@ -45,12 +41,7 @@ const DocEditor = () => {
); );
const [catalogOpen, setCatalogOpen] = useState(true); const [catalogOpen, setCatalogOpen] = useState(true);
const onSave = ( const onSave = (content: string, reason: string, token: string) => {
content: string,
reason: string,
token: string,
contentType?: 'html' | 'md',
) => {
setSaveLoading(true); setSaveLoading(true);
return postShareProV1ContributeSubmit({ return postShareProV1ContributeSubmit({
node_id: id ? id[0] : undefined, node_id: id ? id[0] : undefined,
@ -60,7 +51,6 @@ const DocEditor = () => {
reason, reason,
emoji: nodeDetail?.meta?.emoji, emoji: nodeDetail?.meta?.emoji,
captcha_token: token, captcha_token: token,
content_type: contentType || 'html',
}).then(() => { }).then(() => {
message.success('保存成功, 即将关闭页面'); message.success('保存成功, 即将关闭页面');
setTimeout(() => { setTimeout(() => {

View File

@ -18,7 +18,7 @@
"license": "ISC", "license": "ISC",
"packageManager": "pnpm@10.12.1", "packageManager": "pnpm@10.12.1",
"dependencies": { "dependencies": {
"@ctzhian/tiptap": "^1.11.4", "@ctzhian/tiptap": "^1.10.3",
"@ctzhian/ui": "^7.0.5", "@ctzhian/ui": "^7.0.5",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",

View File

@ -209,6 +209,9 @@ const Carousel = ({ title, items }: CarouselProps) => {
document.body.removeChild(measureContainer); document.body.removeChild(measureContainer);
// 获取目标背景色(从计算样式获取)
const targetBgColor = alpha(theme.palette.text.primary, 0.5).toString();
// 设置容器初始状态(只有 padding背景色透明 // 设置容器初始状态(只有 padding背景色透明
gsap.set(descElement, { gsap.set(descElement, {
width: padding, width: padding,

File diff suppressed because it is too large Load Diff