知识库功能开发

main
jiangxucong 3 days ago
parent 327749afbd
commit 8004ee2e79

@ -19,6 +19,7 @@
"try": "试一下"
},
"back": "返回发现",
"collectSuccess": "收藏成功",
"category": {
"assistant": {
"collect": "收藏",

@ -42,7 +42,7 @@
"db:push-test": "NODE_ENV=test drizzle-kit push",
"db:studio": "drizzle-kit studio",
"db:z-pull": "drizzle-kit introspect",
"dev": "next dev -p 3212",
"dev": "next dev -p 3210",
"docs:i18n": "lobe-i18n md && npm run lint:md && npm run lint:mdx",
"docs:seo": "lobe-seo && npm run lint:mdx",
"i18n": "npm run workflow:i18n && lobe-i18n",

@ -86,18 +86,29 @@ const TopActions = memo<TopActionProps>(({ tab, isPinned }) => {
{/* <div className={value === '/robot' ? cx(styles.iconText, styles.iconSelectText) : cx(styles.iconText) }>数字人</div>*/}
{/* </Link>*/}
{/*)}*/}
{/*{enableKnowledgeBase && (
<Link aria-label={t('tab.files')} href={'/files'}>
<ActionIcon
active={tab === SidebarTabKey.Files}
icon={FolderClosed}
placement={'right'}
size="large"
title={t('tab.files')}
/>
</Link>
)}*/}
{showMarket && (
<Link aria-label={t('tab.plugins')} className={value === '/plugins' ? cx(styles.linkUrl, styles.linkclic) : cx(styles.linkUrl)} href={`/discover/plugins`} onClick={() => {setValue("/plugins")}}>
<Image alt={"plugins"} className={cx(styles.iconImg)} preview={false} src="/images/cj.png" />
<div className={value === '/plugins' ? cx(styles.iconText, styles.iconSelectText) : cx(styles.iconText) }></div>
</Link>
)}
{/*{showMarket && (*/}
{/* <Link aria-label={t('tab.knowledge')} className={value === '/knowledge' ? cx(styles.linkUrl, styles.linkclic) : cx(styles.linkUrl)} href={'/knowledge'} onClick={() => {setValue("/knowledge")}}>*/}
{/* <Image className={cx(styles.iconImg)} preview={false} src="/images/zsk.png" />*/}
{/* <div className={value === '/knowledge' ? cx(styles.iconText, styles.iconSelectText) : cx(styles.iconText) }>知识库</div>*/}
{/* </Link>*/}
{/*)}*/}
{showMarket && (
<Link aria-label={t('tab.knowledge')} className={value === '/knowledge' ? cx(styles.linkUrl, styles.linkclic) : cx(styles.linkUrl)} href={'/knowledge'} onClick={() => {setValue("/knowledge")}}>
<Image className={cx(styles.iconImg)} preview={false} src="/images/zsk.png" />
<div className={value === '/knowledge' ? cx(styles.iconText, styles.iconSelectText) : cx(styles.iconText) }></div>
</Link>
)}
</>
);
});

@ -12,7 +12,6 @@ import { Divider } from "antd";
import Avatar from './SideBar/Avatar';
import SideBar from './SideBar';
import { useUserStore } from '@/store/user';
const title:{[key: string]: string } = {
"/chat": '会话',
@ -70,13 +69,6 @@ const useStyles = createStyles(({ css }) => ({
`,
}))
const getCookie=(name)=> {
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
const CloudBanner = dynamic(() => import('@/features/AlertBanner/CloudBanner'));
const Layout = memo<PropsWithChildren>(({ children }) => {
@ -84,15 +76,7 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
const theme = useTheme();
const { styles, cx } = useStyles()
const { showCloudPromotion } = useServerConfigStore(featureFlagsSelectors);
// const pathName = window?.location?.pathname ?? '/welcome'
// const pathName = '/welcome'
const [openSignIn] = useUserStore((s) => [s.openLogin]);
useEffect(() => {
const cookieValue = getCookie('organizationLogo');
if(!cookieValue) {
openSignIn();
}
}, []);
//const pathName = window?.location?.pathname ?? '/welcome'
return (
<>
@ -107,21 +91,18 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
width={'100%'}
>
<SideBar />
<div style={{width: '100%'}}>
{/*<div style={{width: '100%'}}>*/}
{/* <div className={cx(styles.topCenten)}>*/}
{/* <div className={cx(styles.ledDiv)} style={{width: 'calc(100% - 60px)'}}>*/}
{/* <span className={cx(styles.leftTitle)} style={{marginLeft: '16px'}}>集智AI</span>*/}
{/* <Divider className={cx(styles.dividerCen)} type="vertical"/>*/}
{/* <span className={cx(styles.leftTitle)}>{title[pathName]}</span>*/}
{/* </div>*/}
{/* <div className={cx(styles.ledDiv)} style={{lineHeight: '60px', width: '60px', verticalAlign: 'middle'}}>*/}
{/* <Avatar/>*/}
{/* </div>*/}
{/* </div>*/}
{/*</div>*/}
{children}
</div>
<div style={{width: '100%',position: 'absolute'}}>
<div className={cx(styles.topCenten)}>
<div className={cx(styles.ledDiv)} style={{width: 'calc(100% - 60px)'}}>
<span className={cx(styles.leftTitle)} style={{marginLeft: '110px'}}>AI</span>
<Divider className={cx(styles.dividerCen)} type="vertical"/>
</div>
<div className={cx(styles.ledDiv)} style={{lineHeight: '60px', width: '60px', verticalAlign: 'middle'}}>
<Avatar/>
</div>
</div>
</div>
{children}
</Flexbox>
</>
);

@ -14,28 +14,27 @@ import Changelog from './features/ChangelogModal';
import TelemetryNotification from './features/TelemetryNotification';
export const generateMetadata = async (props: DynamicLayoutProps) => {
const locale = await RouteVariants.getLocale(props);
const { t } = await translation('metadata', locale);
return metadataModule.generate({
description: t('chat.description', { appName: BRANDING_NAME }),
title: t('chat.title', { appName: BRANDING_NAME }),
url: '/chat',
});
// const locale = await RouteVariants.getLocale(props);
// const { t } = await translation('metadata', locale);
// return metadataModule.generate({
// description: t('chat.description', { appName: BRANDING_NAME }),
// title: t('chat.title', { appName: BRANDING_NAME }),
// url: '/chat',
// });
};
const Page = async (props: DynamicLayoutProps) => {
const { hideDocs, showChangelog } = serverFeatureFlags();
const { isMobile, locale } = await RouteVariants.getVariantsFromProps(props);
const { t } = await translation('metadata', locale);
const ld = ldModule.generate({
description: t('chat.description', { appName: BRANDING_NAME }),
title: t('chat.title', { appName: BRANDING_NAME }),
url: '/chat',
});
// const { t } = await translation('metadata', locale);
// const ld = ldModule.generate({
// description: t('chat.description', { appName: BRANDING_NAME }),
// title: t('chat.title', { appName: BRANDING_NAME }),
// url: '/chat',
// });
return (
<>
<StructuredData ld={ld} />
<PageTitle />
<TelemetryNotification mobile={isMobile} />
{showChangelog && !hideDocs && !isMobile && (
@ -47,6 +46,6 @@ const Page = async (props: DynamicLayoutProps) => {
);
};
Page.displayName = 'Chat';
// Page.displayName = 'Chat';
export default Page;

@ -227,20 +227,13 @@ const AssistantCard = memo<AssistantCardProps>(
.slice(0, 4)
.filter(Boolean)
.map((tag: string, index) => {
// const url = qs.stringifyUrl({
// query: { q: tag, type: 'assistants' },
// url: '/discover/search',
// });
return (
<Tag key={index} style={{ margin: '0 5' }}>{startCase(tag).trim()}</Tag>
// <Link href={url} key={index}>
// <Link key={index}>
// <Tag style={{ margin: 0 }}>{startCase(tag).trim()}</Tag>
// </Link>
);
})
)}
</div>
{renderElement()}
</Flexbox>
</Flexbox>
</Flexbox>

@ -89,7 +89,6 @@ const getUserId = (s: { user?: { id?: any } }) => s.user?.id
const ModelCard = memo<ModelCardProps>(({ className,socialData, meta, identifier, style, href,...rest }) => {
const { title, description, functionCall, vision, contextWindowTokens, category, id, displayName, enabled } = meta;
const { conversations, likes, tokens: socialDataToken } = socialData;
// console.log(socialDataToken,"7373736262626----------------------------",meta)
const { createdAt, providers, suggestions, status, classify } = {...rest}
const { t } = useTranslation('models');
const { t:d } = useTranslation('discover');
@ -98,7 +97,6 @@ const ModelCard = memo<ModelCardProps>(({ className,socialData, meta, identifier
const userId = getUserId(useUserStore.getState())
const [val, setVal] = useState(status)
const handleCollect = (e) => {
// console.log(e,"8844848")
e.preventDefault()
e.stopPropagation()
const params = {
@ -123,7 +121,6 @@ const ModelCard = memo<ModelCardProps>(({ className,socialData, meta, identifier
method: "post",
url: "/flxai/api/robot/appaimodel",
}).then(response => {
// console.log(response,"222222222")
if (response.code === 0) {
message.success(d('collectSuccess'));
setVal("1")

@ -89,7 +89,6 @@ const List = memo<ListProps>(({ category, searchKeywords, items = [] }) => {
data={stData}
initialItemCount={24}
itemContent={(_, item) => (
// <Card href={urlJoin('/discover/model/', item.identifier)} showCategory {...item} />
<div onClick={() => handleClickCard(item)} key={item.identifier}>
<Card key={item.identifier} showCategory {...item} />
</div>
@ -109,12 +108,9 @@ const List = memo<ListProps>(({ category, searchKeywords, items = [] }) => {
<Title tag={stData.length}></Title>
<Grid maxItemWidth={280} rows={4}>
{stData.map((item) => (
// <Link href={urlJoin('/discover/assistant/', item.identifier)} key={item.identifier}>
// <Card style={{boxShadow: "1px 0px 12px 0px rgba(42, 77, 255, 0.19)"}} key={item.identifier} onClick={() => handleClickCard(item)} showCategory={!category} {...item} />
<div onClick={() => handleClickCard(item)} key={item.identifier}>
<Card key={item.identifier} showCategory={!category} {...item} />
</div>
// </Link>
))}
</Grid>
</>
@ -125,12 +121,9 @@ const List = memo<ListProps>(({ category, searchKeywords, items = [] }) => {
data={stData}
initialItemCount={24}
itemContent={(_, item) => (
// <Link href={urlJoin('/discover/model/', item.identifier)} key={item.identifier}>
// <Card style={{boxShadow: "1px 0px 12px 0px rgba(42, 77, 255, 0.19)"}} onClick={() => handleClickCard(item)} key={item.identifier} showCategory={!category} {...item} />
<div onClick={() => handleClickCard(item)} key={item.identifier}>
<Card key={item.identifier} showCategory={!category} {...item} />
</div>
// </Link>
)}
style={{
minHeight: '75vh',

@ -117,7 +117,6 @@ const PluginCard = memo<PluginCardProps>(
method: "post",
url: "/flxai/api/robot/appaiplugin",
}).then(response => {
// console.log(response,"222222222")
if (response.code === 0) {
message.success(t('collectSuccess'));
setVal("1")
@ -127,9 +126,7 @@ const PluginCard = memo<PluginCardProps>(
})
}
const renderElement = () => {
// console.log(classify,'66666----------------------')
if (classify !== 'collect') {
// console.log(status,"222222222--------------------------")
if(status === "1" || val === "1") {
return <div className={styles.collectBtn} onClick={(e) => {e.stopPropagation()}}><StarOutlined style={{color: '#FFAD01'}}/></div>
} else {
@ -194,10 +191,6 @@ const PluginCard = memo<PluginCardProps>(
.slice(0, 4)
.filter(Boolean)
.map((tag: string, index) => {
// const url = qs.stringifyUrl({
// query: { q: tag, type: 'plugins' },
// url: '/discover/search',
// });
return (
<Tag key={index} style={{ margin: '0 5' }}>{startCase(tag).trim()}</Tag>
);

@ -9,6 +9,6 @@ import { FilesTabs } from '@/types/files';
export default () => {
const { t } = useTranslation('file');
const [category] = useFileCategory();
return <FileManager category={category} title={t(`tab.${category as FilesTabs}`)} />;
};

@ -28,13 +28,13 @@ const Header = memo<HeaderProps>(({ filename, url }) => {
justify={'space-between'}
paddingBlock={12}
paddingInline={12}
style={{ borderBottom: `1px solid ${theme.colorSplit}` }}
style={{ borderBottom: `1px solid ${theme.colorSplit}`}}
>
<Flexbox align={'baseline'} horizontal>
<Button
icon={<Icon icon={ArrowLeftIcon} />}
onClick={() => {
router.push('/files');
router.push('/knowledge');
}}
size={'small'}
type={'text'}

@ -24,7 +24,7 @@ const FilePage = async (props: PagePropsWithId) => {
if (!file) return notFound();
return (
<Flexbox horizontal width={'100%'}>
<Flexbox horizontal width={'100%'} style={{marginTop: 60}}>
<Flexbox flex={1}>
<Flexbox height={'100%'}>
<Header filename={file.name} id={params.id} url={file.url} />

@ -1,20 +1,33 @@
'use client';
// import { Avatar, Icon } from '@lobehub/ui';
import { Card, Button, List, Image, Flex, Tabs, Table, Space, Upload, Input, Select, App, Popconfirm } from "antd";
import { Card, Button, List, Image, Flex, Tabs, Table, Space, Upload, Input, Select, App, Popconfirm, Empty,Col, Row } from "antd";
import { CloudUploadOutlined} from '@ant-design/icons';
import { createStyles } from 'antd-style';
import { memo, useState } from 'react';
// import { useTranslation } from 'react-i18next';
import { memo, useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
// import { Flexbox } from 'react-layout-kit';
import KnowledgeCreateForm from './KnowledgeCreateForm'; //新增表单
import KnowledgeUpdateForm from './KnowledgeUpdateForm'; //编辑表单
// import request from '@/app/api/request';
import { Virtuoso } from 'react-virtuoso';
import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
import DragUpload from '@/components/DragUpload';
import { useFileStore } from '@/store/file';
import UploadDock from '@/features/FileManager/UploadDock';
import { useQueryState } from 'nuqs';
import { formatSize } from '@/utils/format';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { SortType } from '@/types/files';
import { useRouter } from 'next/navigation';
import { lambdaQuery } from '@/libs/trpc/client';
import SkeletonLoading from './Loading';
import isEqual from 'fast-deep-equal';
const { Search } = Input;
const { Dragger } = Upload;
const { TextArea } = Input;
dayjs.extend(relativeTime);
const useStyles = createStyles(({ css }) => ({
buttonDelete: css`
background: url(/images/delete.png) center center no-repeat;
@ -122,6 +135,20 @@ const useStyles = createStyles(({ css }) => ({
border: 1px solid #0044FF;
cursor: pointer;
`,
documentCardCheck: css`
min-height: 200px;
margin-bottom: 10px;
padding: 20px !important;
border-radius: 16px;
opacity: 1;
background: rgba(0, 68, 255, 0.05);
box-sizing: border-box;
border: 1px solid #0044FF;
`,
checkContent: css`
height: calc(100% - 30px) !important;
`,
formStyle: css`
.ant-form-item-label {
width: 100px !important;
@ -135,10 +162,12 @@ const useStyles = createStyles(({ css }) => ({
font-size: 16px;
color: #0044FF;
border: none;
padding: 18px 30px !important;
padding: 5px 20px;
box-sizing: border-box;
background-color: rgba(0, 68, 255, 0.102);
margin-left: 50px;
cursor: pointer;
border-radius: 20px;
&:hover {
color: #0044FF !important;
background-color: rgba(0, 68, 255, 0.102) !important;
@ -243,11 +272,9 @@ const useStyles = createStyles(({ css }) => ({
`,
testContent: css`
width: 100%;
height: 500px;
height: 500px !important;
margin-top: 15px;
padding: 10px;
border-radius: 2px;
border: 1px solid #D9D9D9;
`,
title: css`
font-size: 18px;
@ -270,46 +297,65 @@ const useStyles = createStyles(({ css }) => ({
const KnowledgeList = memo<{ mobile?: boolean }>(() => {
const { cx, styles } = useStyles();
const { t } = useTranslation(['components', 'common']);
const router = useRouter();
const { message, modal } = App.useApp();
const [value, setValue] = useState(1)
const [tabValue, setTabValue] = useState(1)
const [tabsVal, setTabsVal] = useState(1)
const [tabsVal, setTabsVal] = useState("1")
const [cardValue, setCardValue] = useState(1)
const [updateFormValues, setUpdateFormValues] = useState({})
// const [form] = Form.useForm();
const { message } = App.useApp();
const createNewKnowledgeBase = useKnowledgeBaseStore((s) => s.createNewKnowledgeBase);
// const [formValues, setFormValues] = useState();
const [knowledgeBaseId, setKnowledgeBaseId] = useState('')
const [modalVisible, setModalVisible] = useState(false);
const [updateModalVisible, setUpdateModalVisible] = useState(false);
// const createForm = (values) => {
// console.log("44444")
// // setFormValues(values);
// setOpen(false);
// };
// const handleOk = () => {
// form.validateFields().then(values => {
// console.log("88888",values)
// // setFormValues(values);
// setOpen(false);
// }).catch(info => {
// console.error('Validation failed', info);
// });
// };
// const { t } = useTranslation('common');
const data = [
{
title: '',
},
{
title: '',
},
{
title: '',
},
{
title: '',
},
];
const [docFileData, setDocFileData] = useState([]);
const [questionFileData, setQuestionFileData] = useState([]);
const [queryData, setQueryData] = useState('');
const [testContent, setTestContent] = useState('');
const [useFetchKnowledgeBaseList, createNewKnowledgeBase, updateKnowledgeBase, removeKnowledgeBase] = useKnowledgeBaseStore(
(s) => [
s.useFetchKnowledgeBaseList,
s.createNewKnowledgeBase,
s.updateKnowledgeBase,
s.removeKnowledgeBase,
],
);
const { data, isLoading } = useFetchKnowledgeBaseList();
const [viewConfig, setViewConfig] = useState({ showFilesInKnowledgeBase: false });
const [sorter] = useQueryState('sorter', {
clearOnDefault: true,
defaultValue: 'createdAt',
});
const [sortType] = useQueryState('sortType', {
clearOnDefault: true,
defaultValue: SortType.Desc,
});
const [useFetchFileManage, pushDockFileList, removeFile, semanticSearch, parseFiles] = useFileStore((s) => [
s.useFetchFileManage,
s.pushDockFileList,
s.removeFileItem,
s.semanticSearch,
s.parseFilesToChunks,
]);
const isSimilaritySearching = useFileStore((s) => s.isSimilaritySearching);
const dataSource = useFileStore((s) => s.similaritySearchChunks, isEqual);
// useEffect(() => {
// }, []); // 空数组[]意味着仅在组件挂载时调用一次
console.log(knowledgeBaseId,sortType,sorter,'--------44444444444444444------------')
const { data: dataAllFile } = useFetchFileManage({
// category,
knowledgeBaseId,
q: queryData,
sortType,
sorter,
...viewConfig,
});
console.log(data,'999999999999999999')
console.log(dataAllFile,'8888888888888888888888')
const columnsAll = [
{
@ -317,200 +363,102 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
key: 'index',
render: (text, record, index) => `${index + 1}`,
title: '序号',
width: 80,
},
{
dataIndex: 'filename',
key: 'filename',
dataIndex: 'name',
key: 'name',
title: '文件名',
// width: 150,
render: (_,record) => (
<Space size={50}>
{record.name?.match(/(.*)\.(.*)/)[1]}
</Space>
),
},
{
dataIndex: 'filetype',
key: 'filetype',
dataIndex: 'fileType',
key: 'fileType',
title: '文件类型',
width: 200,
render: (_,record) => (
<Space size={50}>
{record.name?.match(/(.*)\.(.*)/)[2]}
</Space>
),
},
{
dataIndex: 'filesize',
key: 'filesize',
dataIndex: 'size',
key: 'size',
title: '文件大小(KB)',
},
{
dataIndex: 'tokens',
key: 'tokens',
title: 'tokens',
},
{
dataIndex: 'num',
key: 'num',
title: '知识库条数',
},
{
dataIndex: 'status',
key: 'status',
title: '状态',
},
{
dataIndex: 'updatetime',
key: 'updatetime',
title: '更新时间',
},
{
align: 'center',
key: 'action',
render: () => (
width: 200,
render: (_,record) => (
<Space size={50}>
<a style={{color: '#0044FF'}}></a>
<a style={{color: '#26D308'}}></a>
<a style={{color: '#FF0C0C'}}></a>
{formatSize(record.size)}
</Space>
),
title: '操作',
width: 300,
},
];
const columnsDocument = [
// {
// dataIndex: 'tokens',
// key: 'tokens',
// title: 'tokens',
// },
// {
// dataIndex: 'num',
// key: 'num',
// title: '知识库条数',
// },
// {
// dataIndex: 'status',
// key: 'status',
// title: '状态',
// },
{
dataIndex: 'index',
key: 'index',
render: (text, record, index) => `${index + 1}`,
title: '序号',
},
{
dataIndex: 'content',
key: 'content',
title: '内容',
},
{
align: 'center',
key: 'action',
render: () => (
dataIndex: 'createdAt',
key: 'createdAt',
title: '创建时间',
width: 200,
render: (_,record) => (
<Space size={50}>
<a style={{color: '#0044FF'}}></a>
<a style={{color: '#26D308'}}></a>
<a style={{color: '#FF0C0C'}}></a>
{dayjs().diff(dayjs(record.createdAt), 'd') < 7
? dayjs(record.createdAt).fromNow()
: dayjs(record.createdAt).format('YYYY-MM-DD')}
</Space>
),
title: '操作',
width: 300,
},
];
const columnsQuestion = [
{
dataIndex: 'index',
key: 'index',
render: (text, record, index) => `${index + 1}`,
title: '序号',
},
{
dataIndex: 'question',
key: 'question',
title: '问题',
},
{
dataIndex: 'answer',
key: 'answer',
title: '回答',
},
{
align: 'center',
key: 'action',
render: () => (
width: 200,
render: (_,record) => (
<Space size={50}>
<a style={{color: '#0044FF'}}></a>
<a style={{color: '#26D308'}}></a>
<a style={{color: '#FF0C0C'}}></a>
<a style={{color: '#0044FF'}} onClick={() => {
router.push(`/files/${record.id}`);
}}></a>
<a style={{color: '#FF0C0C'}} onClick={() => handleDeleteFile(record)}></a>
</Space>
),
title: '操作',
width: 300,
},
];
const tableDataAll = [
{
filename: '测试文件',
filesize: '1',
filetype: '问答',
key: '1',
num: '2',
status: '生效',
tokens: '64',
updatetime: '2024-10-31 12:12:12'
},
{
filename: '测试文件',
filesize: '1',
filetype: '问答',
key: '1',
num: '2',
status: '生效',
tokens: '64',
updatetime: '2024-10-31 12:12:12'
},
{
filename: '测试文件',
filesize: '1',
filetype: '问答',
key: '1',
num: '2',
status: '生效',
tokens: '64',
updatetime: '2024-10-31 12:12:12'
},
];
const tableDataDocument = [
{
content: '这是一段描述,这是一段描述这是一段描述,这是一段描述,这是一段描述,这是一段描述,',
key: '1',
},
{
content: '这是一段描述,这是一段描述这是一段描述,这是一段描述,这是一段描述,这是一段描述,',
key: '2',
},
];
const tableDataQuestion = [
{
answer: '这是回答内容',
key: '1',
question: '这是问题内容',
},
{
answer: '这是回答内容',
key: '2',
question: '这是问题内容',
},
];
const handleChange = (e) => {
console.log(e.target.value,'--9999222222----')
setTestContent(e.target.value)
}
// const handleChange = () => {
// }
const handleAdd = fields => {
createNewKnowledgeBase({
"description": fields.description,
"name": fields.name,
});
// const params = {
// "0": {
// "json": {
// "name": fields.name,
// "description": fields.description
// }
// }
// }
// request({
// url: "/trpc/lambda/knowledgeBase.createKnowledgeBase?batch=1",
// data: params,
// method: "post",
// // headers: {
// // 'Cookie': 'your-cookie-value'
// // },
// }).then(response => {
// console.log(response,"222222222")
// }).catch(error => {
// console.error('Error fetching data:', error);
// })
message.success('添加成功');
const handleCheck = () => {
if(testContent != "") semanticSearch(testContent, "");
console.log(testContent,'--635353535355----')
}
const handleAdd = async(fields) => {
await createNewKnowledgeBase({
description: fields.description,
name: fields.name,
});
// message.success('添加成功');
setModalVisible(false);
};
@ -518,10 +466,9 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
setModalVisible(!!flag);
};
const handleUpdate = () => {
// const { updateFormValues } = this.state;
message.success('修改成功');
const handleUpdate = async(fields) => {
await updateKnowledgeBase(fields.id, { name: fields.name, description: fields.description});
// message.success('修改成功');
setUpdateModalVisible(false);
};
@ -531,129 +478,174 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
setUpdateFormValues(record || {});
};
const handleDeleteRecord = () => {
const handleDeleteRecord = async(fields) => {
modal.confirm({
content: '知识库删除后将不可恢复,请谨慎操作',
okButtonProps: { danger: true },
onOk: async () => {
await removeKnowledgeBase(fields.id);
// message.success('删除成功');
},
});
};
message.success('删除成功');
const handleDeleteFile = async(fields) => {
modal.confirm({
content: t('FileManager.actions.confirmDelete'),
okButtonProps: { danger: true },
onOk: async () => {
await removeFile(fields.id);
// message.success('删除成功');
},
});
};
const onSearch = (val) => {
setQueryData(val)
setTabsVal("1")
console.log(val,'333333-------------------')
}
const handleTabChange = async(val) => {
console.log(val,"5555")
setTabsVal(val)
if(val == 2) {
await setDocFileData(dataAllFile?.filter((item)=> {
return item.fileType != 'text/csv'
}))
console.log(docFileData,'182828282828288')
} else if(val == 3) {
setQuestionFileData(dataAllFile?.filter((item)=> {
return item.fileType == 'text/csv'
}))
}
}
const handleSwitch = (val,item) => {
setValue(val)
setTabValue(1)
setKnowledgeBaseId(item.id)
}
const handleSelectTab = (val) => {
console.log(val)
setTabValue(val)
}
const handleSelectCard = (val) => {
console.log(val,"5555")
setCardValue(val)
}
const parentMethods = {
handleAdd: handleAdd,
handleModalVisible: handleModalVisible,
};
const updateMethods = {
handleUpdate: handleUpdate,
handleUpdateModalVisible: handleUpdateModalVisible,
};
const items = [
{
children: <Table className={styles.tableHead} columns={columnsAll} dataSource={tableDataAll}/>,
children: <Table className={styles.tableHead} columns={columnsAll} dataSource={dataAllFile} scroll={{
x: 'max-content',
y: 380,
}}/>,
key: '1',
label: '全部',
},
{
children: <>
<Select
className={styles.selectStyle}
defaultValue="集智AI产品架构设计.pdf"
// onChange={handleChange}
options={[
{
label: '集智AI产品架构设计.pdf',
value: '集智AI产品架构设计.pdf',
},
]}
style={{
width: 200,
}}
/>
<Table className={styles.tableHead} columns={columnsDocument} dataSource={tableDataDocument}/>
<Table className={styles.tableHead} columns={columnsAll} dataSource={docFileData} scroll={{
x: 'max-content',
y: 380,
}}/>
</>,
key: '2',
label: '文档',
},
{
children: <>
<Select
className={styles.selectStyle}
defaultValue="template.csv"
// onChange={handleChange}
options={[
{
label: 'template.csv',
value: 'template.csv',
},
]}
style={{
width: 200,
}}
/>
<Table className={styles.tableHead} columns={columnsQuestion} dataSource={tableDataQuestion}/>
<Table className={styles.tableHead} columns={columnsAll} dataSource={questionFileData} scroll={{
x: 'max-content',
y: 380,
}}/>
</>,
key: '3',
label: '问答',
},
];
// const onSearch = () => {
// }
const handleTabChange = (val) => {
console.log(val,"5555")
setTabsVal(val)
}
const operations = <>
<Search
allowClear
className={styles.searchStyle}
// onSearch={onSearch}
onSearch={onSearch}
placeholder="请输入"
style={{
width: 200,
}}
/>
{
tabsVal === 1?(
<Button className={styles.importButtonStyle} onClick={null} shape="round" size="small" type="text" variant="solid">
</Button>
tabsVal == 1?(
<Upload
beforeUpload={async (file) => {
let fileType = 'txt,pdf,md,docx';
if(cardValue == 1) {
fileType = 'txt,pdf,md,docx';
} else {
fileType = 'csv';
}
console.log(file,"77777777777777777")
const isJpgOrPng = fileType.includes(file.name?.match(/(.*)\.(.*)/)[2])
if (!isJpgOrPng) {
message.error(`你只能上传${fileType}文件!`);
return false;
}
const isLt2M = file.size / 1024 / 1024 < 15;
if (!isLt2M) {
message.error('文件大小不能超过 15MB!');
return false;
}
await pushDockFileList([file], knowledgeBaseId)
return false;
}}
multiple={true}
showUploadList={false}
>
<div className={styles.importButtonStyle}></div>
</Upload>
):null
}
</>;
const handleSwitch = (val) => {
setValue(val)
}
const handleSelectTab = (val) => {
console.log(val)
setTabValue(val)
}
const handleSelectCard = (val) => {
console.log(val,"5555")
setCardValue(val)
}
const parentMethods = {
handleAdd: handleAdd,
handleModalVisible: handleModalVisible,
};
const updateMethods = {
handleUpdate: handleUpdate,
handleUpdateModalVisible: handleUpdateModalVisible,
};
const renderItem = () => {
const props = {
action: '#',
beforeUpload(file) {
console.log(file,"77777777777777777")
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
async beforeUpload(file) {
let fileType = 'txt,pdf,md,docx';
if(cardValue == 1) {
fileType = 'txt,pdf,md,docx';
} else {
fileType = 'csv';
}
const isJpgOrPng = fileType.includes(file.name?.match(/(.*)\.(.*)/)[2])
if (!isJpgOrPng) {
message.error('你只能上传JPG/PNG文件!');
message.error(`你只能上传${fileType}文件!`);
return false;
}
const isLt2M = file.size / 1024 / 1024 < 2;
const isLt2M = file.size / 1024 / 1024 < 15;
if (!isLt2M) {
message.error('文件大小不能超过 2MB!');
message.error('文件大小不能超过 15MB!');
return false;
}
return isJpgOrPng && isLt2M;
await pushDockFileList([file], knowledgeBaseId);
return Upload.LIST_IGNORE;
},
multiple: true,
name: 'file',
@ -675,15 +667,15 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
};
if(tabValue === 1) {
return (
<Card bordered={false}>
<Tabs className={styles.tabStyle} items={items} onChange={handleTabChange} tabBarExtraContent={operations} />
<Card variant="borderless">
<Tabs className={styles.tabStyle} activeKey={tabsVal} items={items} onChange={handleTabChange} tabBarExtraContent={operations} />
</Card>
)
} else if(tabValue === 2) {
return (
<div style={{height: '100%'}}>
<Flex gap={30} justify='space-between' style={{height: '100%'}}>
<Card bordered={false} style={{height: '85%', padding: '0 30px', width: '100%'}}>
<Card variant="borderless" style={{height: '85%', padding: '0 30px', width: '100%'}}>
<Flex gap={50}>
<div className={`${cardValue === 1 ? styles.documentCardActive : styles.documentCard}`} onClick={()=>handleSelectCard(1)}>
<Flex>
@ -710,7 +702,7 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
cardValue === 1 ? '支持txt、pdf、md、docx格式文件单次最多上传10个文件。单个文件大小不超过15M.':(<>
<div>CSV()1015M</div>
<div>
(CSV UTF-8)
(CSV UTF-8)
<span style={{color: '#0044FF',cursor: 'pointer',marginLeft: '20px'}}>CSV</span>
</div>
</>)
@ -719,131 +711,51 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
<Dragger {...props}>
<p style={{margin: '50px 0'}}>
<CloudUploadOutlined style={{fontSize: '20px',marginRight: '15px'}}/>
<span style={{fontSize: '16px'}}></span>
<span style={{fontSize: '16px'}}></span>
</p>
</Dragger>
</Card>
<Card bordered={false} className={styles.importFileCard} style={{height: '85%', width: '100%'}}>
<div className={styles.previewContainer}>
<Image preview={false} src="/images/empty.png" />
<div style={{paddingLeft: '30px'}}></div>
</div>
</Card>
</Flex>
</div>
)
} else {
console.log(dataSource,'检索测试91919191991199--------------', isSimilaritySearching)
return (
<div style={{height: '100%'}}>
<Flex gap={30} justify='space-between'style={{height: '100%'}}>
<Card bordered={false} style={{height: '85%', width: '20%'}}>
<Card variant="borderless" style={{height: '85%', width: '20%'}}>
<Flex justify='space-between'>
<div style={{color: '#333333',fontSize: '18px',fontWeight: '500'}}></div>
<Button className={styles.checkButtonStyle} onClick={null} shape="round" size="small" type="text" variant="solid">
<Button className={styles.checkButtonStyle} onClick={handleCheck} shape="round" size="small" type="text" variant="solid">
</Button>
</Flex>
<div className={styles.testContent}></div>
</Card>
<Card bordered={false} className={styles.importFileCard} style={{height: '85%',width: '80%'}}>
<div style={{color: '#333333',fontSize: '18px',fontWeight: '500',marginBottom: '15px'}}></div>
<Flex gap={20} justify='space-between'style={{marginBottom: '20px'}}>
<div className={styles.documentCardActive} style={{ padding: '20px' }}>
<Flex justify='space-between'>
<div>
<span className={styles.checkTitle}>:</span>
<span>0.9212</span>
</div>
<Button className={styles.viewButtonStyle} shape="round" size="small" variant="solid">
</Button>
</Flex>
<div className={styles.checkItem}>
<span className={styles.checkTitle}>:</span>
</div>
<div>
<span>LinkAI</span>
</div>
<div className={styles.checkItem}>
<span className={styles.checkTitle}>:</span>
</div>
<div>
<span>LinkAIAI:https://link-ai.tech/</span>
</div>
</div>
<div className={styles.documentCardActive} style={{ padding: '20px' }}>
<Flex justify='space-between'>
<div>
<span className={styles.checkTitle}>:</span>
<span>0.9212</span>
</div>
<Button className={styles.viewButtonStyle} shape="round" size="small" variant="solid">
</Button>
</Flex>
<div className={styles.checkItem}>
<span className={styles.checkTitle}>:</span>
</div>
<div>
<span>LinkAI</span>
</div>
<div className={styles.checkItem}>
<span className={styles.checkTitle}>:</span>
</div>
<div>
<span>LinkAIAI:https://link-ai.tech/</span>
</div>
</div>
</Flex>
<Flex gap={20} justify='space-between'style={{marginBottom: '20px'}}>
<div className={styles.documentCardActive} style={{padding: '20px'}}>
<Flex justify='space-between'>
<div>
<span className={styles.checkTitle}>:</span>
<span>0.9212</span>
</div>
<Button className={styles.viewButtonStyle} shape="round" size="small" variant="solid">
</Button>
</Flex>
<div className={styles.checkItem}>
<span className={styles.checkTitle}>:</span>
</div>
<div>
<span>LinkAI</span>
</div>
<div className={styles.checkItem}>
<span className={styles.checkTitle}>:</span>
</div>
<div>
<span>LinkAIAI:https://link-ai.tech/</span>
</div>
</div>
<div className={styles.documentCardActive} style={{padding: '20px'}}>
<Flex justify='space-between'>
<div>
<span className={styles.checkTitle}>:</span>
<span>0.9212</span>
</div>
<Button className={styles.viewButtonStyle} shape="round" size="small" variant="solid">
</Button>
</Flex>
<div className={styles.checkItem}>
<span className={styles.checkTitle}>:</span>
</div>
<div>
<span>LinkAI</span>
</div>
<div className={styles.checkItem}>
<span className={styles.checkTitle}>:</span>
</div>
<div>
<span>LinkAIAI:https://link-ai.tech/</span>
</div>
</div>
</Flex>
<div>
<TextArea className={styles.testContent} onChange={handleChange} rows={4} placeholder="请输入测试文本" />
</div>
</Card>
{isSimilaritySearching ? (
<Card variant="borderless" className={styles.importFileCard} style={{height: '85%',width: '80%'}}>
<SkeletonLoading />
</Card>
):(
<Card variant="borderless" className={styles.importFileCard} style={{height: '85%',width: '80%'}}>
<div style={{color: '#333333',fontSize: '18px',fontWeight: '500',marginBottom: '15px'}}></div>
{dataSource && dataSource.length > 0?(
<Virtuoso
data={dataSource}
className={styles.checkContent}
itemContent={(index, item) => (
<div key={item.id} className={styles.documentCardCheck}>
<span>{item.text}</span>
</div>
)}
/>
):(
<Empty style={{marginTop: '120px'}}/>
)}
</Card>
)}
</Flex>
</div>
)
@ -851,7 +763,7 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
}
return (
<>
<div style={{width: '100%',marginTop: '60px'}}>
<div style={{display: value === 1 ? 'block': 'none'}}>
<div style={{ fontSize: '20px', height: '70px', lineHeight: '70px', marginLeft: '60px', }}>
@ -869,7 +781,9 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
) : null}
<List
dataSource={data}
renderItem={(item) => (
loading={isLoading}
style={{height: 'calc(100vh - 150px)',overflow: 'auto'}}
renderItem={(item) =>
<List.Item className={styles.listStyle}>
<Card className={styles.cardStyle}>
<Flex justify='space-between'>
@ -877,13 +791,13 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
<Flex>
<Image className={cx(styles.iconImg)} preview={false} src="/images/zsk1.png" />
<div style={{marginLeft: '20px'}}>
<div className={styles.title}><span style={{color: '#0E46FF',fontSize: '14px',fontWeight: 'normal',marginLeft: '20px'}}>ID: 23039282</span></div>
<div>: xxxxxxxxxxxxxxxxxxxxxxxx</div>
<div className={styles.title}>{item.name}<span style={{color: '#0E46FF',fontSize: '14px',fontWeight: 'normal',marginLeft: '20px'}}>ID: {item.id}</span></div>
<div>: {item.description}</div>
</div>
</Flex>
</div>
<div style={{lineHeight: '53px'}}>
<Button onClick={()=>handleSwitch(2)} style={{color: '#902EFF'}} type="link">
<Button onClick={()=>handleSwitch(2,item)} style={{color: '#902EFF'}} type="link">
<span className={styles.buttonManage}></span>
</Button>
@ -891,17 +805,15 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
<span className={styles.buttonEdit}></span>
</Button>
<Popconfirm cancelText="取消" okText="确定" onConfirm={() => handleDeleteRecord(item)} title="你确定要删除吗?" >
<Button style={{color: '#FF0C0C'}} type="link">
<span className={styles.buttonDelete}></span>
</Button>
</Popconfirm>
<Button onClick={() => handleDeleteRecord(item)} style={{color: '#FF0C0C'}} type="link">
<span className={styles.buttonDelete}></span>
</Button>
</div>
</Flex>
</Card>
</List.Item>
)}
}
/>
</div>
<div style={{display: value === 2 ? 'block': 'none',height: '100%',margin: '20px'}}>
@ -921,11 +833,12 @@ const KnowledgeList = memo<{ mobile?: boolean }>(() => {
</Button>
</Flex>
<UploadDock />
<div style={{height: '100%', marginTop: '20px'}}>
{renderItem()}
</div>
</div>
</>
</div>
);
});

@ -46,7 +46,7 @@ const KnowledgeUpdateForm = (props) => {
form.setFieldsValue({
description: props.values.description,
id: props.values.id,
title: props.values.title,
name: props.values.name,
});
}, []);
@ -63,7 +63,7 @@ const KnowledgeUpdateForm = (props) => {
<Form className={styles.formStyle} form={form}>
<Row gutter={{ lg: 24, md: 8, xl: 48 }}>
<Col md={24} sm={24}>
<FormItem label="知识库名称" name="title" rules={[{ message: '请输入!', min: 1, required: true }]}>
<FormItem label="知识库名称" name="name" rules={[{ message: '请输入!', min: 1, required: true }]}>
<Input placeholder="请输入" />
</FormItem>
</Col>

@ -0,0 +1,16 @@
import { Skeleton } from 'antd';
import { memo } from 'react';
import { Flexbox } from 'react-layout-kit';
const SkeletonLoading = memo(() => (
<Flexbox padding={12}>
<Skeleton active paragraph={{ width: '70%' }} title={false} />
<Skeleton active paragraph={{ width: '40%' }} title={false} />
<Skeleton active paragraph={{ width: '80%' }} title={false} />
<Skeleton active paragraph={{ width: '30%' }} title={false} />
<Skeleton active paragraph={{ width: '50%' }} title={false} />
<Skeleton active paragraph={{ width: '70%' }} title={false} />
</Flexbox>
));
export default SkeletonLoading;

@ -0,0 +1,35 @@
import { Input } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { MAX_GREETING_LENGTH } from '@/constants/common';
import { agentSelectors, useAgentStore } from '@/store/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const { t } = useTranslation('role');
const [greeting, updateAgentConfig] = useAgentStore((s) => [
agentSelectors.currentAgentGreeting(s),
s.updateAgentConfig,
]);
return (
<Input.TextArea
className={className}
style={style}
value={greeting}
autoSize={{ minRows: 2, maxRows: 4 }}
placeholder={t('role.greetTip')}
showCount
maxLength={MAX_GREETING_LENGTH}
onChange={(e) => {
updateAgentConfig({ greeting: e.target.value });
}}
/>
);
});

@ -0,0 +1,78 @@
import { Upload } from 'antd';
import { createStyles } from 'antd-style';
import NextImage from 'next/image';
import React, { CSSProperties, memo, useCallback } from 'react';
import {
AVATAR_COMPRESS_SIZE,
AVATAR_IMAGE_SIZE,
COVER_COMPRESS_SIZE,
DEFAULT_AGENT_AVATAR_URL,
} from '@/constants/common';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { createUploadImageHandler } from '@/utils/common';
import { imageToBase64 } from '@/utils/imageToBase64';
const useStyle = createStyles(
({ css, token }) => css`
cursor: pointer;
overflow: hidden;
border-radius: 50%;
transition:
scale 400ms ${token.motionEaseOut},
box-shadow 100ms ${token.motionEaseOut};
&:hover {
box-shadow: 0 0 0 3px ${token.colorText};
}
&:active {
scale: 0.8;
}
`,
);
interface AvatarWithUploadProps {
id?: string;
size?: number;
style?: CSSProperties;
}
export default memo<AvatarWithUploadProps>(({ size = AVATAR_IMAGE_SIZE, style, id }) => {
const { styles } = useStyle();
const [avatar, updateAgentMeta] = useAgentStore((s) => [
agentSelectors.currentAgentMeta(s)?.avatar,
s.updateAgentMeta,
]);
const handleUploadAvatar = useCallback(
createUploadImageHandler((avatar) => {
const img = new Image();
img.src = avatar;
img.addEventListener('load', () => {
const avatar = imageToBase64({ img, size: AVATAR_COMPRESS_SIZE });
const cover = imageToBase64({ img, size: COVER_COMPRESS_SIZE });
updateAgentMeta({ avatar, cover });
});
}),
[],
);
return (
<div
className={styles}
id={id}
style={{ maxHeight: AVATAR_IMAGE_SIZE, maxWidth: AVATAR_IMAGE_SIZE, ...style }}
>
<Upload beforeUpload={handleUploadAvatar} itemRender={() => void 0} maxCount={1}>
<NextImage
alt={avatar ? 'userAvatar' : 'LobeVidol'}
height={size}
src={!!avatar ? avatar : DEFAULT_AGENT_AVATAR_URL}
unoptimized
width={size}
/>
</Upload>
</div>
);
});

@ -0,0 +1,35 @@
import { Input } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { MAX_README_LENGTH } from '@/constants/common';
import { agentSelectors, useAgentStore } from '@/store/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const { t } = useTranslation('role');
const [readme, updateAgentMeta] = useAgentStore((s) => [
agentSelectors.currentAgentMeta(s)?.readme,
s.updateAgentMeta,
]);
return (
<Input.TextArea
className={className}
style={style}
value={readme}
autoSize={{ minRows: 10, maxRows: 10 }}
placeholder={t('role.roleReadmeTip')}
showCount
maxLength={MAX_README_LENGTH}
onChange={(e) => {
updateAgentMeta({ readme: e.target.value });
}}
/>
);
});

@ -0,0 +1,44 @@
import { Select } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { RoleCategoryEnum } from '@/types/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const [category, updateAgentMeta] = useAgentStore((s) => [
agentSelectors.currentAgentMeta(s)?.category,
s.updateAgentMeta,
]);
const { t } = useTranslation('role');
return (
<Select
className={className}
style={style}
options={[
{ label: t('category.animal'), value: RoleCategoryEnum.ANIMAL },
{ label: t('category.anime'), value: RoleCategoryEnum.ANIME },
{ label: t('category.book'), value: RoleCategoryEnum.BOOK },
{ label: t('category.game'), value: RoleCategoryEnum.GAME },
{ label: t('category.history'), value: RoleCategoryEnum.HISTORY },
{ label: t('category.movie'), value: RoleCategoryEnum.MOVIE },
{ label: t('category.realistic'), value: RoleCategoryEnum.REALISTIC },
{ label: t('category.vroid'), value: RoleCategoryEnum.VROID },
{ label: t('category.vtuber'), value: RoleCategoryEnum.VTUBER },
]}
value={category}
defaultActiveFirstOption={true}
onChange={(value) => {
updateAgentMeta({ category: value });
}}
/>
);
});

@ -0,0 +1,34 @@
import { Input } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { MAX_DESCRIPTION_LENGTH } from '@/constants/common';
import { agentSelectors, useAgentStore } from '@/store/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const { t } = useTranslation('role');
const [description, updateAgentMeta] = useAgentStore((s) => [
agentSelectors.currentAgentMeta(s)?.description,
s.updateAgentMeta,
]);
return (
<Input
className={className}
style={style}
value={description}
placeholder={t('role.roleDescriptionTip')}
maxLength={MAX_DESCRIPTION_LENGTH}
showCount
onChange={(e) => {
updateAgentMeta({ description: e.target.value });
}}
/>
);
});

@ -0,0 +1,37 @@
import { Select } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { GenderEnum } from '@/types/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const [gender, updateAgentMeta] = useAgentStore((s) => [
agentSelectors.currentAgentMeta(s)?.gender,
s.updateAgentMeta,
]);
const { t } = useTranslation('role');
return (
<Select
className={className}
style={style}
options={[
{ label: t('gender.male'), value: GenderEnum.MALE },
{ label: t('gender.female'), value: GenderEnum.FEMALE },
]}
value={gender}
defaultActiveFirstOption={true}
onChange={(value) => {
updateAgentMeta({ gender: value });
}}
/>
);
});

@ -0,0 +1,34 @@
import { Input } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { MAX_NAME_LENGTH } from '@/constants/common';
import { agentSelectors, useAgentStore } from '@/store/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const { t } = useTranslation('role');
const [name, updateAgentMeta] = useAgentStore((s) => [
agentSelectors.currentAgentMeta(s)?.name,
s.updateAgentMeta,
]);
return (
<Input
className={className}
style={style}
value={name}
placeholder={t('role.roleNameTip')}
maxLength={MAX_NAME_LENGTH}
showCount
onChange={(e) => {
updateAgentMeta({ name: e.target.value });
}}
/>
);
});

@ -0,0 +1,67 @@
import { Form, FormProps } from '@lobehub/ui';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FORM_STYLE } from '@/constants/token';
import Greeting from './Greeting';
import PreviewWithUpload from './PreviewWithUpload';
import ReadMe from './ReadMe';
import RoleCategory from './RoleCategory';
import RoleDescription from './RoleDescription';
import RoleGender from './RoleGender';
import RoleName from './RoleName';
const Info = () => {
const [form] = Form.useForm();
const { t } = useTranslation('role');
const basic: FormProps['items'] = [
{
label: t('info.avatarLabel'),
desc: t('info.avatarDescription'),
name: 'avatar',
children: <PreviewWithUpload />,
},
{
label: t('info.nameLabel'),
desc: t('info.nameDescription'),
name: 'name',
children: <RoleName />,
},
{
label: t('info.descLabel'),
desc: t('info.descDescription'),
name: 'description',
children: <RoleDescription />,
},
{
label: t('info.greetLabel'),
desc: t('info.greetDescription'),
name: 'greeting',
children: <Greeting />,
},
{
label: t('info.genderLabel'),
desc: t('info.genderDescription'),
name: 'gender',
children: <RoleGender />,
},
{
label: t('info.categoryLabel'),
desc: t('info.categoryDescription'),
name: 'category',
children: <RoleCategory />,
},
{
label: t('info.readmeLabel'),
desc: t('info.readmeDescription'),
name: 'readme',
children: <ReadMe />,
},
];
return <Form form={form} items={basic} itemsType={'flat'} variant={'block'} {...FORM_STYLE} />;
};
export default Info;

@ -0,0 +1,23 @@
import { SliderWithInput } from '@lobehub/ui';
import React, { memo } from 'react';
import { agentSelectors, useAgentStore } from '@/store/agent';
const FrequencyPenalty = memo(() => {
const [frequency_penalty, updateAgentConfig] = useAgentStore((s) => [
agentSelectors.currentAgentParams(s)?.frequency_penalty,
s.updateAgentConfig,
]);
return (
<SliderWithInput
max={2}
min={-2}
step={0.1}
value={frequency_penalty}
onChange={(value) => updateAgentConfig({ params: { frequency_penalty: value } })}
/>
);
});
export default FrequencyPenalty;

@ -0,0 +1,79 @@
import { Select, SelectProps } from 'antd';
import { createStyles } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { memo, useMemo } from 'react';
import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { useSettingStore } from '@/store/setting';
import { modelProviderSelectors } from '@/store/setting/selectors';
import { ModelProviderCard } from '@/types/llm';
const useStyles = createStyles(({ css, prefixCls }) => ({
select: css`
&.${prefixCls}-select-dropdown .${prefixCls}-select-item-option-grouped {
padding-inline-start: 12px;
}
`,
}));
interface ModelOption {
label: any;
provider: string;
value: string;
}
interface ModelSelectProps {
onChange?: (props: { model: string; provider: string }) => void;
showAbility?: boolean;
}
const ModelSelect = memo<ModelSelectProps>(({ showAbility = true }) => {
const enabledList = useSettingStore(
modelProviderSelectors.modelProviderListForModelSelect,
isEqual,
);
const [model, provider, updateAgentConfig] = useAgentStore((s) => [
agentSelectors.currentAgentModel(s),
agentSelectors.currentAgentProvider(s),
s.updateAgentConfig,
]);
const { styles } = useStyles();
const options = useMemo<SelectProps['options']>(() => {
const getChatModels = (provider: ModelProviderCard) =>
provider.chatModels.map((model) => ({
label: <ModelItemRender {...model} showInfoTag={showAbility} />,
provider: provider.id,
value: `${provider.id}/${model.id}`,
}));
if (enabledList.length === 1) {
const provider = enabledList[0];
return getChatModels(provider);
}
return enabledList.map((provider) => ({
label: <ProviderItemRender name={provider.name} provider={provider.id} />,
options: getChatModels(provider),
}));
}, [enabledList]);
return (
<Select
onChange={(value, option) => {
const model = value.split('/').slice(1).join('/');
updateAgentConfig({ model, provider: (option as unknown as ModelOption).provider });
}}
options={options}
popupClassName={styles.select}
popupMatchSelectWidth={false}
value={`${provider}/${model}`}
/>
);
});
export default ModelSelect;

@ -0,0 +1,23 @@
import { SliderWithInput } from '@lobehub/ui';
import React, { memo } from 'react';
import { agentSelectors, useAgentStore } from '@/store/agent';
const PresencePenalty = memo(() => {
const [presence_penalty, updateAgentConfig] = useAgentStore((s) => [
agentSelectors.currentAgentParams(s)?.presence_penalty,
s.updateAgentConfig,
]);
return (
<SliderWithInput
max={2}
min={-2}
step={0.1}
value={presence_penalty}
onChange={(value) => updateAgentConfig({ params: { presence_penalty: value } })}
/>
);
});
export default PresencePenalty;

@ -0,0 +1,23 @@
import { SliderWithInput } from '@lobehub/ui';
import React, { memo } from 'react';
import { agentSelectors, useAgentStore } from '@/store/agent';
const Temperature = memo(() => {
const [temperature, updateAgentConfig] = useAgentStore((s) => [
agentSelectors.currentAgentParams(s)?.temperature,
s.updateAgentConfig,
]);
return (
<SliderWithInput
max={1}
min={0}
step={0.1}
value={temperature}
onChange={(value) => updateAgentConfig({ params: { temperature: value } })}
/>
);
});
export default Temperature;

@ -0,0 +1,23 @@
import { SliderWithInput } from '@lobehub/ui';
import React, { memo } from 'react';
import { agentSelectors, useAgentStore } from '@/store/agent';
const TopP = memo(() => {
const [top_p, updateAgentConfig] = useAgentStore((s) => [
agentSelectors.currentAgentParams(s)?.top_p,
s.updateAgentConfig,
]);
return (
<SliderWithInput
max={1}
min={0}
step={0.1}
value={top_p}
onChange={(value) => updateAgentConfig({ params: { top_p: value } })}
/>
);
});
export default TopP;

@ -0,0 +1,58 @@
'use client';
import { Form, FormProps } from '@lobehub/ui';
import React, { memo } from 'react';
import { useTranslation } from 'react-i18next';
import FrequencyPenalty from '@/app/role/RoleEdit/LangModel/FrequencyPenalty';
import ModelSelect from '@/app/role/RoleEdit/LangModel/ModelSelect';
import PresencePenalty from '@/app/role/RoleEdit/LangModel/PresencePenalty';
import Temperature from '@/app/role/RoleEdit/LangModel/Temperature';
import TopP from '@/app/role/RoleEdit/LangModel/TopP';
import { FORM_STYLE } from '@/constants/token';
const LangModel = memo(() => {
const { t } = useTranslation('role');
const model: FormProps['items'] = [
{
children: <ModelSelect />,
desc: t('llm.modelDescription'),
label: t('llm.modelLabel'),
name: 'model',
tag: 'model',
},
{
children: <Temperature />,
desc: t('llm.temperatureDescription'),
label: t('llm.temperatureLabel'),
name: ['params', 'temperature'],
tag: 'temperature',
},
{
children: <TopP />,
desc: t('llm.topPDescription'),
label: t('llm.topPLabel'),
name: ['params', 'top_p'],
tag: 'top_p',
},
{
children: <PresencePenalty />,
desc: t('llm.presencePenaltyDescription'),
label: t('llm.presencePenaltyLabel'),
name: ['params', 'presence_penalty'],
tag: 'presence_penalty',
},
{
children: <FrequencyPenalty />,
desc: t('llm.frequencyPenaltyDescription'),
label: t('llm.frequencyPenaltyLabel'),
name: ['params', 'frequency_penalty'],
tag: 'frequency_penalty',
},
];
return <Form items={model} itemsType={'flat'} variant={'block'} {...FORM_STYLE} />;
});
export default LangModel;

@ -0,0 +1,36 @@
import { Input } from 'antd';
import React, { CSSProperties, memo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { MAX_SYSTEM_ROLE_LENGTH } from '@/constants/common';
import { agentSelectors, useAgentStore } from '@/store/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const inputRef = useRef(null);
const [systemRole, updateAgentConfig] = useAgentStore((s) => [
agentSelectors.currentAgentItem(s)?.systemRole,
s.updateAgentConfig,
]);
const { t } = useTranslation('role');
return (
<Input.TextArea
ref={inputRef}
className={className}
style={style}
value={systemRole}
autoSize={{ minRows: 16 }}
placeholder={t('role.inputRoleSetting')}
showCount
maxLength={MAX_SYSTEM_ROLE_LENGTH}
onChange={(e) => {
updateAgentConfig({ systemRole: e.target.value });
}}
/>
);
});

@ -0,0 +1,50 @@
import { Card, Cards } from '@lobehub/ui/mdx';
import React, { CSSProperties, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { agentSelectors, useAgentStore } from '@/store/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const [name, updateAgentConfig] = useAgentStore((s) => [
agentSelectors.currentAgentItem(s)?.meta.name,
s.updateAgentConfig,
]);
const { t } = useTranslation('role');
return (
<Cards style={{ marginTop: 24, ...style }} className={className}>
<Card
image="https://r2.vidol.chat/common/default.png"
title={t('systemRole.defaultLabel', { ns: 'role' })}
onClick={() => {
updateAgentConfig({
systemRole: t('systemRole.default', { ns: 'role', char: name }),
});
}}
/>
<Card
image="https://r2.vidol.chat/common/genshin.png"
title={t('systemRole.geniusLabel', { ns: 'role' })}
onClick={() => {
updateAgentConfig({
systemRole: t('systemRole.genius', { ns: 'role', char: name }),
});
}}
/>
<Card
image="https://r2.vidol.chat/common/zzz.png"
title={t('systemRole.zzzLabel', { ns: 'role' })}
onClick={() => {
updateAgentConfig({
systemRole: t('systemRole.zzz', { ns: 'role', char: name }),
});
}}
/>
</Cards>
);
});

@ -0,0 +1,27 @@
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import React from 'react';
import SystemRole from './SystemRole';
import Templates from './Templates';
const useStyles = createStyles(({ css }) => ({
container: css`
display: flex;
flex-direction: column;
padding: 0 16px;
`,
}));
const Info = () => {
const { styles } = useStyles();
return (
<div className={classNames(styles.container)}>
<SystemRole />
<Templates />
</div>
);
};
export default Info;

@ -0,0 +1,167 @@
import { ActionIcon, Form, FormItem, Modal } from '@lobehub/ui';
import { VRMExpressionPresetName } from '@pixiv/three-vrm';
import { Input, Select } from 'antd';
import { Edit2Icon, Plus } from 'lucide-react';
import React, { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { INPUT_WIDTH_MD, INPUT_WIDTH_SM } from '@/constants/token';
import { MAX_TOUCH_ACTION_TEXT_LENGTH } from '@/constants/touch';
import { MotionPresetName, motionPresetMap } from '@/libs/emoteController/motionPresetMap';
import { useAgentStore } from '@/store/agent';
import { TouchAction, TouchAreaEnum } from '@/types/touch';
interface Props {
index?: number;
isEdit?: boolean;
touchAction?: TouchAction;
touchArea: TouchAreaEnum;
}
export default memo((props: Props) => {
const { touchArea, index, touchAction, isEdit = true } = props;
const [open, setOpen] = useState(false);
const [form] = Form.useForm();
const { t } = useTranslation('role');
const [updateTouchAction, createTouchAction] = useAgentStore((s) => [
s.updateTouchAction,
s.createTouchAction,
]);
const showModal = () => {
setOpen(true);
};
const handleOk = () => {
form.validateFields().then((values) => {
setOpen(false);
if (isEdit) {
updateTouchAction(touchArea, index!, values);
} else {
createTouchAction(touchArea, values);
}
});
};
const handleCancel = () => {
setOpen(false);
};
return (
<>
<ActionIcon
icon={isEdit ? Edit2Icon : Plus}
title={isEdit ? t('actions.edit', { ns: 'chat' }) : t('actions.add', { ns: 'chat' })}
onClick={showModal}
/>
<Modal
allowFullscreen
onCancel={handleCancel}
onOk={handleOk}
open={open}
width={800}
destroyOnClose
title={isEdit ? t('touch.editAction') : t('touch.addAction')}
okText={t('confirm', { ns: 'common' })}
cancelText={t('cancel', { ns: 'common' })}
>
<Form
layout="horizontal"
requiredMark
initialValues={
isEdit
? touchAction
: {
expression: VRMExpressionPresetName.Neutral,
motion: MotionPresetName.FemaleHappy,
}
}
form={form}
preserve={false}
>
<FormItem
label={t('info.textLabel')}
desc={t('info.textDescription')}
name={'text'}
rules={[{ required: true, message: t('touch.inputDIYText') }]}
>
<Input.TextArea
placeholder={t('touch.inputActionText')}
maxLength={MAX_TOUCH_ACTION_TEXT_LENGTH}
showCount
autoSize
style={{ width: INPUT_WIDTH_MD }}
/>
</FormItem>
<FormItem
label={t('info.emotionLabel')}
desc={t('info.emotionDescription')}
divider
rules={[{ required: true, message: t('touch.inputActionEmotion') }]}
name="expression"
>
<Select
options={[
{
label: t('touch.expression.natural'),
value: VRMExpressionPresetName.Neutral,
},
{
label: t('touch.expression.happy'),
value: VRMExpressionPresetName.Happy,
},
{
label: t('touch.expression.angry'),
value: VRMExpressionPresetName.Angry,
},
{
label: t('touch.expression.sad'),
value: VRMExpressionPresetName.Sad,
},
{
label: t('touch.expression.relaxed'),
value: VRMExpressionPresetName.Relaxed,
},
{
label: t('touch.expression.surprised'),
value: VRMExpressionPresetName.Surprised,
},
{
label: t('touch.expression.blink'),
value: VRMExpressionPresetName.Blink,
},
{
label: t('touch.expression.blinkLeft'),
value: VRMExpressionPresetName.BlinkLeft,
},
{
label: t('touch.expression.blinkRight'),
value: VRMExpressionPresetName.BlinkRight,
},
]}
style={{ width: INPUT_WIDTH_SM }}
defaultActiveFirstOption={true}
/>
</FormItem>
<FormItem
label={t('info.motionLabel')}
desc={t('info.motionDescription')}
divider
rules={[{ required: true, message: t('touch.inputActionEmotion') }]}
name="motion"
>
<Select
options={Object.entries(motionPresetMap).map(([key, value]) => ({
label: t(`${value.name}`),
value: key,
}))}
style={{ width: INPUT_WIDTH_SM }}
defaultActiveFirstOption={true}
/>
</FormItem>
</Form>
</Modal>
</>
);
});

@ -0,0 +1,32 @@
import { ActionIcon } from '@lobehub/ui';
import { Popconfirm } from 'antd';
import { XIcon } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useAgentStore } from '@/store/agent';
import { TouchAreaEnum } from '@/types/touch';
interface Props {
index: number;
touchArea: TouchAreaEnum;
}
export default memo((props: Props) => {
const { touchArea, index } = props;
const { t } = useTranslation('common');
const [removeTouchAction] = useAgentStore((s) => [s.removeTouchAction]);
return (
<Popconfirm
title={t('confirmDel')}
key="delete"
okText={t('confirm')}
cancelText={t('cancel')}
onConfirm={() => {
removeTouchAction(touchArea, index);
}}
>
<ActionIcon icon={XIcon} title={t('delete')} />
</Popconfirm>
);
});

@ -0,0 +1,20 @@
import { Switch } from 'antd';
import React, { memo } from 'react';
import { agentSelectors, useAgentStore } from '@/store/agent';
export default memo(() => {
const [enable, updateAgentConfig] = useAgentStore((s) => [
agentSelectors.currentAgentItem(s)?.touch?.enable,
s.updateAgentConfig,
]);
return (
<Switch
value={enable}
// style={{ width: 48 }}
onChange={(value) => {
updateAgentConfig({ touch: { enable: value } });
}}
/>
);
});

@ -0,0 +1,62 @@
import { ActionIcon } from '@lobehub/ui';
import { message } from 'antd';
import { isEqual } from 'lodash-es';
import { Loader2, PlayIcon } from 'lucide-react';
import { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { speakCharacter } from '@/libs/messages/speakCharacter';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { useGlobalStore } from '@/store/global';
import { TouchAction } from '@/types/touch';
interface Props {
touchAction: TouchAction;
}
export default memo((props: Props) => {
const { touchAction } = props;
const [loading, setLoading] = useState(false);
const viewer = useGlobalStore((s) => s.viewer);
const { t } = useTranslation('role');
const currentAgentTTS = useAgentStore((s) => agentSelectors.currentAgentTTS(s), isEqual);
if (!touchAction) {
return null;
}
return (
<ActionIcon
icon={loading ? Loader2 : PlayIcon}
spin={loading}
disable={loading}
title={t('play', { ns: 'common' })}
key="play"
onClick={() => {
speakCharacter(
{
expression: touchAction.expression,
tts: {
...currentAgentTTS,
message: touchAction.text,
},
motion: touchAction.motion,
},
viewer,
{
onStart: () => {
setLoading(true);
},
onComplete: () => {
setLoading(false);
},
onError: () => {
message.error(t('ttsTransformFailed', { ns: 'error' }));
},
},
);
}}
/>
);
});

@ -0,0 +1,92 @@
import { Empty } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import { get } from 'lodash-es';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import ListItem from '@/components/ListItem';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { TouchAction, TouchAreaEnum } from '@/types/touch';
import Header from '../components/Header';
import AddOrEdit from './Actions/AddOrEdit';
import Delete from './Actions/Delete';
import Play from './Actions/Play';
const useStyles = createStyles(({ css, token }) => ({
list: css`
width: 100%;
`,
listItem: css`
position: relative;
margin-block: 2px;
font-size: ${token.fontSize}px;
background-color: ${token.colorBgContainer};
border-radius: ${token.borderRadius}px;
`,
}));
interface AreaListProps {
areaOptions?: { label: string; value: TouchAreaEnum }[];
className?: string;
currentTouchArea: TouchAreaEnum;
style?: React.CSSProperties;
}
const AreaList = (props: AreaListProps) => {
const { styles } = useStyles();
const { currentTouchArea, style, className, areaOptions = [] } = props;
const [currentAgentTouch] = useAgentStore((s) => [agentSelectors.currentAgentTouch(s)]);
const { t } = useTranslation('role');
const items = get(currentAgentTouch, currentTouchArea)
? (get(currentAgentTouch, currentTouchArea) as TouchAction[])
: [];
const touchArea = areaOptions.find((item) => item.value === currentTouchArea)?.label;
return (
<Flexbox flex={1} style={style} className={className}>
<Header
title={t('touch.touchActionList', { touchArea })}
extra={<AddOrEdit isEdit={false} touchArea={currentTouchArea} />}
/>
{items.map((item, index) => {
return (
<ListItem
key={`${item.text}_${index}`}
className={classNames(styles.listItem)}
showAction={true}
avatar={<Play key={`${currentTouchArea}_play_${index}`} touchAction={item} />}
title={item.text}
active={false}
actions={[
<AddOrEdit
key={`${currentTouchArea}_edit_${index}`}
index={index}
touchArea={currentTouchArea}
touchAction={item}
isEdit={true}
/>,
<Delete
key={`${currentTouchArea}_delete_${index}`}
index={index}
touchArea={currentTouchArea}
/>,
]}
/>
);
})}
{items.length === 0 && (
<Empty description={t('touch.noTouchActions')} image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</Flexbox>
);
};
export default AreaList;

@ -0,0 +1,60 @@
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import { MousePointerClick } from 'lucide-react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import ListItem from '@/components/ListItem';
import { TouchAreaEnum } from '@/types/touch';
import Enabled from '../ActionList/Actions/Enabled';
import Header from '../components/Header';
const useStyles = createStyles(({ css, token, responsive }) => ({
listItem: css`
position: relative;
width: 100%;
margin-block: 2px;
border-radius: ${token.borderRadius}px;
`,
container: css`
display: flex;
flex-direction: column;
width: 180px;
${responsive.lg} {
flex-flow: row wrap;
width: 100%;
}
`,
}));
interface IndexProps {
areaOptions: { label: string; value: TouchAreaEnum }[];
currentTouchArea: TouchAreaEnum;
setCurrentTouchArea: (area: TouchAreaEnum) => void;
}
const Index = (props: IndexProps) => {
const { styles } = useStyles();
const { currentTouchArea, setCurrentTouchArea, areaOptions = [] } = props;
const { t } = useTranslation('role');
return (
<Flexbox className={styles.container}>
<Header title={t('touch.customEnable')} extra={<Enabled />} />
{areaOptions.map((item) => (
<ListItem
avatar={<MousePointerClick />}
className={classNames(styles.listItem)}
active={item.value === currentTouchArea}
key={item.value}
title={item.label}
onClick={() => setCurrentTouchArea(item.value)}
/>
))}
</Flexbox>
);
};
export default Index;

@ -0,0 +1,68 @@
import { InboxOutlined } from '@ant-design/icons';
import { Upload } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import AgentViewer from '@/features/AgentViewer';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { useGlobalStore } from '@/store/global';
import { getModelPathByAgentId } from '@/utils/file';
import { cacheStorage } from '@/utils/storage';
import { useStyles } from './style';
interface ViewerWithUploadProps {
style?: CSSProperties;
}
const ViewerWithUpload = memo<ViewerWithUploadProps>(({ style }) => {
const viewer = useGlobalStore((s) => s.viewer);
const { t } = useTranslation('role');
const { styles } = useStyles();
const [currentAgentId, currentAgent3DModel, updateAgentConfig] = useAgentStore((s) => [
agentSelectors.currentAgentId(s),
agentSelectors.currentAgent3DModel(s),
s.updateAgentConfig,
]);
const handleUploadAvatar = (file: any) => {
if (!currentAgentId) return;
const blob = new Blob([file], { type: 'application/octet-stream' });
const modelKey = getModelPathByAgentId(currentAgentId!);
cacheStorage.setItem(modelKey, blob).then(() => {
updateAgentConfig({ meta: { model: modelKey } });
const vrmUrl = window.URL.createObjectURL(blob as Blob);
viewer.loadVrm(vrmUrl);
});
};
return currentAgent3DModel && currentAgentId ? (
<AgentViewer agentId={currentAgentId} interactive={false} toolbar={false} />
) : (
<Upload
beforeUpload={handleUploadAvatar}
itemRender={() => void 0}
accept={'.vrm'}
maxCount={1}
style={style}
openFileDialogOnClick={!currentAgent3DModel}
>
<Flexbox
className={styles.guide}
align="center"
justify={'center'}
width={'100%'}
height={'100%'}
>
<InboxOutlined className={styles.icon} />
<p className={styles.info}>{t('uploadTip', { ns: 'common' })}</p>
<p className={styles.extra}>{t('upload.support')}</p>
</Flexbox>
</Upload>
);
});
export default ViewerWithUpload;

@ -0,0 +1,24 @@
import { createStyles } from 'antd-style';
import { ROLE_VIEWER_WIDTH } from '@/constants/common';
export const useStyles = createStyles(({ css, token }) => ({
guide: css`
cursor: pointer;
width: ${ROLE_VIEWER_WIDTH}px;
height: 100%;
min-height: 480px;
border: 1px dashed ${token.colorBorderSecondary};
`,
icon: css`
font-size: 48px;
color: ${token.geekblue};
`,
info: css``,
extra: css`
font-size: 12px;
color: ${token.colorTextDescription};
`,
}));

@ -0,0 +1,32 @@
import { createStyles } from 'antd-style';
import React from 'react';
import { Flexbox } from 'react-layout-kit';
interface HeaderProps {
extra?: React.ReactNode;
title: React.ReactNode;
}
const useStyles = createStyles(({ css, token }) => ({
title: css`
font-size: ${token.fontSize}px;
color: ${token.colorPrimary};
`,
header: css`
align-items: center;
height: 48px;
font-size: ${token.fontSize}px;
`,
}));
export default (props: HeaderProps) => {
const { styles } = useStyles();
const { title, extra } = props;
return (
<Flexbox justify="space-between" horizontal className={styles.header}>
<div className={styles.title}>{title}</div>
{extra}
</Flexbox>
);
};

@ -0,0 +1,95 @@
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import React, { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { ROLE_VIEWER_WIDTH } from '@/constants/common';
import { TouchAreaEnum } from '@/types/touch';
import ActionList from './ActionList';
import SideBar from './SideBar';
import ViewerWithUpload from './ViewerWithUpload';
const useStyles = createStyles(({ css, token, responsive }) => ({
container: css`
position: relative;
display: flex;
width: 100%;
min-height: 480px;
padding: 0 16px;
background-color: rgba(255, 255, 255, 2%);
border-radius: ${token.borderRadius}px;
${responsive.lg} {
flex-direction: column;
min-height: auto;
}
`,
model: css`
width: ${ROLE_VIEWER_WIDTH}px;
height: 100%;
${responsive.lg} {
width: 100%;
}
`,
}));
interface TouchProps {
className?: string;
style?: React.CSSProperties;
}
const Touch = (props: TouchProps) => {
const { style, className } = props;
const { styles } = useStyles();
const [currentTouchArea, setCurrentTouchArea] = useState<TouchAreaEnum>(TouchAreaEnum.Head);
const { t } = useTranslation('role');
const TOUCH_AREA_OPTIONS = [
{
label: t('touch.area.head'),
value: TouchAreaEnum.Head,
},
{
label: t('touch.area.arm'),
value: TouchAreaEnum.Arm,
},
{
label: t('touch.area.leg'),
value: TouchAreaEnum.Leg,
},
{
label: t('touch.area.chest'),
value: TouchAreaEnum.Chest,
},
{
label: t('touch.area.belly'),
value: TouchAreaEnum.Belly,
},
{
label: t('touch.area.buttocks'),
value: TouchAreaEnum.Buttocks,
},
];
return (
<Flexbox className={classNames(className, styles.container)} style={style} horizontal gap={12}>
<SideBar
currentTouchArea={currentTouchArea}
setCurrentTouchArea={setCurrentTouchArea}
areaOptions={TOUCH_AREA_OPTIONS}
/>
<ActionList currentTouchArea={currentTouchArea} areaOptions={TOUCH_AREA_OPTIONS} />
<Flexbox className={styles.model}>
<ViewerWithUpload />
</Flexbox>
</Flexbox>
);
};
export default memo(Touch);

@ -0,0 +1,35 @@
import { Select } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { TTS_ENGINE } from '@/types/tts';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const [engine, updateAgentTTS] = useAgentStore((s) => [
agentSelectors.currentAgentTTS(s)?.engine,
s.updateAgentTTS,
]);
return (
<Select
className={className}
style={style}
value={engine}
options={[
{
label: 'Edge',
value: 'edge',
},
]}
onChange={(value) => {
updateAgentTTS({ engine: value as TTS_ENGINE });
}}
/>
);
});

@ -0,0 +1,30 @@
import { Select } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { supportedLocales } from '@/constants/tts';
import { agentSelectors, useAgentStore } from '@/store/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const [locale, updateAgentTTS] = useAgentStore((s) => [
agentSelectors.currentAgentTTS(s)?.locale,
s.updateAgentTTS,
]);
return (
<Select
className={className}
style={style}
value={locale}
options={supportedLocales}
onChange={(value) => {
updateAgentTTS({ locale: value });
}}
/>
);
});

@ -0,0 +1,44 @@
import { InputNumber, Slider } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { Flexbox } from 'react-layout-kit';
import { MAX_TTS_PITCH, MIN_TTS_PITCH, TTS_PITCH_STEP } from '@/constants/tts';
import { agentSelectors, useAgentStore } from '@/store/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const [pitch, updateAgentTTS] = useAgentStore((s) => [
agentSelectors.currentAgentTTS(s)?.pitch,
s.updateAgentTTS,
]);
return (
<Flexbox className={className} style={style} flex={1} horizontal gap={8}>
<Slider
value={pitch}
max={MAX_TTS_PITCH}
style={{ flex: 1 }}
min={MIN_TTS_PITCH}
step={TTS_PITCH_STEP}
onChange={(value) => {
updateAgentTTS({ pitch: value });
}}
/>
<InputNumber
min={MIN_TTS_PITCH}
max={MAX_TTS_PITCH}
step={TTS_PITCH_STEP}
style={{ width: 80 }}
value={pitch}
onChange={(value) => {
updateAgentTTS({ pitch: value === null ? undefined : value });
}}
/>
</Flexbox>
);
});

