Compare commits

..

4 Commits

Author SHA1 Message Date
yu.kuai e5bad16b3c pref: code 2025-11-05 16:07:02 +08:00
yu.kuai 1a621205ef feat: 记录学习状态 2025-11-05 15:51:40 +08:00
yu.kuai 0081f05cd9 feat: 网页挂件配置支持推荐问题和推荐文档 2025-11-05 14:07:28 +08:00
yu.kuai 1d0e4857a9 fix: 发布人员 icon 调整大小 2025-11-05 11:15:43 +08:00
13 changed files with 190 additions and 53 deletions

View File

@ -1,5 +1,8 @@
import { AppType, IconMap, ModelProvider } from '@/constant/enums';
import { DomainNodePermissions } from '@/request/types';
import {
ConstsNodeRagInfoStatus,
DomainNodePermissions,
} from '@/request/types';
export type Paging = {
page?: number;
@ -193,6 +196,8 @@ export interface ITreeItem {
parentId?: string;
content_type?: string;
summary?: string;
rag_status?: ConstsNodeRagInfoStatus;
rag_message?: string;
children?: ITreeItem[];
type: 1 | 2;
isEditting?: boolean;

View File

@ -1,9 +1,8 @@
import { postApiV1NodeSummary } from '@/request/Node';
import { DomainRecommendNodeListResp } from '@/request/types';
import { useAppSelector } from '@/store';
import { Ellipsis, Icon } from '@ctzhian/ui';
import { Box, IconButton, Stack } from '@mui/material';
import { Ellipsis, Icon, message } from '@ctzhian/ui';
import { CSSProperties, forwardRef, HTMLAttributes, useState } from 'react';
import { CSSProperties, forwardRef, HTMLAttributes } from 'react';
export type ItemProps = HTMLAttributes<HTMLDivElement> & {
item: DomainRecommendNodeListResp;
@ -38,19 +37,6 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
minWidth: '0px',
...style,
};
const [loading, setLoading] = useState(false);
const handleCreateSummary = () => {
setLoading(true);
postApiV1NodeSummary({ ids: [item.id!], kb_id })
.then(() => {
message.success('生成摘要成功');
refresh?.();
})
.finally(() => {
setLoading(false);
});
};
return (
<Box ref={ref} style={inlineStyles} {...props}>
@ -102,11 +88,6 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
</Box>
) : null}
{/* : item.type === 2 ? <Button size='small' loading={loading} sx={{
height: '21px',
px: 0,
ml: '18px',
}} onClick={handleCreateSummary}></Button> : null} */}
{item.recommend_nodes && item.recommend_nodes.length > 0 && (
<Stack sx={{ fontSize: 14, color: 'text.tertiary', pl: '20px' }}>
{item.recommend_nodes

View File

@ -4,7 +4,7 @@ import { FC } from 'react';
import Item, { ItemProps } from './Item';
type SortableItemProps = ItemProps & {
refresh: () => void;
refresh?: () => void;
};
const SortableItem: FC<SortableItemProps> = ({ item, refresh, ...rest }) => {

View File

@ -1,3 +1,4 @@
import { DomainRecommendNodeListResp } from '@/request/types';
import {
closestCenter,
DndContext,
@ -9,7 +10,6 @@ import {
useSensor,
useSensors,
} from '@dnd-kit/core';
import { DomainRecommendNodeListResp } from '@/request/types';
import {
arrayMove,
rectSortingStrategy,
@ -23,7 +23,7 @@ import SortableItem from './SortableItem';
interface DragRecommendProps {
data: DomainRecommendNodeListResp[];
columns?: number;
refresh: () => void;
refresh?: () => void;
onChange: (data: DomainRecommendNodeListResp[]) => void;
}

View File

@ -4,9 +4,10 @@ import {
TreeItemComponentProps,
TreeItemWrapper,
} from '@/components/TreeDragSortable';
import RAG_SOURCES from '@/constant/rag';
import { treeSx } from '@/constant/styles';
import { postApiV1Node, putApiV1NodeDetail } from '@/request/Node';
import { ConstsNodeAccessPerm } from '@/request/types';
import { ConstsNodeAccessPerm, ConstsNodeRagInfoStatus } from '@/request/types';
import { useAppSelector } from '@/store';
import { AppContext, updateTree } from '@/utils/drag';
import { handleMultiSelect, updateAllParentStatus } from '@/utils/tree';
@ -19,6 +20,7 @@ import {
Stack,
TextField,
Theme,
Tooltip,
alpha,
styled,
} from '@mui/material';
@ -520,6 +522,20 @@ const TreeItem = React.forwardRef<
gap={1}
sx={{ flexShrink: 0, fontSize: 12 }}
>
{item.type === 2 &&
item.rag_status &&
![
ConstsNodeRagInfoStatus.NodeRagStatusBasicSucceeded,
ConstsNodeRagInfoStatus.NodeRagStatusEnhanceSucceeded,
].includes(item.rag_status) && (
<Tooltip title={item.rag_message}>
<StyledTag
color={RAG_SOURCES[item.rag_status].color as any}
>
{RAG_SOURCES[item.rag_status].name}
</StyledTag>
</Tooltip>
)}
{item.status === 1 && (
<StyledTag color='error'></StyledTag>
)}

View File

@ -0,0 +1,38 @@
import { ConstsNodeRagInfoStatus } from '@/request';
const RAG_SOURCES = {
[ConstsNodeRagInfoStatus.NodeRagStatusBasicPending]: {
name: '待学习',
color: 'warning',
},
[ConstsNodeRagInfoStatus.NodeRagStatusBasicRunning]: {
name: '正在学习',
color: 'warning',
},
[ConstsNodeRagInfoStatus.NodeRagStatusBasicFailed]: {
name: '学习失败',
color: 'error',
},
[ConstsNodeRagInfoStatus.NodeRagStatusBasicSucceeded]: {
name: '学习成功',
color: 'success',
},
[ConstsNodeRagInfoStatus.NodeRagStatusEnhancePending]: {
name: '等待增强学习',
color: 'warning',
},
[ConstsNodeRagInfoStatus.NodeRagStatusEnhanceRunning]: {
name: '正在增强学习',
color: 'warning',
},
[ConstsNodeRagInfoStatus.NodeRagStatusEnhanceFailed]: {
name: '增强学习失败',
color: 'error',
},
[ConstsNodeRagInfoStatus.NodeRagStatusEnhanceSucceeded]: {
name: '增强学习成功',
color: 'success',
},
};
export default RAG_SOURCES;

View File

@ -196,24 +196,10 @@ const History = () => {
curNode.creator_account || curNode.publisher_account ? (
<Stack>
{curNode.creator_account && (
<Stack
direction={'row'}
alignItems={'center'}
gap={0.5}
>
<Icon type='icon-chuangjian' />
{curNode.creator_account}
</Stack>
<Box>{curNode.creator_account}</Box>
)}
{curNode.publisher_account && (
<Stack
direction={'row'}
alignItems={'center'}
gap={0.5}
>
<Icon type='icon-fabu' />
{curNode.publisher_account}
</Stack>
<Box>{curNode.publisher_account}</Box>
)}
</Stack>
) : null

View File

@ -1,12 +1,12 @@
import { ITreeItem, NodeListFilterData } from '@/api';
import { getApiV1NodeList } from '@/request/Node';
import Nodata from '@/assets/images/nodata.png';
import DragTree from '@/components/Drag/DragTree';
import { getApiV1NodeList } from '@/request/Node';
import { useAppSelector } from '@/store';
import { convertToTree } from '@/utils/drag';
import { filterEmptyFolders } from '@/utils/tree';
import { Box, Skeleton, Stack } from '@mui/material';
import { Modal } from '@ctzhian/ui';
import { Box, Skeleton, Stack } from '@mui/material';
import { useCallback, useEffect, useState } from 'react';
interface AddRecommendContentProps {

View File

@ -1,11 +1,11 @@
import { DomainKnowledgeBaseDetail } from '@/request/types';
import { Box } from '@mui/material';
import CardRobotWebComponent from './CardRobot/WebComponent';
import CardRobotApi from './CardRobotApi';
import CardRobotDing from './CardRobotDing';
import CardRobotDiscord from './CardRobotDiscord';
import CardRobotFeishu from './CardRobotFeishu';
import CardRobotLark from './CardRobotLark';
import CardRobotWebComponent from './CardRobotWebComponent';
import CardRobotWechatOfficeAccount from './CardRobotWechatOfficeAccount';
import CardRobotWecom from './CardRobotWecom';
import CardRobotWecomAIBot from './CardRobotWecomAIBot';

View File

@ -0,0 +1,68 @@
import DragRecommend from '@/components/Drag/DragRecommend';
import {
DomainRecommendNodeListResp,
getApiV1NodeRecommendNodes,
} from '@/request';
import { useAppSelector } from '@/store';
import { Box, Button, Stack } from '@mui/material';
import { useEffect, useState } from 'react';
import AddRecommendContent from '../../AddRecommendContent';
const RecommendDocDragList = ({
ids,
onChange,
}: {
ids: string[];
onChange: (ids: string[]) => void;
}) => {
const { kb_id } = useAppSelector(state => state.config);
const [data, setData] = useState<DomainRecommendNodeListResp[]>([]);
const [open, setOpen] = useState(false);
const getDetail = (node_ids: string[]) => {
if (kb_id && node_ids.length > 0) {
getApiV1NodeRecommendNodes({
kb_id,
node_ids,
}).then(res => {
setData(res || []);
});
}
};
useEffect(() => {
getDetail(ids);
}, [ids, kb_id]);
return (
<Stack gap={1} flex={1}>
<Box>
<DragRecommend
data={data}
onChange={value => {
setData(value);
onChange(value.map(item => item.id!));
}}
/>
</Box>
<Button
color='primary'
size='small'
onClick={() => setOpen(true)}
sx={{
alignSelf: 'flex-start',
}}
>
</Button>
<AddRecommendContent
open={open}
selected={ids}
onChange={onChange}
onClose={() => setOpen(false)}
/>
</Stack>
);
};
export default RecommendDocDragList;

View File

@ -1,6 +1,14 @@
import { DomainKnowledgeBaseDetail } from '@/request/types';
import { FreeSoloAutocomplete } from '@/components/FreeSoloAutocomplete';
import ShowText from '@/components/ShowText';
import UploadFile from '@/components/UploadFile';
import { useCommitPendingInput } from '@/hooks';
import { getApiV1AppDetail, putApiV1App } from '@/request/App';
import {
DomainAppDetailResp,
DomainKnowledgeBaseDetail,
} from '@/request/types';
import { useAppSelector } from '@/store';
import { Icon, message } from '@ctzhian/ui';
import {
Box,
FormControlLabel,
@ -10,13 +18,10 @@ import {
Stack,
TextField,
} from '@mui/material';
import { Icon, message } from '@ctzhian/ui';
import { useEffect, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { FormItem, SettingCardItem } from './Common';
import { DomainAppDetailResp } from '@/request/types';
import { getApiV1AppDetail, putApiV1App } from '@/request/App';
import { useAppSelector } from '@/store';
import { FormItem, SettingCardItem } from '../../Common';
import RecommendDocDragList from './RecommendDocDragList';
interface CardRobotWebComponentProps {
kb: DomainKnowledgeBaseDetail;
@ -31,6 +36,8 @@ const CardRobotWebComponent = ({ kb }: CardRobotWebComponentProps) => {
control,
handleSubmit,
reset,
watch,
setValue,
formState: { errors },
} = useForm({
defaultValues: {
@ -38,11 +45,24 @@ const CardRobotWebComponent = ({ kb }: CardRobotWebComponentProps) => {
theme_mode: 'light',
btn_text: '',
btn_logo: '',
recommend_questions: [] as string[],
recommend_node_ids: [] as string[],
},
});
const [url, setUrl] = useState<string>('');
const recommend_questions = watch('recommend_questions') || [];
const recommend_node_ids = watch('recommend_node_ids') || [];
const recommendQuestionsField = useCommitPendingInput<string>({
value: recommend_questions,
setValue: value => {
setIsEdit(true);
setValue('recommend_questions', value);
},
});
useEffect(() => {
if (kb.access_settings?.base_url) {
setUrl(kb.access_settings.base_url);
@ -69,6 +89,10 @@ const CardRobotWebComponent = ({ kb }: CardRobotWebComponentProps) => {
theme_mode: res.settings?.widget_bot_settings?.theme_mode || 'light',
btn_text: res.settings?.widget_bot_settings?.btn_text || '在线客服',
btn_logo: res.settings?.widget_bot_settings?.btn_logo,
recommend_questions:
res.settings?.widget_bot_settings?.recommend_questions || [],
recommend_node_ids:
res.settings?.widget_bot_settings?.recommend_node_ids || [],
});
setIsEnabled(res.settings?.widget_bot_settings?.is_open ? true : false);
});
@ -86,6 +110,8 @@ const CardRobotWebComponent = ({ kb }: CardRobotWebComponentProps) => {
theme_mode: data.theme_mode as 'light' | 'dark',
btn_text: data.btn_text,
btn_logo: data.btn_logo,
recommend_questions: data.recommend_questions || [],
recommend_node_ids: data.recommend_node_ids || [],
},
},
},
@ -220,6 +246,21 @@ const CardRobotWebComponent = ({ kb }: CardRobotWebComponentProps) => {
)}
/>
</FormItem>
<FormItem label='推荐问题'>
<FreeSoloAutocomplete
{...recommendQuestionsField}
placeholder='回车确认,填写下一个推荐问题'
/>
</FormItem>
<FormItem label='推荐文档'>
<RecommendDocDragList
ids={recommend_node_ids}
onChange={(value: string[]) => {
setIsEdit(true);
setValue('recommend_node_ids', value);
}}
/>
</FormItem>
<FormItem label='嵌入代码'>
{url ? (
<ShowText

View File

@ -111,6 +111,8 @@ export function convertToTree(data: DomainNodeListItemResp[]) {
emoji: item.emoji,
content_type: item.content_type,
type: item.type!,
rag_status: item.rag_info?.status,
rag_message: item.rag_info?.message,
parentId: item.parent_id,
children: [],
canHaveChildren: item.type === 1,

View File

@ -219,9 +219,9 @@ const DocContent = ({
<>
<Box>·</Box>
<Box>
{info?.creator_account && info?.creator_account === 'admin'
{info?.editor_account && info?.editor_account === 'admin'
? '管理员'
: info?.creator_account}{' '}
: info?.editor_account}{' '}
{dayjs(info.updated_at).fromNow()}
</Box>
</>