@ -0,0 +1,72 @@
import { PlayCircleOutlined } from '@ant-design/icons';
import { useRequest } from 'ahooks';
import { Button, message } from 'antd';
import React, { CSSProperties, memo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { supportedLocales } from '@/constants/tts';
import { speechApi } from '@/services/tts';
import { agentSelectors, useAgentStore } from '@/store/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const ref = useRef<HTMLAudioElement>(null);
const { t } = useTranslation('role');
const tts = useAgentStore((s) => agentSelectors.currentAgentTTS(s));
const sample = supportedLocales.find((item) => item.value === tts?.locale)?.sample;
const { loading, run: speek } = useRequest(speechApi, {
manual: true,
onError: (err) => {
message.error(err.message);
if (ref.current) {
ref.current.pause();
ref.current.currentTime = 0;
ref.current.src = '';
}
},
onSuccess: (res) => {
message.success(t('tts.transformSuccess'));
const adUrl = URL.createObjectURL(new Blob([res]));
if (ref.current) {
ref.current.src = adUrl;
ref.current.play();
}
},
});
return (
<>
<Button
htmlType="button"
type={'primary'}
style={style}
className={className}
icon={<PlayCircleOutlined />}
loading={loading}
onClick={() => {
if (!tts?.locale) {
message.error(t('tts.selectLanguage'));
return;
}
if (!tts?.voice) {
message.error(t('tts.selectVoice'));
return;
}
if (sample) {
speek({ ...tts, message: sample });
}
}}
>
{t('tts.audition')}
</Button>
<audio ref={ref} />
</>
);
});

@ -0,0 +1,44 @@
import { InputNumber, Slider } from 'antd';
import React, { CSSProperties, memo } from 'react';
import { Flexbox } from 'react-layout-kit';
import { MAX_TTS_SPEED, MIN_TTS_SPEED, TTS_SPEED_STEP } from '@/constants/tts';
import { agentSelectors, useAgentStore } from '@/store/agent';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const [speed, updateAgentTTS] = useAgentStore((s) => [
agentSelectors.currentAgentTTS(s)?.speed,
s.updateAgentTTS,
]);
return (
<Flexbox className={className} style={style} flex={1} horizontal gap={8}>
<Slider
value={speed}
style={{ flex: 1 }}
min={MIN_TTS_SPEED}
max={MAX_TTS_SPEED}
step={TTS_SPEED_STEP}
onChange={(value) => {
updateAgentTTS({ speed: value });
}}
/>
<InputNumber
min={MIN_TTS_SPEED}
max={MAX_TTS_SPEED}
step={TTS_SPEED_STEP}
style={{ width: 80 }}
value={speed}
onChange={(value) => {
updateAgentTTS({ speed: value === null ? undefined : value });
}}
/>
</Flexbox>
);
});

@ -0,0 +1,72 @@
import { useRequest } from 'ahooks';
import { Select } from 'antd';
import { isEqual } from 'lodash-es';
import React, { CSSProperties, memo, useEffect, useState } from 'react';
import { voiceListApi } from '@/services/tts';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { Voice } from '@/types/tts';
interface Props {
className?: string;
style?: CSSProperties;
}
export default memo<Props>((props) => {
const { style, className } = props;
const [voices, setVoices] = useState<Voice[]>([]);
const [voice, engine, locale, updateAgentTTS] = useAgentStore(
(s) => [
agentSelectors.currentAgentTTS(s)?.voice,
agentSelectors.currentAgentTTS(s)?.engine,
agentSelectors.currentAgentTTS(s)?.locale,
s.updateAgentTTS,
],
isEqual,
);
const { loading: voiceLoading } = useRequest(
() => {
if (!engine) {
return Promise.resolve({ data: [] });
}
return voiceListApi(engine);
},
{
onSuccess: (res) => {
setVoices(res.data);
},
refreshDeps: [engine],
},
);
useEffect(() => {
if (!locale) {
return;
}
const voice = voices.find((voice) => voice.locale === locale);
if (voice) {
updateAgentTTS({ voice: voice.ShortName });
}
}, [locale, engine]);
return (
<Select
className={className}
style={style}
value={voice}
disabled={voiceLoading}
loading={voiceLoading}
options={voices
.filter((voice) => voice.locale === locale)
.map((item) => ({
label: `${item.DisplayName}-${item.LocalName}`,
value: item.ShortName,
}))}
onChange={(value) => {
updateAgentTTS({ voice: value });
}}
/>
);
});

@ -0,0 +1,55 @@
import { Form, FormProps } from '@lobehub/ui';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FORM_STYLE } from '@/constants/token';
import TTSEngine from './TTSEngine';
import TTSLocale from './TTSLocale';
import TTSPitch from './TTSPitch';
import TTSPlay from './TTSPlay';
import TTSSpeed from './TTSSpeed';
import TTSVoice from './TTSVoice';
export default () => {
const { t } = useTranslation('role');
const voice: FormProps['items'] = [
{
label: t('tts.engineLabel'),
desc: t('tts.engineDescription'),
name: 'engine',
children: <TTSEngine />,
},
{
label: t('tts.localeLabel'),
desc: t('tts.localeDescription'),
name: 'locale',
children: <TTSLocale />,
},
{
label: t('tts.voiceLabel'),
desc: t('tts.voiceDescription'),
name: 'voice',
children: <TTSVoice />,
},
{
label: t('tts.speedLabel'),
desc: t('tts.speedDescription'),
name: 'speed',
children: <TTSSpeed />,
},
{
label: t('tts.pitchLabel'),
desc: t('tts.pitchDescription'),
name: 'pitch',
children: <TTSPitch />,
},
{
label: t('tts.audition'),
desc: t('tts.auditionDescription'),
children: <TTSPlay />,
},
];
return <Form items={voice} itemsType={'flat'} variant={'block'} {...FORM_STYLE} />;
};

@ -0,0 +1,29 @@
import { ActionIcon, Icon } from '@lobehub/ui';
import { Button } from 'antd';
import { Book } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/token';
const handleOpenDocs = () => {
window.open('https://docs.vidol.chat/role-manual/quickstart/introduction', '_blank');
};
const RoleBookButton = memo<{ modal?: boolean }>(({ modal }) => {
const { t } = useTranslation('role');
return modal ? (
<Button icon={<Icon icon={Book} />} onClick={handleOpenDocs}>
{t('roleBook')}
</Button>
) : (
<ActionIcon
icon={Book}
onClick={handleOpenDocs}
size={DESKTOP_HEADER_ICON_SIZE}
title={t('roleBook')}
/>
);
});
export default RoleBookButton;

@ -0,0 +1,176 @@
'use client';
import { Alert, Icon, Modal, type ModalProps } from '@lobehub/ui';
import { Button, Divider, Input, Popover, Progress, Space, Typography, message } from 'antd';
import { useTheme } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { kebabCase } from 'lodash-es';
import { Dices } from 'lucide-react';
import qs from 'query-string';
import React, { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import AgentCard from '@/components/agent/AgentCard';
import SystemRole from '@/components/agent/SystemRole';
import { AGENTS_INDEX_GITHUB_ISSUE } from '@/constants/url';
import { useUploadAgent } from '@/hooks/useUploadAgent';
import { agentSelectors, useAgentStore } from '@/store/agent';
import { configSelectors, useSettingStore } from '@/store/setting';
import { Agent } from '@/types/agent';
const SubmitAgentModal = memo<ModalProps>(({ open, onCancel }) => {
const [agentId, setAgentId] = useState('');
const theme = useTheme();
const currentAgent: Agent | undefined = useAgentStore(
(s) => agentSelectors.currentAgentItem(s),
isEqual,
);
const language = useSettingStore((s) => configSelectors.currentLanguage(s));
const meta = currentAgent?.meta;
const { t } = useTranslation(['role', 'common', 'error']);
const { uploading, uploadAgentData, percent } = useUploadAgent();
const isFormPass = Boolean(
currentAgent?.greeting &&
currentAgent?.systemRole &&
meta?.name &&
meta?.description &&
meta?.avatar &&
meta?.model,
);
const handleSubmit = async () => {
if (!currentAgent || !meta || !agentId) {
return;
}
const { avatarUrl, coverUrl, modelUrl } = await uploadAgentData(agentId, meta);
if (!avatarUrl || !coverUrl || !modelUrl) {
message.error(t('fileUploadError', { ns: 'error' }));
return;
}
const body = [
'### agentId',
agentId,
'### avatar',
avatarUrl,
'### cover',
coverUrl,
'### systemRole',
currentAgent.systemRole,
'### greeting',
currentAgent.greeting,
'### modelUrl',
modelUrl,
'### name',
meta.name,
'### description',
meta.description,
'### category',
meta.category,
'### readme',
meta.readme,
'### gender',
meta.gender,
'### tts',
JSON.stringify(currentAgent.tts),
'### touch',
JSON.stringify(currentAgent.touch),
'### model',
currentAgent.model,
'### params',
JSON.stringify(currentAgent.params),
'### locale',
language,
].join('\n\n');
const url = qs.stringifyUrl({
query: { body, labels: '🤖 Agent PR', title: `[Agent] ${meta.name}` },
url: AGENTS_INDEX_GITHUB_ISSUE,
});
window.open(url, '_blank');
};
return (
<Modal
allowFullscreen
footer={
<Popover
open={uploading}
title={
<Flexbox>
<Typography.Text type={'secondary'}>{t('submit.uploadingTip')}</Typography.Text>
<Space>
<Progress steps={30} percent={percent.cover} size="small" />
<Typography.Text style={{ fontSize: 12 }}>
{t('submit.uploadingCover')}
</Typography.Text>
</Space>
<Space>
<Progress steps={30} percent={percent.avatar} size="small" />
<Typography.Text style={{ fontSize: 12 }}>
{t('submit.uploadingAvatar')}
</Typography.Text>
</Space>
<Space>
<Progress steps={30} percent={percent.model} size="small" />
<Typography.Text style={{ fontSize: 12 }}>
{t('submit.uploadingModel')}
</Typography.Text>
</Space>
</Flexbox>
}
>
<Button
block
disabled={!isFormPass || !agentId}
onClick={handleSubmit}
size={'large'}
type={'primary'}
loading={uploading}
>
{t('submit.submitAssistant')}
</Button>
</Popover>
}
onCancel={onCancel}
open={open}
title={t('shareToMarket')}
>
<Flexbox gap={16}>
{!isFormPass && <Alert message={t('submit.submitWarning')} showIcon type={'warning'} />}
<AgentCard agent={currentAgent} />
<Divider style={{ margin: '8px 0' }} />
<SystemRole systemRole={currentAgent?.systemRole} />
<Divider style={{ margin: '8px 0' }} />
<strong>
<span style={{ color: theme.colorError, marginRight: 4 }}>*</span>
agentId {t('submit.assistantId')}
</strong>
<Space.Compact style={{ width: '100%' }}>
<Input
onChange={(e) => setAgentId(e.target.value)}
placeholder={t('submit.assistantIdTip')}
value={agentId}
/>
<Button
type="primary"
icon={<Icon icon={Dices} />}
title={t('random', { ns: 'common' })}
onClick={() => {
const randomId = Math.random().toString(36).slice(7);
setAgentId(kebabCase(randomId));
}}
></Button>
</Space.Compact>
</Flexbox>
</Modal>
);
});
export default SubmitAgentModal;

@ -0,0 +1,34 @@
import { ActionIcon, Icon } from '@lobehub/ui';
import { Button } from 'antd';
import { Share2 } from 'lucide-react';
import { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/token';
import SubmitAgentModal from './SubmitAgentModal';
const SubmitAgentButton = memo<{ modal?: boolean }>(({ modal }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const { t } = useTranslation('role');
const shareToMarket = t('shareToMarket');
return (
<>
{modal ? (
<Button block icon={<Icon icon={Share2} />} onClick={() => setIsModalOpen(true)}>
{shareToMarket}
</Button>
) : (
<ActionIcon
icon={Share2}
onClick={() => setIsModalOpen(true)}
size={DESKTOP_HEADER_ICON_SIZE}
title={shareToMarket}
/>
)}
<SubmitAgentModal onCancel={() => setIsModalOpen(false)} open={isModalOpen} />
</>
);
});
export default SubmitAgentButton;

@ -0,0 +1,46 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, token, prefixCls }) => ({
author: css`
font-size: 12px;
`,
avatar: css`
flex: none;
`,
container: css`
position: relative;
padding: 16px 16px 24px;
border-bottom: 1px solid ${token.colorBorderSecondary};
`,
date: css`
font-size: 12px;
color: ${token.colorTextDescription};
`,
desc: css`
color: ${token.colorTextDescription};
text-align: center;
`,
loading: css`
.${prefixCls}-skeleton-content {
display: flex;
flex-direction: column;
}
`,
nav: css`
padding-top: 4px;
.${prefixCls}-tabs-tab {
margin: 4px !important;
+ .${prefixCls}-tabs-tab {
margin: 4px !important;
}
}
`,
title: css`
font-size: 20px;
font-weight: 600;
text-align: center;
`,
}));

@ -0,0 +1,20 @@
import { ActionIcon } from '@lobehub/ui';
import { ChevronsLeft, List } from 'lucide-react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/token';
import { useGlobalStore } from '@/store/global';
export default () => {
const [showRoleList, toggleRoleList] = useGlobalStore((s) => [s.showRoleList, s.toggleRoleList]);
const { t } = useTranslation('chat');
return (
<ActionIcon
icon={showRoleList ? ChevronsLeft : List}
onClick={() => toggleRoleList()}
title={t('roleList')}
size={DESKTOP_HEADER_ICON_SIZE}
/>
);
};

@ -0,0 +1,80 @@
'use client';
import { TabsNav } from '@lobehub/ui';
import { useResponsive } from 'ahooks';
import classNames from 'classnames';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import Info from './Info';
import LangModel from './LangModel';
import Role from './Role';
import Shell from './Shell';
import Voice from './Voice';
import RoleBookButton from './actions/RoleBook';
import SubmitAgentButton from './actions/SubmitAgentButton';
import ToogleRoleList from './actions/ToogleRoleList';
import { useStyles } from './style';
interface RolePanelProps {
className?: string;
style?: React.CSSProperties;
}
const RolePanel = (props: RolePanelProps) => {
const { styles } = useStyles();
const { md = true } = useResponsive();
const { className, style } = props;
const [tab, setTab] = useState('info');
const { t } = useTranslation('role');
return (
<Flexbox flex={1} gap={12} className={classNames(styles.container, className)} style={style}>
<TabsNav
activeKey={tab}
items={[
{
key: 'info',
label: t('nav.info'),
},
{
key: 'role',
label: t('nav.role'),
},
{
key: 'voice',
label: t('nav.voice'),
},
{
key: 'shell',
label: t('nav.shell'),
},
{
key: 'llm',
label: t('nav.llm'),
},
]}
tabBarExtraContent={{
left: <ToogleRoleList />,
right: (
<Flexbox horizontal gap={8}>
<RoleBookButton modal={md} />
<SubmitAgentButton modal={md} />
</Flexbox>
),
}}
onChange={(key) => {
setTab(key);
}}
/>
{tab === 'info' ? <Info /> : null}
{tab === 'role' ? <Role /> : null}
{tab === 'voice' ? <Voice /> : null}
{tab === 'shell' ? <Shell /> : null}
{tab === 'llm' ? <LangModel /> : null}
</Flexbox>
);
};
export default RolePanel;

@ -0,0 +1,7 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css }) => ({
container: css`
width: 100%;
`,
}));

@ -0,0 +1,25 @@
import { FormInstance } from 'antd/es/form/hooks/useForm';
import { isEqual } from 'lodash-es';
import { useLayoutEffect } from 'react';
import { agentSelectors, useAgentStore } from '@/store/agent';
export const useSyncSettings = (form: FormInstance) => {
useLayoutEffect(() => {
const currentAgent = agentSelectors.currentAgentItem(useAgentStore.getState());
form.setFieldsValue(currentAgent);
// sync with later updated settings
const unsubscribe = useAgentStore.subscribe(
(s) => agentSelectors.currentAgentItem(s),
(agent) => {
form.setFieldsValue(agent);
},
{ equalityFn: isEqual },
);
return () => {
unsubscribe();
};
}, []);
};

@ -0,0 +1,128 @@
import { ActionIcon, Modal } from '@lobehub/ui';
import { Avatar, Button, message } from 'antd';
import { createStyles } from 'antd-style';
import { MessageSquarePlus } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import ListItem from '@/components/ListItem';
import { useAgentStore } from '@/store/agent';
import { GenderEnum } from '@/types/agent';
const useStyles = createStyles(({ css, token }) => ({
genderList: css`
display: flex;
gap: 16px;
margin-top: 24px;
`,
genderCard: css`
cursor: pointer;
flex: 1;
padding: 16px;
text-align: center;
border: 1px solid ${token.colorBorder};
border-radius: 8px;
&:hover {
border-color: ${token.colorPrimary};
}
&.selected {
border-color: ${token.colorPrimary};
}
`,
createButton: css`
width: 100%;
margin-top: 24px;
`,
}));
export default function CreateRole() {
const { t } = useTranslation('role');
const { styles } = useStyles();
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedGender, setSelectedGender] = useState<GenderEnum | null>(null);
const [loading, setLoading] = useState(false);
const router = useRouter();
const createNewAgent = useAgentStore((s) => s.createNewAgent);
const handleCreateRole = async () => {
if (!selectedGender) return;
try {
setLoading(true);
await createNewAgent(selectedGender);
router.push(`/role`);
setIsModalOpen(false);
} catch (error) {
console.error('create role failed:', error);
message.error(t('role.createRoleFailed'));
} finally {
setLoading(false);
}
};
const genderOptions = [
{
key: GenderEnum.MALE,
label: t('agent.male'),
icon: 'https://oss.vidol.chat/docs/2024/12/8fc3717dd8f190f26cf204789d54b297.png',
},
{
key: GenderEnum.FEMALE,
label: t('agent.female'),
icon: 'https://oss.vidol.chat/docs/2024/12/6c45a6c800590acdb782ab905939fa04.png',
},
];
return (
<>
<ActionIcon
onClick={() => setIsModalOpen(true)}
icon={MessageSquarePlus}
size="large"
title={t('role.create')}
/>
<Modal
allowFullscreen
height={360}
title={t('role.selectGender')}
open={isModalOpen}
footer={
<Button
className={styles.createButton}
type="primary"
disabled={!selectedGender || loading}
onClick={handleCreateRole}
loading={loading}
>
{t('role.create')}
</Button>
}
width={480}
onCancel={() => !loading && setIsModalOpen(false)}
closable={!loading}
maskClosable={!loading}
>
<div className={styles.genderList}>
{genderOptions.map((option) => (
<ListItem
key={option.key}
avatar={<Avatar src={option.icon} size={120} />}
className={`${styles.genderCard} ${selectedGender === option.key ? 'selected' : ''}`}
onClick={() => setSelectedGender(option.key as GenderEnum)}
active={selectedGender === option.key}
title={option.label}
/>
))}
</div>
</Modal>
</>
);
}

@ -0,0 +1,42 @@
'use client';
import { Typography } from 'antd';
import { createStyles } from 'antd-style';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import CreateRole from './CreateRole';
export const useStyles = createStyles(({ css, token }) => ({
logo: css`
color: ${token.colorText};
fill: ${token.colorText};
`,
top: css`
position: sticky;
inset-block-start: 0;
height: 64px;
line-height: 64px;
`,
}));
const RoleHeader = memo(() => {
const { styles } = useStyles();
const { t } = useTranslation('role');
return (
<Flexbox className={styles.top} gap={16} padding={12}>
<Flexbox distribution={'space-between'} horizontal>
<Flexbox align={'center'} gap={4} horizontal>
<Typography.Title level={4} style={{ margin: 0 }}>
{t('role.myRole')}
</Typography.Title>
</Flexbox>
<CreateRole />
</Flexbox>
</Flexbox>
);
});
export default RoleHeader;

@ -0,0 +1,36 @@
import { Space, Tag } from 'antd';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { LOBE_VIDOL_DEFAULT_AGENT_ID } from '@/constants/agent';
import { useAgentStore } from '@/store/agent';
import ListItem from '../../ListItem';
const V = memo(() => {
const { t } = useTranslation('common');
const [activeId, activateAgent, defaultAgent] = useAgentStore((s) => [
s.currentIdentifier,
s.activateAgent,
s.defaultAgent,
]);
return (
<ListItem
onClick={() => {
activateAgent(LOBE_VIDOL_DEFAULT_AGENT_ID);
}}
active={activeId === LOBE_VIDOL_DEFAULT_AGENT_ID}
avatar={defaultAgent.meta.avatar}
title={
<Space align={'center'}>
{defaultAgent.meta.name}
<Tag color="geekblue">{t('defaultAssistant')}</Tag>
</Space>
}
description={defaultAgent.meta.description}
/>
);
});
export default V;

@ -0,0 +1,80 @@
import { ActionIcon } from '@lobehub/ui';
import { App, Dropdown, MenuProps } from 'antd';
import { MessageCircle, MoreVertical, Trash2 } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useTranslation } from 'react-i18next';
import { useAgentStore } from '@/store/agent';
import { useSessionStore } from '@/store/session';
interface ActionsProps {
id: string;
setOpen: (open: boolean) => void;
}
export default (props: ActionsProps) => {
const { id, setOpen } = props;
const { modal } = App.useApp();
const router = useRouter();
const { t } = useTranslation('role');
const [removeLocalAgent] = useAgentStore((s) => [s.removeLocalAgent]);
const currentAgent = useAgentStore((s) => s.getAgentById(id));
const [createSession, removeSessionByAgentId] = useSessionStore((s) => [
s.createSession,
s.removeSessionByAgentId,
]);
const items: MenuProps['items'] = [
{
icon: <MessageCircle />,
label: t('startChat'),
key: 'chat',
onClick: ({ domEvent }) => {
domEvent.stopPropagation();
if (!currentAgent) return;
createSession(currentAgent);
router.push('/chat');
},
},
{
danger: true,
icon: <Trash2 />,
key: 'delete',
label: t('delRole'),
onClick: ({ domEvent }) => {
domEvent.stopPropagation();
modal.confirm({
centered: true,
okButtonProps: { danger: true },
async onOk() {
await removeLocalAgent(id);
removeSessionByAgentId(id);
},
okText: t('delete', { ns: 'common' }),
cancelText: t('cancel', { ns: 'common' }),
title: t('delAlert'),
});
},
},
];
return (
<Dropdown
menu={{
items,
onClick: ({ domEvent }) => {
domEvent.stopPropagation();
},
}}
onOpenChange={(open) => setOpen(open)}
trigger={['click']}
>
<ActionIcon
icon={MoreVertical}
onClick={(e) => {
e.stopPropagation();
}}
/>
</Dropdown>
);
};

@ -0,0 +1,38 @@
import { memo, useMemo, useState } from 'react';
import { shallow } from 'zustand/shallow';
import { DEFAULT_AGENT_AVATAR_URL } from '@/constants/common';
import { useAgentStore } from '@/store/agent';
import ListItem from '../../ListItem';
import Actions from './Actions';
interface SessionItemProps {
id: string;
onClick: () => void;
}
const SessionItem = memo<SessionItemProps>(({ id, onClick }) => {
const [open, setOpen] = useState(false);
const [active] = useAgentStore((s) => [s.currentIdentifier === id]);
const [getAgentById] = useAgentStore((s) => [s.getAgentById]);
const agent = getAgentById(id);
const { name, description, avatar } = agent?.meta || {};
const actions = useMemo(() => <Actions id={id} setOpen={setOpen} />, [id]);
return (
<ListItem
actions={actions}
active={active}
avatar={avatar || DEFAULT_AGENT_AVATAR_URL}
description={description || agent?.systemRole}
onClick={onClick}
showAction={open}
title={name}
/>
);
}, shallow);
export default SessionItem;

@ -0,0 +1,44 @@
import { Skeleton } from 'antd';
import { createStyles } from 'antd-style';
import { Flexbox } from 'react-layout-kit';
const useStyles = createStyles(({ css }) => ({
avatar: css``,
paragraph: css`
height: 12px !important;
margin-top: 12px !important;
> li {
height: 12px !important;
}
`,
title: css`
height: 14px !important;
margin-top: 4px !important;
margin-bottom: 12px !important;
> li {
height: 14px !important;
}
`,
}));
const SkeletonList = () => {
const { styles } = useStyles();
const list = Array.from({ length: 4 }).fill('');
return (
<Flexbox gap={8} paddingInline={16}>
{list.map((_, index) => (
<Skeleton
active
avatar
key={index}
paragraph={{ className: styles.paragraph, rows: 1 }}
title={{ className: styles.title }}
/>
))}
</Flexbox>
);
};
export default SkeletonList;

@ -0,0 +1,53 @@
import { Empty } from 'antd';
import { createStyles } from 'antd-style';
import { isEqual } from 'lodash-es';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import LazyLoad from 'react-lazy-load';
import { agentSelectors, useAgentStore } from '@/store/agent';
import SessionItem from './Item';
const useStyles = createStyles(
({ css }) => css`
min-height: 70px;
`,
);
interface SessionListProps {
filter?: string;
}
const SessionList = memo<SessionListProps>(({ filter }) => {
const [filterAgentListIds, activateAgent, agentListIds] = useAgentStore(
(s) => [
agentSelectors.filterAgentListIds(s, filter),
s.activateAgent,
agentSelectors.agentListIds(s),
],
isEqual,
);
const { styles } = useStyles();
const { t } = useTranslation('role');
return (
<>
{filterAgentListIds.map((id) => (
<LazyLoad className={styles} key={id}>
<SessionItem
id={id}
onClick={() => {
activateAgent(id);
}}
/>
</LazyLoad>
))}
{agentListIds.length === 0 && (
<Empty description={t('noRole')} image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</>
);
});
export default SessionList;

@ -0,0 +1,47 @@
import { Avatar, List, ListItemProps } from '@lobehub/ui';
import { useHover } from 'ahooks';
import { createStyles } from 'antd-style';
import { memo, useMemo, useRef } from 'react';
const { Item } = List;
const useStyles = createStyles(({ css, token }) => {
return {
container: css`
position: relative;
margin-block: 2px;
padding-right: 16px;
padding-left: 8px;
border-radius: ${token.borderRadius}px;
`,
};
});
const ListItem = memo<ListItemProps & { avatar: string }>(
({ avatar, active, showAction, actions, ...props }) => {
const ref = useRef(null);
const isHovering = useHover(ref);
const { styles } = useStyles();
const avatarRender = useMemo(
() => <Avatar animation={isHovering} avatar={avatar} shape="circle" size={46} />,
[isHovering, avatar],
);
return (
<Item
actions={actions}
active={active}
avatar={avatarRender}
className={styles.container}
ref={ref}
showAction={actions && (isHovering || showAction)}
{...(props as any)}
/>
);
},
);
export default ListItem;

@ -0,0 +1,106 @@
import { Icon, SearchBar } from '@lobehub/ui';
import { Collapse } from 'antd';
import { createStyles } from 'antd-style';
import { ChevronDown } from 'lucide-react';
import dynamic from 'next/dynamic';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import SkeletonList from '@/components/SkeletonList';
import Elsa from './List/Elsa';
const List = dynamic(() => import('./List'), {
ssr: false,
loading: () => <SkeletonList style={{ marginTop: 8 }} />,
});
const useStyles = createStyles(({ css, token, prefixCls }) => ({
role: css`
overflow-y: auto;
height: 100%;
`,
list: css`
padding: 8px;
`,
container: css`
.${prefixCls}-collapse-header {
padding-inline: 8px !important;
color: ${token.colorTextDescription} !important;
border-radius: ${token.borderRadius}px !important;
&:hover {
color: ${token.colorText} !important;
background: ${token.colorFillTertiary};
.${prefixCls}-collapse-extra {
display: block;
}
}
}
.${prefixCls}-collapse-extra {
display: none;
}
.${prefixCls}-collapse-content {
border-radius: 0 !important;
}
.${prefixCls}-collapse-content-box {
padding: 0 !important;
}
`,
icon: css`
transition: all 100ms ${token.motionEaseOut};
`,
}));
const RoleList = () => {
const { styles } = useStyles();
const [searchName, setSearchName] = useState<string>();
const { t } = useTranslation('role');
return (
<div className={styles.role}>
<Flexbox style={{ padding: '0 8px 0' }} gap={8}>
<SearchBar
enableShortKey
onChange={(e) => {
setSearchName(e.target.value);
}}
placeholder={t('search', { ns: 'common' })}
shortKey="f"
spotlight
type={'block'}
value={searchName}
/>
</Flexbox>
<div className={styles.list}>
<Elsa />
<Collapse
bordered={false}
defaultActiveKey={'default'}
className={styles.container}
expandIcon={({ isActive }) => (
<Icon
className={styles.icon}
icon={ChevronDown}
size={{ fontSize: 16 }}
style={isActive ? {} : { rotate: '-90deg' }}
/>
)}
expandIconPosition={'end'}
ghost
size={'small'}
items={[
{
children: <List filter={searchName} />,
label: t('roleList'),
key: 'default',
},
]}
/>
</div>
</div>
);
};
export default RoleList;

@ -0,0 +1,62 @@
import { DraggablePanel } from '@lobehub/ui';
import { createStyles, useResponsive } from 'antd-style';
import { isEqual } from 'lodash-es';
import { rgba } from 'polished';
import { useEffect, useState } from 'react';
import { SIDEBAR_MAX_WIDTH, SIDEBAR_WIDTH } from '@/constants/token';
import { useGlobalStore } from '@/store/global';
import RoleHeader from './RoleHeader';
import RoleList from './RoleList';
const useStyles = createStyles(({ css, token }) => ({
content: css`
display: flex;
flex-direction: column;
height: 100% !important;
`,
sidebar: css`
z-index: 10;
background-color: ${rgba(token.colorBgLayout, 0.2)};
backdrop-filter: saturate(180%) blur(8px);
`,
}));
const SideBar = () => {
const { styles } = useStyles();
const [showRoleList, setRoleList] = useGlobalStore((s) => [s.showRoleList, s.setRoleList]);
const { md = true } = useResponsive();
const [cacheExpand, setCacheExpand] = useState<boolean>(Boolean(showRoleList));
const handleExpand = (expand: boolean) => {
if (isEqual(expand, Boolean(showRoleList))) return;
setRoleList(expand);
setCacheExpand(expand);
};
useEffect(() => {
if (md && cacheExpand) setRoleList(true);
if (!md) setRoleList(false);
}, [md, cacheExpand]);
return (
<DraggablePanel
className={styles.sidebar}
classNames={{ content: styles.content }}
maxWidth={SIDEBAR_MAX_WIDTH}
minWidth={SIDEBAR_WIDTH}
mode={md ? 'fixed' : 'float'}
placement={'left'}
onExpandChange={handleExpand}
expand={showRoleList}
>
<RoleHeader />
<RoleList />
</DraggablePanel>
);
};
export default SideBar;

@ -0,0 +1,17 @@
'use client';
import { ReactNode, memo } from 'react';
import AppLayout from '@/layout/AppLayout';
export interface LayoutProps {
children?: ReactNode;
}
const LayoutDesktop = (props: LayoutProps) => {
const { children } = props;
return <AppLayout>{children}</AppLayout>;
};
export default memo(LayoutDesktop);

@ -0,0 +1,38 @@
'use client';
import { Spin } from 'antd';
import dynamic from 'next/dynamic';
import React, { memo } from 'react';
import { Center, Flexbox } from 'react-layout-kit';
import SideBar from './SideBar';
import { useStyles } from './style';
const RoleEdit = dynamic(() => import('./RoleEdit'), {
ssr: false,
loading: () => (
<Center style={{ height: '100%', width: '100%' }}>
<Spin />
</Center>
),
});
const Role = () => {
const { styles } = useStyles();
return (
<Flexbox
flex={1}
height={'100%'}
width={'100%'}
horizontal
style={{ overflow: 'hidden', position: 'relative' }}
>
<SideBar />
<Flexbox className={styles.preview} horizontal>
<RoleEdit />
</Flexbox>
</Flexbox>
);
};
export default memo(Role);

@ -0,0 +1,18 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, cx, token }) => ({
preview: cx(
'role-preview',
css`
overflow: scroll;
width: 100%;
height: 100%;
padding: 0 ${token.paddingSM}px;
`,
),
container: css`
width: 1024px;
height: 100%;
margin: 0 auto;
`,
}));

@ -165,7 +165,7 @@ const Anthropic: ModelProviderCard = {
checkModel: 'claude-3-haiku-20240307',
description:
'Anthropic 是一家专注于人工智能研究和开发的公司,提供了一系列先进的语言模型,如 Claude 3.5 Sonnet、Claude 3 Sonnet、Claude 3 Opus 和 Claude 3 Haiku。这些模型在智能、速度和成本之间取得了理想的平衡适用于从企业级工作负载到快速响应的各种应用场景。Claude 3.5 Sonnet 作为其最新模型,在多项评估中表现优异,同时保持了较高的性价比。',
enabled: true,
enabled: false,
id: 'anthropic',
modelList: { showModelFetcher: true },
modelsUrl: 'https://docs.anthropic.com/en/docs/about-claude/models#model-names',

@ -178,7 +178,7 @@ const Google: ModelProviderCard = {
checkModel: 'gemini-2.0-flash',
description:
'Google 的 Gemini 系列是其最先进、通用的 AI模型由 Google DeepMind 打造专为多模态设计支持文本、代码、图像、音频和视频的无缝理解与处理。适用于从数据中心到移动设备的多种环境极大提升了AI模型的效率与应用广泛性。',
enabled: true,
enabled: false,
id: 'google',
modelList: { showModelFetcher: true },
modelsUrl: 'https://ai.google.dev/gemini-api/docs/models/gemini',

@ -331,6 +331,7 @@ const Ollama: ModelProviderCard = {
description:
'Ollama 提供的模型广泛涵盖代码生成、数学运算、多语种处理和对话互动等领域,支持企业级和本地化部署的多样化需求。',
id: 'ollama',
enabled: false,
modelList: { showModelFetcher: true },
modelsUrl: 'https://ollama.com/library',
name: 'Ollama',

@ -294,7 +294,7 @@ const OpenAI: ModelProviderCard = {
checkModel: 'gpt-4o-mini',
description:
'OpenAI 是全球领先的人工智能研究机构其开发的模型如GPT系列推动了自然语言处理的前沿。OpenAI 致力于通过创新和高效的AI解决方案改变多个行业。他们的产品具有显著的性能和经济性广泛用于研究、商业和创新应用。',
enabled: true,
enabled: false,
id: 'openai',
modelList: { showModelFetcher: true },
modelsUrl: 'https://platform.openai.com/docs/models',

@ -6,11 +6,11 @@ export const DEFAULT_LLM_CONFIG = genUserLLMConfig({
fetchOnClient: true,
},
ollama: {
enabled: true,
enabled: false,
fetchOnClient: true,
},
openai: {
enabled: true,
enabled: false,
},
});

@ -97,9 +97,9 @@ export const knowledgeBaseFiles = pgTable(
.references(() => files.id, { onDelete: 'cascade' })
.notNull(),
// userId: text('user_id')
// .references(() => users.id, { onDelete: 'cascade' })
// .notNull(),
userId: text('user_id')
.references(() => users.id, { onDelete: 'cascade' })
.notNull(),
createdAt: createdAt(),
},
@ -108,7 +108,7 @@ export const knowledgeBaseFiles = pgTable(
columns: [
t.knowledgeBaseId,
t.fileId,
// t.userId
t.userId
],
}),
}),

@ -48,6 +48,9 @@ export const fileChunks = pgTable(
fileId: varchar('file_id').references(() => files.id, { onDelete: 'cascade' }),
chunkId: uuid('chunk_id').references(() => chunks.id, { onDelete: 'cascade' }),
createdAt: createdAt(),
userId: text('user_id')
.references(() => users.id, { onDelete: 'cascade' })
.notNull(),
},
(t) => ({
pk: primaryKey({ columns: [t.fileId, t.chunkId] }),

@ -1,5 +1,5 @@
import { cosineDistance, count, sql } from 'drizzle-orm';
import { and, asc, desc, eq, inArray, isNull } from 'drizzle-orm/expressions';
import { and, asc, desc, eq, inArray, isNull, isNotNull} from 'drizzle-orm/expressions';
import { chunk } from 'lodash-es';
import { LobeChatDatabase } from '@/database/type';
@ -30,8 +30,12 @@ export class ChunkModel {
if (params.length === 0) return [];
const result = await trx.insert(chunks).values(params).returning();
const fileChunksData = result.map((chunk) => ({ chunkId: chunk.id, fileId }));
console.log('向量化-------------------------',this.userId)
const fileChunksData = result.map((chunk) => ({
chunkId: chunk.id,
fileId,
userId: this.userId,
}));
if (fileChunksData.length > 0) {
await trx.insert(fileChunks).values(fileChunksData);
@ -149,6 +153,7 @@ export class ChunkModel {
fileIds: string[] | undefined;
query: string;
}) => {
console.log('检索测试1111111111111')
const similarity = sql<number>`1 - (${cosineDistance(embeddings.embeddings, embedding)})`;
const data = await this.db
@ -163,10 +168,17 @@ export class ChunkModel {
type: chunks.type,
})
.from(chunks)
.leftJoin(embeddings, eq(chunks.id, embeddings.chunkId))
.leftJoin(fileChunks, eq(chunks.id, fileChunks.chunkId))
.innerJoin(embeddings, eq(chunks.id, embeddings.chunkId))
.innerJoin(fileChunks, eq(chunks.id, fileChunks.chunkId))
.leftJoin(files, eq(fileChunks.fileId, files.id))
.where(fileIds ? inArray(fileChunks.fileId, fileIds) : undefined)
.where(
fileIds
? inArray(fileChunks.fileId, fileIds)
: and(
isNotNull(fileChunks.fileId), // 排除无文件关联的记录
isNotNull(embeddings.embeddings) // 确保嵌入向量有效
)
)
.orderBy((t) => desc(t.similarity))
.limit(30);

@ -40,7 +40,6 @@ export class FileModel {
url: params.url,
});
}
const result = await trx
.insert(files)
.values({ ...params, userId: this.userId })
@ -51,7 +50,7 @@ export class FileModel {
if (params.knowledgeBaseId) {
await trx
.insert(knowledgeBaseFiles)
.values({ fileId: item.id, knowledgeBaseId: params.knowledgeBaseId });
.values({ fileId: item.id, knowledgeBaseId: params.knowledgeBaseId, userId: this.userId });
}
return item;

@ -52,7 +52,7 @@ const SearchItem = memo<ChunkItemProps>(({ text, pageNumber, type, similarity })
{text}
<Flexbox align={'center'} distribution={'space-between'} horizontal>
<Tag bordered={false}>{similarity.toFixed(2)}</Tag>
{/*<Tag bordered={false}>{similarity.toFixed(2)}</Tag>*/}
<Flexbox className={styles.pageNumber}> {pageNumber} </Flexbox>
</Flexbox>
</Flexbox>

@ -114,7 +114,7 @@ const FileRenderItem = memo<FileRenderItemProps>(
dayjs().diff(dayjs(createdAt), 'd') < 7
? dayjs(createdAt).fromNow()
: dayjs(createdAt).format('YYYY-MM-DD');
console.log(displayTime,'displayTime999999--------')
return (
<Flexbox
align={'center'}

@ -61,11 +61,11 @@ const FileList = memo<FileListProps>(({ knowledgeBaseId, category }) => {
});
const useFetchFileManage = useFileStore((s) => s.useFetchFileManage);
console.log(knowledgeBaseId,query,sortType,sorter,'--------82727277722------------')
const { data, isLoading } = useFetchFileManage({
category,
// category,
knowledgeBaseId,
q: query,
// q: query,
sortType,
sorter,
...viewConfig,

@ -27,6 +27,7 @@ const FilesSearchBar = memo<{ mobile?: boolean }>(({ mobile }) => {
}
}}
onPressEnter={() => {
console.log(setQuery,'keywords-------------------')
setQuery(keywords);
}}
placeholder={t('searchFilePlaceholder')}

@ -57,7 +57,7 @@ const PDFViewer = memo<PDFViewerProps>(({ url, fileId }) => {
);
const dataSource = data?.pages.flatMap((page) => page.items) || [];
console.log(url,'pdf9999999----------------')
return (
<Flexbox className={styles.container}>
<Flexbox

@ -27,6 +27,7 @@ const FileViewer = memo<FileViewerProps>(({ id, style, fileType, url, name }) =>
if (fileType?.toLowerCase() === 'pdf' || name?.toLowerCase().endsWith('.pdf')) {
return <PDFRenderer fileId={id} url={url} />;
}
console.log(url,fileType,'72727277--------11111')
return (
<DocViewer

@ -54,6 +54,7 @@ const ModelSwitchPanel = memo<PropsWithChildren>(({ children }) => {
const router = useRouter();
const enabledList = useEnabledChatModels();
console.log(enabledList,'773636363636')
const items = useMemo<ItemType[]>(() => {
const getModelItems = (provider: EnabledProviderWithModels) => {

@ -9,7 +9,7 @@ import { EnabledProviderWithModels } from '@/types/aiProvider';
export const useEnabledChatModels = (): EnabledProviderWithModels[] => {
const enabledList = useUserStore(modelProviderSelectors.modelProviderListForModelSelect, isEqual);
const enabledChatModelList = useAiInfraStore((s) => s.enabledChatModelList, isEqual);
console.log(enabledChatModelList,enabledList,'--------22222222--------')
if (isDeprecatedEdition) {
return enabledList;
}

@ -25,7 +25,10 @@ export const config = {
'/discover(.*)',
'/chat',
'/chat(.*)',
'/role',
'/role(.*)',
'/applicationset(.*)',
'/knowledge(.*)',
'/changelog(.*)',
'/settings(.*)',
'/files',

@ -115,10 +115,11 @@ export const chunkRouter = router({
model,
});
console.timeEnd('embedding');
console.log(embeddings,'3333333333333333')
console.log(input.fileIds,'555555555555555555')
return ctx.chunkModel.semanticSearch({
embedding: embeddings![0],
fileIds: input.fileIds,
// fileIds: input.fileIds,
query: input.query,
});
}),

@ -172,9 +172,11 @@ export const createAiProviderSlice: StateCreator<
useClientDataSWR<AiProviderRuntimeState | undefined>(
!isDeprecatedEdition ? [AiProviderSwrKey.fetchAiProviderRuntimeState, isLogin] : null,
async ([, isLogin]) => {
console.log(isLogin,'-----72727277272722-------',aiProviderService.getAiProviderRuntimeState())
if (isLogin) return aiProviderService.getAiProviderRuntimeState();
const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
console.log(DEFAULT_MODEL_PROVIDER_LIST,'-----919191919------')
return {
enabledAiModels: LOBE_DEFAULT_MODEL_LIST.filter((m) => m.enabled),
enabledAiProviders: DEFAULT_MODEL_PROVIDER_LIST.filter(
@ -199,13 +201,14 @@ export const createAiProviderSlice: StateCreator<
return uniqBy(models, 'id');
};
console.log(data.enabledAiProviders,'------yyyyyyyy-------')
// 3. 组装最终数据结构
const enabledChatModelList = data.enabledAiProviders.map((provider) => ({
...provider,
children: getModelListByType(provider.id, 'chat'),
name: provider.name || provider.id,
}));
console.log(enabledChatModelList,'-----jdjdhdhdh37377337------')
const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
set(

@ -86,7 +86,8 @@ export const createFileManageSlice: StateCreator<
get().toggleParsingIds(ids, false);
},
pushDockFileList: async (rawFiles, knowledgeBaseId) => {
const { dispatchDockFileList } = get();
console.log('pushDockFileList-------')
const { dispatchDockFileList, parseFilesToChunks } = get();
// 0. skip file in blacklist
const files = rawFiles.filter((file) => !FILE_UPLOAD_BLACKLIST.includes(file.name));
@ -99,12 +100,15 @@ export const createFileManageSlice: StateCreator<
});
const pools = files.map(async (file) => {
console.log('pools---------------')
await get().uploadWithProgress({
file,
knowledgeBaseId,
onStatusUpdate: dispatchDockFileList,
});
}).then((res)=> {
console.log(res,'res--------------------------------')
parseFilesToChunks([res.id])
})
await get().refreshFileList();
});

@ -7,7 +7,7 @@ export const createDevtools =
(name: string): typeof _devtools =>
(initializer) => {
let showDevtools = false;
console.log(name,'----88888-----')
// check url to show devtools
if (typeof window !== 'undefined') {
const url = new URL(window.location.href);

Loading…
Cancel
Save