feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
import { mergeConfig } from 'vite';
import svgr from 'vite-plugin-svgr';
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
viteFinal: config =>
mergeConfig(config, {
plugins: [
svgr({
svgrOptions: {
native: false,
},
}),
],
}),
};
export default config;

View File

@@ -0,0 +1,14 @@
/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View File

@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});

View File

@@ -0,0 +1,16 @@
# @coze-data/knowledge-modal-base
> Project template for react component with storybook.
## Features
- [x] eslint & ts
- [x] esm bundle
- [x] umd bundle
- [x] storybook
## Commands
- init: `rush update`
- dev: `npm run dev`
- build: `npm run build`

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["dist"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});

View File

@@ -0,0 +1,89 @@
{
"name": "@coze-data/knowledge-modal-base",
"version": "0.0.1",
"description": "coze knowledge modal base components",
"license": "Apache-2.0",
"author": "haozhenfei@bytedance.com",
"maintainers": [],
"exports": {
".": "./src/index.tsx",
"./create-knowledge-modal-v2": "./src/create-knowledge-modal-v2/index.ts",
"./create-knowledge-modal-v2/*": "./src/create-knowledge-modal-v2/*"
},
"main": "src/index.tsx",
"typesVersions": {
"*": {
"create-knowledge-modal-v2": [
"./src/create-knowledge-modal-v2/index.ts"
],
"create-knowledge-modal-v2/*": [
"./src/create-knowledge-modal-v2/*"
]
}
},
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-error": "workspace:*",
"@coze-arch/bot-flags": "workspace:*",
"@coze-arch/bot-http": "workspace:*",
"@coze-arch/bot-icons": "workspace:*",
"@coze-arch/bot-semi": "workspace:*",
"@coze-arch/bot-space-api": "workspace:*",
"@coze-arch/bot-studio-store": "workspace:*",
"@coze-arch/bot-tea": "workspace:*",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-arch/logger": "workspace:*",
"@coze-arch/report-events": "workspace:*",
"@coze-common/biz-components": "workspace:*",
"@coze-common/biz-tooltip-ui": "workspace:*",
"@coze-common/table-view": "workspace:*",
"@coze-data/e2e": "workspace:*",
"@coze-data/knowledge-resource-processor-core": "workspace:*",
"@coze-data/knowledge-stores": "workspace:*",
"@coze-data/reporter": "workspace:*",
"@coze-data/utils": "workspace:*",
"@coze-studio/bot-detail-store": "workspace:*",
"@coze-studio/components": "workspace:*",
"@douyinfe/semi-icons": "^2.36.0",
"@douyinfe/semi-ui": "~2.72.3",
"ahooks": "^3.7.8",
"classnames": "^2.3.2",
"copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.7",
"dompurify": "3.0.8",
"immer": "^10.0.3",
"lodash-es": "^4.17.21"
},
"devDependencies": {
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/dompurify": "3.0.5",
"@types/lodash-es": "^4.17.10",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"stylelint": "^15.11.0",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,9 @@
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.21054 1.5C2.98969 1.5 2 2.48969 2 3.71054V20.2896C2 21.5104 2.98969 22.5001 4.21054 22.5001H20.7896C22.0104 22.5001 23.0001 21.5104 23.0001 20.2896V3.71054C23.0001 2.48969 22.0104 1.5 20.7896 1.5H4.21054ZM7.71563 8.09264C7.71563 7.08544 8.53212 6.26895 9.53932 6.26895C10.5465 6.26895 11.363 7.08544 11.363 8.09264C11.363 9.09984 10.5465 9.91633 9.53932 9.91633C8.53212 9.91633 7.71563 9.09984 7.71563 8.09264ZM18.184 9.57697C18.184 9.24258 17.7825 9.07208 17.5419 9.30432L12.0841 14.5725L10.6802 13.2051C10.5331 13.0618 10.2985 13.0618 10.1514 13.2051L6.22038 17.034C5.97681 17.2713 6.14477 17.6844 6.48479 17.6844H13.7409L13.7481 17.6844L17.8051 17.6844C18.0144 17.6844 18.1841 17.5148 18.1841 17.3055L18.184 9.57697Z" fill="url(#paint0_linear_1170_181385)" style=""/>
<defs>
<linearGradient id="paint0_linear_1170_181385" x1="12.5001" y1="1.5" x2="12.5001" y2="22.5001" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC059" style="stop-color:#FFC059;stop-color:color(display-p3 1.0000 0.7547 0.3502);stop-opacity:1;"/>
<stop offset="1" stop-color="#F8A00E" style="stop-color:#F8A00E;stop-color:color(display-p3 0.9733 0.6270 0.0559);stop-opacity:1;"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,12 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="80" height="80" rx="8" fill="#FFB233" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M26.1842 22.5C24.1495 22.5 22.5 24.1495 22.5 26.1842V53.816C22.5 55.8507 24.1495 57.5002 26.1842 57.5002H53.816C55.8507 57.5002 57.5002 55.8507 57.5002 53.816V26.1842C57.5002 24.1495 55.8507 22.5 53.816 22.5H26.1842ZM32.026 33.4877C32.026 31.8091 33.3869 30.4482 35.0655 30.4482C36.7442 30.4482 38.105 31.8091 38.105 33.4877C38.105 35.1664 36.7442 36.5272 35.0655 36.5272C33.3869 36.5272 32.026 35.1664 32.026 33.4877ZM49.4734 35.9616C49.4734 35.4043 48.8042 35.1201 48.4032 35.5072L39.3068 44.2876L36.967 42.0085C36.7218 41.7696 36.3309 41.7696 36.0856 42.0085L29.534 48.39C29.128 48.7854 29.408 49.474 29.9746 49.474H42.0682L42.0802 49.474L48.8418 49.474C49.1907 49.474 49.4734 49.1913 49.4734 48.8425L49.4734 35.9616Z"
fill="white" />
<path
d="M80 52V72C80 76.4183 76.4183 80 72 80H52V80C54.2091 80 56 78.2091 56 76V64C56 59.5817 59.5817 56 64 56H76C78.2091 56 80 54.2091 80 52V52Z"
fill="#1D1C23" fill-opacity="0.3" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M67.4083 67.7147L70.8981 64.2239L71.7826 65.1083L68.2928 68.5991C68.0727 68.8192 67.4707 68.7958 67.2844 68.7828C67.2513 68.7805 67.2265 68.7556 67.2242 68.7225C67.2113 68.5362 67.1886 67.9344 67.4083 67.7147ZM72.4583 64.4324L72.0774 64.8134L71.1929 63.9289L71.5738 63.5479C71.8374 63.2843 72.2491 63.2686 72.4934 63.5129C72.7376 63.7571 72.7219 64.1688 72.4583 64.4324ZM64.2365 64.7374C64.2365 64.601 64.3472 64.4904 64.4836 64.4904H68.0673C68.3381 64.4904 68.5577 64.2708 68.5577 63.9999C68.5577 63.729 68.3381 63.5095 68.0673 63.5095L64.4836 63.5095C63.8054 63.5095 63.2556 64.0592 63.2556 64.7374L63.2556 71.5192C63.2556 72.1974 63.8054 72.7472 64.4836 72.7472H71.2654C71.9431 72.7472 72.4934 72.1985 72.4934 71.5198V67.9356C72.4934 67.6647 72.2738 67.4451 72.0029 67.4451C71.7321 67.4451 71.5125 67.6647 71.5125 67.9356V71.5198C71.5125 71.6558 71.4023 71.7663 71.2654 71.7663H64.4836C64.3472 71.7663 64.2365 71.6557 64.2365 71.5192L64.2365 64.7374Z"
fill="white" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,14 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="80" height="80" rx="8" fill="#35C566"
style="fill:#35C566;fill:color(display-p3 0.2078 0.7725 0.4000);fill-opacity:1;" />
<path
d="M80 52V72C80 76.4183 76.4183 80 72 80H52C54.2091 80 56 78.2091 56 76V64C56 59.5817 59.5817 56 64 56H76C78.2091 56 80 54.2091 80 52Z"
fill="#1D1C23" fill-opacity="0.3"
style="fill:#1D1C23;fill:color(display-p3 0.1137 0.1098 0.1373);fill-opacity:0.3;" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M67.4085 67.7145L70.8983 64.2237L71.7828 65.1082L68.293 68.599C68.0729 68.8191 67.4709 68.7957 67.2846 68.7826C67.2515 68.7803 67.2267 68.7555 67.2244 68.7223C67.2116 68.5361 67.1888 67.9343 67.4085 67.7145ZM72.4585 64.4323L72.0776 64.8133L71.1932 63.9288L71.5741 63.5478C71.8377 63.2842 72.2494 63.2685 72.4936 63.5127C72.7378 63.757 72.7221 64.1687 72.4585 64.4323ZM64.2368 64.7373C64.2368 64.6009 64.3474 64.4902 64.4838 64.4902H68.0675C68.3384 64.4902 68.558 64.2706 68.558 63.9998C68.558 63.7289 68.3384 63.5093 68.0675 63.5093L64.4838 63.5093C63.8056 63.5093 63.2559 64.0591 63.2559 64.7373L63.2559 71.5191C63.2559 72.1973 63.8056 72.7471 64.4838 72.7471H71.2656C71.9433 72.7471 72.4936 72.1983 72.4936 71.5197V67.9354C72.4936 67.6646 72.274 67.445 72.0031 67.445C71.7323 67.445 71.5127 67.6646 71.5127 67.9354V71.5197C71.5127 71.6556 71.4026 71.7662 71.2656 71.7662H64.4838C64.3474 71.7662 64.2368 71.6555 64.2368 71.5191L64.2368 64.7373Z"
fill="white" style="fill:white;fill-opacity:1;" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M25.0539 22.5005C24.5646 22.5005 24.168 22.8971 24.168 23.3864V49.2652C24.168 51.4493 25.0697 53.544 26.6747 55.0884C28.2798 56.6328 30.4567 57.5005 32.7265 57.5005H54.9487C55.438 57.5005 55.8346 57.1039 55.8346 56.6146V30.7358C55.8346 28.5516 54.9329 26.457 53.3279 24.9125C51.7229 23.3681 49.5459 22.5005 47.2761 22.5005H25.0539ZM44.7787 31.2504H31.2559V44.7731C31.2559 46.9697 33.0366 48.7504 35.2332 48.7504H48.7559V35.2276C48.7559 33.031 46.9752 31.2504 44.7787 31.2504ZM41.1336 38.8072V33.6367H44.7787C45.6573 33.6367 46.3696 34.349 46.3696 35.2276V38.8072H41.1336ZM33.6423 33.6367H38.7472V38.8072H33.6423V33.6367ZM33.6423 44.7731V41.1936H38.7472V46.364H35.2332C34.3546 46.364 33.6423 45.6517 33.6423 44.7731ZM46.3696 46.364H41.1336V41.1936H46.3696V46.364Z"
fill="white" style="fill:white;fill-opacity:1;" />
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,13 @@
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M6.06307 3C5.47595 3 5 3.47595 5 4.06307V35.1176C5 37.7386 6.08204 40.2522 8.00809 42.1055C9.93414 43.9588 12.5464 45 15.2703 45H41.9369C42.524 45 43 44.524 43 43.9369V12.8824C43 10.2614 41.918 7.74777 39.9919 5.89447C38.0659 4.04117 35.4536 3 32.7297 3H6.06307ZM29.7312 13.5H13.5039V29.7273C13.5039 32.3632 15.6407 34.5 18.2766 34.5H34.5039V18.2727C34.5039 15.6368 32.3671 13.5 29.7312 13.5ZM25.3571 22.5682V16.3636H29.7312C30.7855 16.3636 31.6403 17.2184 31.6403 18.2727V22.5682H25.3571ZM16.3675 16.3636H22.4935V22.5682H16.3675V16.3636ZM16.3675 29.7273V25.4319H22.4935V31.6364H18.2766C17.2223 31.6364 16.3675 30.7816 16.3675 29.7273ZM31.6403 31.6364H25.3571V25.4319H31.6403V31.6364Z"
fill="url(#paint0_linear_478_75498)" style="" />
<defs>
<linearGradient id="paint0_linear_478_75498" x1="24" y1="3" x2="24" y2="45" gradientUnits="userSpaceOnUse">
<stop stop-color="#5ED988"
style="stop-color:#5ED988;stop-color:color(display-p3 0.3692 0.8529 0.5338);stop-opacity:1;" />
<stop offset="1" stop-color="#2CC25F"
style="stop-color:#2CC25F;stop-color:color(display-p3 0.1708 0.7606 0.3715);stop-opacity:1;" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,14 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="80" height="80" rx="8" fill="#2288FF"
style="fill:#2288FF;fill:color(display-p3 0.1347 0.5317 1.0000);fill-opacity:1;" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M25.0541 22.4985C24.5647 22.4985 24.168 22.8953 24.168 23.3847V49.2701C24.168 51.4547 25.0699 53.55 26.6754 55.0948C28.2808 56.6396 30.4583 57.5074 32.7287 57.5074H54.9566C55.446 57.5074 55.8427 57.1107 55.8427 56.6213V30.7359C55.8427 28.5512 54.9408 26.456 53.3353 24.9112C51.7299 23.3664 49.5524 22.4985 47.282 22.4985H25.0541ZM31.6639 34.5015C31.6639 33.857 32.1864 33.3346 32.8309 33.3346H47.1679C47.8124 33.3346 48.3348 33.857 48.3348 34.5015C48.3348 35.146 47.8124 35.6685 47.1679 35.6685H32.8309C32.1864 35.6685 31.6639 35.146 31.6639 34.5015ZM31.6639 40.1697C31.6639 39.5252 32.1864 39.0027 32.8309 39.0027H47.1679C47.8124 39.0027 48.3348 39.5252 48.3348 40.1697C48.3348 40.8141 47.8124 41.3366 47.1679 41.3366H32.8309C32.1864 41.3366 31.6639 40.8141 31.6639 40.1697ZM32.8309 44.6708C32.1864 44.6708 31.6639 45.1933 31.6639 45.8378C31.6639 46.4823 32.1864 47.0047 32.8309 47.0047H40.4995C41.144 47.0047 41.6665 46.4823 41.6665 45.8378C41.6665 45.1933 41.144 44.6708 40.4995 44.6708H32.8309Z"
fill="white" style="fill:white;fill-opacity:1;" />
<path
d="M80 52V72C80 76.4183 76.4183 80 72 80H52C54.2091 80 56 78.2091 56 76V64C56 59.5817 59.5817 56 64 56H76C78.2091 56 80 54.2091 80 52Z"
fill="#1D1C23" fill-opacity="0.3"
style="fill:#1D1C23;fill:color(display-p3 0.1137 0.1098 0.1373);fill-opacity:0.3;" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M67.4085 67.7145L70.8983 64.2237L71.7828 65.1082L68.293 68.599C68.0729 68.8191 67.4709 68.7957 67.2846 68.7826C67.2515 68.7803 67.2267 68.7555 67.2244 68.7223C67.2116 68.5361 67.1888 67.9343 67.4085 67.7145ZM72.4585 64.4323L72.0776 64.8133L71.1932 63.9288L71.5741 63.5478C71.8377 63.2842 72.2494 63.2685 72.4936 63.5127C72.7378 63.757 72.7221 64.1687 72.4585 64.4323ZM64.2368 64.7373C64.2368 64.6009 64.3474 64.4902 64.4838 64.4902H68.0675C68.3384 64.4902 68.558 64.2706 68.558 63.9998C68.558 63.7289 68.3384 63.5093 68.0675 63.5093L64.4838 63.5093C63.8056 63.5093 63.2559 64.0591 63.2559 64.7373L63.2559 71.5191C63.2559 72.1973 63.8056 72.7471 64.4838 72.7471H71.2656C71.9433 72.7471 72.4936 72.1983 72.4936 71.5197V67.9354C72.4936 67.6646 72.274 67.445 72.0031 67.445C71.7323 67.445 71.5127 67.6646 71.5127 67.9354V71.5197C71.5127 71.6556 71.4026 71.7662 71.2656 71.7662H64.4838C64.3474 71.7662 64.2368 71.6555 64.2368 71.5191L64.2368 64.7373Z"
fill="white" style="fill:white;fill-opacity:1;" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,14 @@
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M6.07115 2.99902C5.48389 2.99902 5.00781 3.4751 5.00781 4.06237V35.1248C5.00781 37.7465 6.09013 40.2607 8.01667 42.1145C9.94321 43.9683 12.5562 45.0097 15.2807 45.0097H41.9541C42.5414 45.0097 43.0175 44.5336 43.0175 43.9464V12.8839C43.0175 10.2623 41.9352 7.74801 40.0086 5.89423C38.0821 4.04046 35.4691 2.99902 32.7446 2.99902H6.07115ZM14 17.4023C14 16.6289 14.627 16.002 15.4004 16.002H32.6047C33.3781 16.002 34.0051 16.6289 34.0051 17.4023C34.0051 18.1757 33.3781 18.8027 32.6047 18.8027H15.4004C14.627 18.8027 14 18.1757 14 17.4023ZM14 24.204C14 23.4306 14.627 22.8037 15.4004 22.8037H32.6047C33.3781 22.8037 34.0051 23.4306 34.0051 24.204C34.0051 24.9774 33.3781 25.6044 32.6047 25.6044H15.4004C14.627 25.6044 14 24.9774 14 24.204ZM15.4004 29.6054C14.627 29.6054 14 30.2324 14 31.0058C14 31.7792 14.627 32.4061 15.4004 32.4061H24.6027C25.3761 32.4061 26.0031 31.7792 26.0031 31.0058C26.0031 30.2324 25.3761 29.6054 24.6027 29.6054H15.4004Z"
fill="url(#paint0_linear_478_75494)" style="" />
<defs>
<linearGradient id="paint0_linear_478_75494" x1="24.0126" y1="2.99902" x2="24.0126" y2="45.0097"
gradientUnits="userSpaceOnUse">
<stop stop-color="#67A5FF"
style="stop-color:#67A5FF;stop-color:color(display-p3 0.4039 0.6471 1.0000);stop-opacity:1;" />
<stop offset="1" stop-color="#2273FF"
style="stop-color:#2273FF;stop-color:color(display-p3 0.1333 0.4510 1.0000);stop-opacity:1;" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
IconCozDicumentOnline,
IconCozDocument,
IconCozGoogleDriveFill,
IconCozLarkFill,
IconCozNotionFill,
IconCozPencilPaper,
IconCozWechatFill,
} from '@coze-arch/coze-design/icons';
import { DocumentSource } from '@coze-arch/bot-api/knowledge';
type TDocumentSource = {
[key in DocumentSource]: JSX.Element | string;
};
export const ICON_MAP: TDocumentSource = {
[DocumentSource.Document]: (
<IconCozDocument className="text-[16px] mr-[8px]" />
),
[DocumentSource.Web]: (
<IconCozDicumentOnline className="text-[16px] mr-[8px]" />
),
[DocumentSource.FrontCrawl]: (
<IconCozDicumentOnline className="text-[16px]" />
),
[DocumentSource.Notion]: (
<IconCozNotionFill className="text-[16px] mr-[8px]" />
),
[DocumentSource.FeishuWeb]: (
<IconCozLarkFill className="text-[16px] mr-[8px]" />
),
[DocumentSource.GoogleDrive]: (
<IconCozGoogleDriveFill className="text-[16px] mr-[8px]" />
),
[DocumentSource.OpenApi]: (
<IconCozPencilPaper className="text-[16px] mr-[8px]" />
),
[DocumentSource.Custom]: (
<IconCozPencilPaper className="text-[16px] mr-[8px]" />
),
[DocumentSource.ThirdParty]: '',
[DocumentSource.LarkWeb]: (
<IconCozLarkFill className="text-[16px] mr-[8px]" />
),
[DocumentSource.WeChat]: (
<IconCozWechatFill className="text-[16px] mr-[8px]" />
),
};

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect, useMemo, useState } from 'react';
import { I18n } from '@coze-arch/i18n';
import {
Checkbox,
CheckboxGroup,
Tag,
Typography,
} from '@coze-arch/coze-design';
import {
DocumentSource,
type DocumentInfo,
} from '@coze-arch/bot-api/knowledge';
/* eslint-disable no-restricted-imports */
import { getUpdateIntervalOptions } from '@coze-data/utils';
import { KnowledgeE2e } from '@coze-data/e2e';
import { type CheckboxEvent } from '@douyinfe/semi-ui/lib/es/checkbox';
import { ICON_MAP } from './const';
import styles from './index.module.less';
export interface IBatchCheckboxDocProps {
documentList: DocumentInfo[];
showTag?: boolean;
disabled?: boolean;
}
export const useBatchCheckboxDoc = (props: IBatchCheckboxDocProps) => {
const { documentList, showTag = false, disabled = false } = props;
const [checkedList, setCheckedList] = useState<string[]>([]);
const [indeterminate, setIndeterminate] = useState(true);
const [checkAll, setCheckAll] = useState(false);
const checkboxOptions = useMemo(
() =>
documentList?.filter(
item =>
![DocumentSource.Custom, DocumentSource.Document].includes(
item?.source_type as DocumentSource,
),
),
[documentList],
);
const plainOptions: string[] = useMemo(() => {
if (disabled) {
return ((checkboxOptions?.filter(item => !item.is_disconnect) || [])?.map(
doc => doc.document_id,
) || []) as string[];
}
return (checkboxOptions?.map(v => v.document_id) ?? []) as string[];
}, [checkboxOptions, disabled]);
// 初始化所有选中
const initCheckedList = () => {
setCheckedList(plainOptions);
setCheckAll(plainOptions?.length > 0);
};
useEffect(() => {
initCheckedList();
}, [documentList]);
const onCheckAllChange = (e: CheckboxEvent) => {
console.log(e);
setCheckedList(e?.target?.checked ? plainOptions : []);
setIndeterminate(false);
setCheckAll(e?.target?.checked ?? false);
};
const onChange = (list: string[]) => {
setCheckedList(list);
setIndeterminate(!!list.length && list.length < plainOptions.length);
setCheckAll(list.length === plainOptions.length);
};
const UpdateIntervalOptions = getUpdateIntervalOptions();
return {
node: (
<div className={styles['batch-checkbox-doc']}>
<div className={styles['batch-checkbox-doc-title']}>
<div
className="flex items-center height-[20px]"
data-testid={
KnowledgeE2e.SegmentDetailBatchFrequencyModalcheckboxAll
}
>
<Checkbox
indeterminate={indeterminate}
onChange={onCheckAllChange}
checked={checkAll}
>
<div className={styles['checked-all-title']}>
{I18n.t('knowledge_optimize_010')}
</div>
</Checkbox>
</div>
</div>
<div className={styles['batch-checkbox-doc-list']}>
<CheckboxGroup value={checkedList} onChange={onChange}>
{checkboxOptions?.map(item => (
<div
className={styles['batch-checkbox-doc-list-item']}
data-dtestid={`${KnowledgeE2e.SegmentDetailBatchFrequencyModalcheckboxItem}.${item.name}`}
>
<Checkbox
disabled={disabled && item.is_disconnect}
value={item.document_id}
style={{
display: 'flex',
alignItems: 'center',
}}
>
<div className={styles['batch-checkbox-doc-list-item-left']}>
{ICON_MAP[item?.source_type ?? DocumentSource.Web]}
<Typography.Text
className={
styles['batch-checkbox-doc-list-item-left-label']
}
ellipsis={{
showTooltip: {
opts: { content: item.name },
},
}}
style={{
width: 270,
}}
>
{item.name}
</Typography.Text>
{disabled && item.is_disconnect ? (
<Tag size="small" color="primary">
{I18n.t('knowledge_optimize_099')}
</Tag>
) : null}
</div>
</Checkbox>
{showTag &&
UpdateIntervalOptions?.find(
v => v.value === item.update_interval,
)?.label ? (
<Tag size="small" color="primary">
{
UpdateIntervalOptions?.find(
v => v.value === item.update_interval,
)?.label
}
</Tag>
) : null}
</div>
))}
</CheckboxGroup>
</div>
</div>
),
checkedList,
initCheckedList,
};
};

View File

@@ -0,0 +1,66 @@
/* stylelint-disable selector-class-pattern */
.batch-checkbox-doc {
&-title {
height: 20px;
margin-bottom: 8px;
font-weight: 600;
line-height: 20px;
color: var(--coz-fg-primary);
:global {
.semi-checkbox {
column-gap: 12px
}
}
}
.checked-all-title {
font-size: 14px;
font-weight: 600;
line-height: 20px;
color: var(--coz-fg-primary);
}
&-list {
overflow-y: auto;
max-height: 180px;
padding: 8px 12px;
border: 1px solid var(--coz-stroke-primary);
border-radius: 6px;
:global {
.semi-checkboxGroup-vertical {
row-gap: 8px;
}
.semi-checkbox {
column-gap: 12px
}
}
&-item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 32px;
&-left {
display: flex;
align-items: center;
color: var(--coz-fg-primary);
}
&-left-label {
color: var(--coz-fg-primary);
}
}
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { useBatchCheckboxDoc, type IBatchCheckboxDocProps } from './hooks';

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useRequest } from 'ahooks';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { useDataModalWithCoze } from '@coze-data/utils';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import {
useBatchCheckboxDoc,
type IBatchCheckboxDocProps,
} from '../batch-checkbox-doc';
import styles from './index.module.less';
export type TBatchFetchModalProps = IBatchCheckboxDocProps & {
onfinish: () => void;
};
export const useBatchFetchModal = (props: TBatchFetchModalProps) => {
const { documentList, onfinish } = props;
const { node, checkedList, initCheckedList } = useBatchCheckboxDoc({
documentList,
disabled: true,
});
const { loading, run } = useRequest(
async () => {
await KnowledgeApi.FetchWebUrl({
document_ids: checkedList,
});
},
{
onSuccess: () => {
// Toast.success(I18n.t('Update_success'));
close();
onfinish();
initCheckedList();
},
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeFetchWebUrl,
error,
});
},
manual: true,
},
);
const { modal, open, close } = useDataModalWithCoze({
title: I18n.t('knowledge_optimize_008'),
centered: true,
cancelText: I18n.t('Cancel'),
okText: I18n.t('knowledge_optimize_007'),
okButtonProps: {
disabled: !checkedList.length,
loading,
},
onOk: () => {
run();
},
onCancel: () => {
close();
initCheckedList();
},
});
return {
node: modal(
<div className={styles['batch-fetch']}>
<div className={styles['batch-fetch-title']}>
{I18n.t('knowledge_optimize_009')}
</div>
<div className={styles['batch-fetch-content']}>{node}</div>
</div>,
),
open,
close,
};
};

View File

@@ -0,0 +1,8 @@
.batch-fetch {
&-title {
margin-bottom: 16px;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { useBatchFetchModal, type TBatchFetchModalProps } from './hooks';

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useMemo, useState } from 'react';
import { useRequest } from 'ahooks';
import {
getUpdateIntervalOptions,
isFeishuOrLarkDocumentSource,
useDataModalWithCoze,
} from '@coze-data/utils';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { KnowledgeE2e } from '@coze-data/e2e';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { DocumentSource, UpdateType } from '@coze-arch/bot-api/knowledge';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { Select, Toast } from '@coze-arch/coze-design';
import {
useBatchCheckboxDoc,
type IBatchCheckboxDocProps,
} from '../batch-checkbox-doc';
import styles from './index.module.less';
export type TBatchFrequencyModalProps = IBatchCheckboxDocProps & {
onfinish: () => void;
};
export const useBatchFrequencyModal = (props: TBatchFrequencyModalProps) => {
const { documentList, onfinish } = props;
const [updateInterval, setUpdateInterval] = useState<number>(0);
const documents = useMemo(
() =>
documentList?.filter(
item =>
[
DocumentSource.Web,
DocumentSource.FrontCrawl,
// DocumentSource.ThirdParty,
].includes(item?.source_type as DocumentSource) ||
isFeishuOrLarkDocumentSource(item?.source_type),
),
[documentList],
);
const { node, checkedList, initCheckedList } = useBatchCheckboxDoc({
documentList: documents,
showTag: true,
});
const { loading, run } = useRequest(
async () => {
await KnowledgeApi.BatchUpdateDocument({
document_ids: checkedList,
update_rule: {
update_type: updateInterval ? UpdateType.Cover : UpdateType.NoUpdate,
update_interval: updateInterval,
},
});
},
{
onSuccess: () => {
Toast.success(I18n.t('Update_success'));
close();
onfinish();
initModalData();
},
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeBatchUpdateDocument,
error,
});
},
manual: true,
},
);
const initModalData = () => {
setUpdateInterval(0);
initCheckedList();
};
const { modal, open, close } = useDataModalWithCoze({
className: styles['batch-update-frequency-modal'],
title: I18n.t('knowledge_optimize_014'),
centered: true,
cancelText: I18n.t('Cancel'),
okText: I18n.t('knowledge_optimize_007'),
okButtonProps: {
disabled: !checkedList.length,
loading,
},
onOk: () => {
run();
},
onCancel: () => {
close();
initModalData();
},
});
return {
node: modal(
<div className={styles['batch-update-frequency']}>
<div className={styles['batch-update-frequency-title']}>
{I18n.t('knowledge_optimize_015')}
</div>
<div className={styles['batch-update-frequency-content']}>
<div className={styles['batch-update-frequency-content-select']}>
<div
className={styles['batch-update-frequency-content-select-label']}
>
{I18n.t('datasets_frequencyModal_frequency')}
<span className={styles['frequency-label-required']}>*</span>
</div>
<Select
data-testid={KnowledgeE2e.SegmentDetailBatchFrequencyModalSelect}
style={{ width: '100%' }}
value={updateInterval}
onChange={v => setUpdateInterval(v as number)}
placeholder={I18n.t('datasets_frequencyModal_frequency')}
optionList={getUpdateIntervalOptions()}
></Select>
</div>
{node}
</div>
</div>,
),
open,
close,
};
};

View File

@@ -0,0 +1,38 @@
.batch-update-frequency-modal {
:global {
.semi-modal-content {
color: var(--coz-fg-primary);
}
}
}
.batch-update-frequency {
&-title {
margin-bottom: 16px;
padding-bottom: 12px;
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: var(--coz-fg-primary);
}
&-content {
&-select {
margin-bottom: 22px;
&-label {
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: var(--coz-fg-primary);
}
}
.frequency-label-required {
color: var(--coz-fg-hglt-red);
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {
useBatchFrequencyModal,
type TBatchFrequencyModalProps,
} from './hooks';

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const KNOWLEDGE_UNIT_NAME_MAX_LEN = 100;
export const KNOWLEDGE_MAX_DOC_SIZE = 300;
export const KNOWLEDGE_MAX_SLICE_COUNT = 10000;
export const DATA_REFACTOR_CLASS_NAME = 'data-refactor';

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Radio, Typography } from '@coze-arch/coze-design';
interface SourceRadioProps {
title: string;
description: string;
icon?: React.ReactNode;
e2e?: string;
key?: string;
value?: string;
}
export const SourceRadio = (props: SourceRadioProps) => {
const { title, description, icon, e2e, key, value } = props;
return (
<Radio
key={key}
value={value}
extra={
<Typography.Text
type="tertiary"
ellipsis={{
showTooltip: {
opts: { content: description },
},
}}
style={{ lineHeight: '20px', width: 180 }}
>
{description}
</Typography.Text>
}
className="flex-[0_0_49%]"
data-testid={e2e}
>
{icon ? <div className="flex items-center mr-2">{icon}</div> : null}
{title}
</Radio>
);
};

View File

@@ -0,0 +1,88 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable selector-class-pattern */
.common-svg-icon(@size: 14px, @color: #3370ff) {
>svg {
width: @size;
height: @size;
>path {
fill: @color;
}
}
}
.radio-wrapper {
.radio-group {
display: flex;
gap: 8px;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.radio-item {
flex: 0 0 49%;
}
.displayNone {
display: none;
}
.form-line-wrapper {
position: relative;
}
.form-line {
margin-top: 24px;
}
.pt6 {
padding-top: 6px;
}
:global {
.semi-radioGroup-vertical {
row-gap: 8px;
}
.semi-radio-cardRadioGroup {
column-gap: 16px;
border: 1px solid var(--coz-stroke-plus);
&:hover {
background: var(--coz-mg-secondary-hovered);
}
&:active {
background: var(--coz-mg-secondary-pressed);
}
}
.semi-radio-cardRadioGroup_checked {
background: var(--coz-mg-hglt);
border: 1px solid var(--coz-stroke-hglt);
&:hover {
background: var(--coz-mg-hglt-hovered);
}
&:active {
background: var(--coz-mg-hglt-pressed);
}
}
}
.radio-icon {
display: flex;
align-items: center;
margin-right: 8px;
}
}
.icon-size-16 {
.common-svg-icon(16px, null);
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ReactNode } from 'react';
import { RadioGroup } from '@coze-arch/coze-design';
import type { RadioGroupProps } from '@coze-arch/coze-design';
import styles from './index.module.less';
export interface SourceSelectProps {
value: RadioGroupProps['value'];
onChange: RadioGroupProps['onChange'];
children: ReactNode;
}
export const SourceSelect = (props: SourceSelectProps) => {
const { value, onChange, children } = props;
return (
<div className={styles['radio-wrapper']}>
<RadioGroup
type="pureCard"
onChange={onChange}
value={value}
direction="horizontal"
name="format-type"
className={styles['radio-group']}
>
{children}
</RadioGroup>
</div>
);
};

View File

@@ -0,0 +1,43 @@
/* stylelint-disable declaration-no-important */
.create-knowledge-modal {
:global {
.semi-modal-content {
max-height: calc(100vh - 100px) !important;
}
}
}
.import-unit-type-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: bold;
line-height: 20px;
}
.create-form {
:global {
.semi-form-field {
padding-top: 0;
padding-bottom: 24px !important;
}
.semi-form-field-label {
margin-bottom: 0;
padding: 0 8px 6px;
font-size: 12px;
font-weight: 500;
font-style: normal;
line-height: 16px; /* 133.333% */
color: var(--coz-fg-secondary);
}
}
}
.upload-avatar-container {
width: auto;
}

View File

@@ -0,0 +1,205 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @coze-arch/max-line-per-function */
import { useEffect, useState } from 'react';
import { CozeFormTextArea, CozeInputWithCountField } from '@coze-data/utils';
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { PictureUpload } from '@coze-common/biz-components/picture-upload';
import { I18n } from '@coze-arch/i18n';
import { FormatType } from '@coze-arch/bot-api/memory';
import { type Icon } from '@coze-arch/bot-api/knowledge';
import { FileBizType, IconType } from '@coze-arch/bot-api/developer_api';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { useFormApi } from '@coze-arch/coze-design';
import { SelectFormatType } from '../../select-format-type/base';
import { ImportKnowledgeSourceSelect } from '../../import-knowledge-source-select/base';
import styles from './index.module.less';
export interface CozeKnowledgeAddTypeContentFormData {
name: string;
icon_uri?: Array<{
url: string;
uri: string;
uid: string;
isDefault?: boolean;
}>;
format_type: FormatType;
description: string;
}
export interface AddTypeContentProps {
onImportKnowledgeTypeChange?: (type: UnitType) => void;
onSelectFormatTypeChange?: (type: FormatType) => void;
}
export const CozeKnowledgeAddTypeContent = (params: AddTypeContentProps) => {
const { onImportKnowledgeTypeChange, onSelectFormatTypeChange } = params;
const formApi = useFormApi<CozeKnowledgeAddTypeContentFormData>();
// 使用 useState 保证能重新渲染
const [currentFormatType, setCurrentFormatType] = useState(FormatType.Text);
const [iconInfoGenerate, setIconInfoGenerate] = useState<{
name: string;
desc: string;
}>({
name: '',
desc: '',
});
const [coverIcon, setCoverIcon] = useState<Icon | undefined>({
uri: '',
url: '',
});
const fetchIcon = async (formatType: FormatType) => {
const { icon } = await KnowledgeApi.GetIcon({
format_type: formatType,
});
setCoverIcon(icon);
const currentCover = formApi.getValue('icon_uri');
if (!currentCover || currentCover[0]?.isDefault) {
formApi.setValue('icon_uri', [
{
url: icon?.url ?? '',
uri: icon?.uri ?? '',
uid: icon?.uri ?? '',
isDefault: true,
},
]);
}
};
const [unitType, setUnitType] = useState<UnitType>(UnitType.TEXT_DOC);
useEffect(() => {
fetchIcon(currentFormatType);
if (currentFormatType === FormatType.Text) {
setUnitType(UnitType.TEXT_DOC);
} else if (currentFormatType === FormatType.Table) {
setUnitType(UnitType.TABLE_DOC);
} else if (currentFormatType === FormatType.Image) {
setUnitType(UnitType.IMAGE_FILE);
}
}, [currentFormatType]);
useEffect(() => {
if (!unitType) {
return;
}
onImportKnowledgeTypeChange?.(unitType);
}, [unitType]);
return (
<div data-testid={KnowledgeE2e.CreateKnowledgeModal}>
<SelectFormatType
field="format_type"
noLabel
onChange={(type: FormatType) => {
setCurrentFormatType(type);
formApi.setValue('format_type', type);
onSelectFormatTypeChange?.(type);
}}
/>
<CozeInputWithCountField
data-testid={KnowledgeE2e.CreateKnowledgeModalNameInput}
field="name"
label={I18n.t('datasets_model_create_name')}
maxLength={100}
onChange={(value: string) => {
setIconInfoGenerate(prev => ({
...prev,
name: value?.trim() || '',
}));
}}
rules={[
{
required: true,
whitespace: true,
message: I18n.t('dataset-name-empty-tooltip'),
},
{
pattern: /^[^"'`\\]+$/,
message: I18n.t('dataset-name-has-wrong-word-tooltip'),
},
]}
placeholder={I18n.t('datasets_model_create_name_placeholder')}
/>
<CozeFormTextArea
field="description"
data-testid={KnowledgeE2e.CreateKnowledgeModalDescInput}
// className={s['textarea-multi-line']}
label={I18n.t('datasets_model_create_description')}
autosize={{ minRows: 1, maxRows: 2 }}
maxCount={2000}
maxLength={2000}
placeholder={I18n.t('datasets_model_create_description_placeholder')}
onChange={(value: string) => {
setIconInfoGenerate(prev => ({
...prev,
desc: value?.trim() || '',
}));
}}
/>
<div
className="semi-form-field"
x-label-pos="top"
x-field-id="name"
x-extra-pos="bottom"
>
<label className="semi-form-field-label semi-form-field-label-left">
<div className="semi-form-field-label-text" x-semi-prop="label">
{I18n.t('create-dataset-import-type')}
</div>
</label>
<ImportKnowledgeSourceSelect
formatType={currentFormatType}
initValue={unitType}
onChange={setUnitType}
/>
</div>
<PictureUpload
label={I18n.t('datasets_model_create_avatar')}
field="icon_uri"
testId={KnowledgeE2e.CreateKnowledgeModalAvatarUploader}
fileBizType={FileBizType.BIZ_DATASET_ICON}
uploadClassName={styles['upload-avatar-container']}
iconType={IconType.Dataset}
generateInfo={iconInfoGenerate}
generateTooltip={{
generateBtnText: I18n.t(
'dataset_create_knowledge_generate_avatar_tips',
),
contentNotLegalText: I18n.t(
'dataset_create_knowledge_generate_content_tips',
),
}}
initValue={[
{
url: coverIcon?.url,
uri: coverIcon?.uri,
isDefault: true,
},
]}
/>
</div>
);
};

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type ImportKnowledgeSourceSelectModuleProps } from '../module';
import { ImageLocal } from '../../import-knowledge-source/image-local';
import { SourceSelect } from '../../../components/source-select';
export const ImageKnowledgeSourceSelect = (
props: Omit<ImportKnowledgeSourceSelectModuleProps, 'formatType'>,
) => {
const { initValue, onChange } = props;
return (
<SourceSelect
value={initValue}
onChange={e => {
onChange(e.target.value);
}}
>
<ImageLocal />
</SourceSelect>
);
};

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormatType } from '@coze-arch/bot-api/knowledge';
import { type ImportKnowledgeSourceSelectModule } from '../module';
import { TextKnowledgeSourceSelect } from './text-knowledge-source-select';
import { TableKnowledgeSourceSelect } from './table-knowledge-source-select';
import { ImageKnowledgeSourceSelect } from './image-knowledge-source-select';
export const ImportKnowledgeSourceSelect: ImportKnowledgeSourceSelectModule =
props => {
const { formatType, initValue, onChange } = props;
if (formatType === FormatType.Text) {
return (
<TextKnowledgeSourceSelect initValue={initValue} onChange={onChange} />
);
}
if (formatType === FormatType.Image) {
return (
<ImageKnowledgeSourceSelect initValue={initValue} onChange={onChange} />
);
}
if (formatType === FormatType.Table) {
return (
<TableKnowledgeSourceSelect initValue={initValue} onChange={onChange} />
);
}
};

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type ImportKnowledgeSourceSelectModuleProps } from '../module';
import { TableLocal } from '../../import-knowledge-source/table-local';
import { TableCustom } from '../../import-knowledge-source/table-custom';
import { SourceSelect } from '../../../components/source-select';
export const TableKnowledgeSourceSelect = (
props: Omit<ImportKnowledgeSourceSelectModuleProps, 'formatType'>,
) => {
const { initValue, onChange } = props;
return (
<SourceSelect
value={initValue}
onChange={e => {
onChange(e.target.value);
}}
>
<TableLocal />
<TableCustom />
</SourceSelect>
);
};

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type ImportKnowledgeSourceSelectModuleProps } from '../module';
import { TextLocal } from '../../import-knowledge-source/text-local';
import { TextCustom } from '../../import-knowledge-source/text-custom';
import { SourceSelect } from '../../../components/source-select';
export const TextKnowledgeSourceSelect = (
props: Omit<ImportKnowledgeSourceSelectModuleProps, 'formatType'>,
) => {
const { initValue, onChange } = props;
return (
<SourceSelect
value={initValue}
onChange={e => {
onChange(e.target.value);
}}
>
<TextLocal />
<TextCustom />
</SourceSelect>
);
};

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
import { type FormatType } from '@coze-arch/bot-api/knowledge';
export interface ImportKnowledgeSourceSelectModuleProps {
formatType: FormatType;
initValue?: UnitType;
onChange: (val: UnitType) => void;
}
export type ImportKnowledgeSourceSelectModule =
React.ComponentType<ImportKnowledgeSourceSelectModuleProps>;

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozDocument } from '@coze-arch/coze-design/icons';
import { SourceRadio } from '../../../components/source-radio';
export const ImageLocal = () => (
<SourceRadio
title={I18n.t('knowledge_photo_002')}
description={I18n.t('knowledge_photo_003')}
icon={<IconCozDocument className="w-4 h-4" />}
e2e={KnowledgeE2e.CreateKnowledgeModalPhotoImgRadio}
key={UnitType.IMAGE_FILE}
value={UnitType.IMAGE_FILE}
/>
);

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { TableCustom } from './table-custom';
export { TableLocal } from './table-local';
export { TextCustom } from './text-custom';
export { TextLocal } from './text-local';
export { ImageLocal } from './image-local';
export { ImportKnowledgeSourceModule } from './module';

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type RadioGroupProps } from '@coze-arch/coze-design';
export type ImportKnowledgeSourceModule = React.ComponentType<RadioGroupProps>;

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozPencilPaper } from '@coze-arch/coze-design/icons';
import { SourceRadio } from '../../../components/source-radio';
export const TableCustom = () => (
<SourceRadio
title={I18n.t('datasets_createFileModel_step1_TabCustomTitle')}
description={I18n.t('datasets_createFileModel_step1_TabCustomDescription')}
icon={<IconCozPencilPaper className="w-4 h-4" />}
e2e={KnowledgeE2e.CreateKnowledgeModalTableCustomRadio}
key={UnitType.TABLE_CUSTOM}
value={UnitType.TABLE_CUSTOM}
/>
);

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozDocument } from '@coze-arch/coze-design/icons';
import { SourceRadio } from '../../../components/source-radio';
export const TableLocal = () => (
<SourceRadio
title={I18n.t('datasets_createFileModel_step1_TabLocalTitle')}
description={I18n.t('datasets_createFileModel_step1_TabLocalDescription')}
icon={<IconCozDocument className="w-4 h-4" />}
e2e={KnowledgeE2e.CreateKnowledgeModalTableLocalRadio}
key={UnitType.TABLE_DOC}
value={UnitType.TABLE_DOC}
/>
);

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozPencilPaper } from '@coze-arch/coze-design/icons';
import { SourceRadio } from '../../../components/source-radio';
export const TextCustom = () => (
<SourceRadio
title={I18n.t('datasets_createFileModel_step1_CustomTitle')}
description={I18n.t('datasets_createFileModel_step1_CustomDescription')}
icon={<IconCozPencilPaper className="w-4 h-4" />}
e2e={KnowledgeE2e.CreateKnowledgeModalTextCustomRadio}
key={UnitType.TEXT_CUSTOM}
value={UnitType.TEXT_CUSTOM}
/>
);

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozDocument } from '@coze-arch/coze-design/icons';
import { SourceRadio } from '../../../components/source-radio';
export const TextLocal = () => (
<SourceRadio
title={I18n.t('datasets_createFileModel_step1_LocalTitle')}
description={I18n.t('datasets_createFileModel_step1_LocalDescription')}
icon={<IconCozDocument className="w-4 h-4" />}
e2e={KnowledgeE2e.CreateKnowledgeModalTextLocalRadio}
key={UnitType.TEXT_DOC}
value={UnitType.TEXT_DOC}
/>
);

View File

@@ -0,0 +1,74 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable selector-class-pattern */
/* stylelint-disable max-nesting-depth */
.select-format-type {
display: flex !important;
gap: 8px;
padding-top: 0 !important;
padding-bottom: 0 !important;
:global {
.info-icon {
position: absolute;
top: 10px;
right: 10px;
display: none;
color: var(--coz-fg-dim);
}
.semi-radio-cardRadioGroup {
padding: 10px 16px;
border: 1px solid var(--coz-stroke-plus);
&:hover {
background: var(--coz-mg-secondary-hovered);
}
&:active {
background: var(--coz-mg-secondary-pressed);
}
}
.semi-radio-cardRadioGroup_checked {
background: var(--coz-mg-hglt);
border: 1px solid var(--coz-stroke-hglt);
&:hover {
background: var(--coz-mg-hglt-hovered);
}
&:active {
background: var(--coz-mg-hglt-pressed);
}
}
.semi-radio {
position: relative;
display: flex;
flex: 1;
justify-content: center;
&:hover {
.info-icon {
display: block;
}
}
.semi-radio-content {
height: 48px !important;
.semi-radio-addon {
display: flex !important;
flex-direction: column;
.radio-logo {
height: 28px;
}
}
}
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { withField } from '@coze-arch/bot-semi';
import { FormatType } from '@coze-arch/bot-api/memory';
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
import { Radio, RadioGroup, Tooltip } from '@coze-arch/coze-design';
import type {
SelectFormatTypeModule,
SelectFormatTypeModuleProps,
} from '../module';
// eslint-disable-next-line @coze-arch/no-deep-relative-import
import { ReactComponent as TextKnowledgeLogo } from '../../../../assets/text-knowledge.svg';
// eslint-disable-next-line @coze-arch/no-deep-relative-import
import { ReactComponent as TableKnowledgeLogo } from '../../../../assets/table-knowledge.svg';
// eslint-disable-next-line @coze-arch/no-deep-relative-import
import { ReactComponent as ImageKnowledgeLogo } from '../../../../assets/image-knowledge.svg';
import styles from './index.module.less';
const SelectFormatTypeComponent: React.FC<
SelectFormatTypeModuleProps
> = props => {
const { onChange } = props;
return (
<RadioGroup
defaultValue={FormatType.Text}
onChange={v => {
onChange?.(v.target.value);
}}
type="pureCard"
direction="horizontal"
className={styles['select-format-type']}
>
<Radio
value={FormatType.Text}
key={FormatType.Text}
data-testid={KnowledgeE2e.CreateKnowledgeModalTextRadioGroup}
>
<div className="radio-logo">
<TextKnowledgeLogo />
</div>
<div>{I18n.t('create-knowledge-text-type')}</div>
</Radio>
<Radio
value={FormatType.Table}
key={FormatType.Table}
data-testid={KnowledgeE2e.CreateKnowledgeModalTableRadioGroup}
>
<div className="radio-logo">
<TableKnowledgeLogo />
</div>
<div>{I18n.t('create-knowledge-table-type')}</div>
<Tooltip content={I18n.t('knowledge_table_nl2sql_tooltip')}>
<IconCozInfoCircle className={'info-icon'} />
</Tooltip>
</Radio>
<Radio
value={FormatType.Image}
key={FormatType.Image}
data-testid={KnowledgeE2e.CreateKnowledgeModalPhotoRadioGroup}
>
<div className="radio-logo">
<ImageKnowledgeLogo />
</div>
<div>{I18n.t('knowledge_photo_001')}</div>
</Radio>
</RadioGroup>
);
};
export const SelectFormatType: SelectFormatTypeModule = withField(
SelectFormatTypeComponent,
);

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type React from 'react';
import type { FormatType } from '@coze-arch/bot-api/memory';
import type { CommonFieldProps } from '@coze-arch/coze-design';
export interface SelectFormatTypeModuleProps {
onChange: (type: FormatType) => void;
}
export type SelectFormatTypeModule = React.ComponentType<
SelectFormatTypeModuleProps & Omit<CommonFieldProps, 'change'>
>;

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {
CozeKnowledgeAddTypeContent,
type CozeKnowledgeAddTypeContentFormData,
} from './features/add-type-content/coze-knowledge';
export {
TableCustom,
TableLocal,
TextCustom,
TextLocal,
ImageLocal,
} from './features/import-knowledge-source';
export { SourceRadio } from './components/source-radio';
export { ImportKnowledgeSourceModule } from './features/import-knowledge-source';
export { SelectFormatType } from './features/select-format-type/base';
export {
type ImportKnowledgeSourceSelectModule,
type ImportKnowledgeSourceSelectModuleProps,
} from './features/import-knowledge-source-select/module';
export { SourceSelect } from './components/source-select';

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import classNames from 'classnames';
import { useRequest } from 'ahooks';
import { useDataModalWithCoze } from '@coze-data/utils';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { type ButtonColor } from '@coze-arch/coze-design/types';
import { Toast } from '@coze-arch/coze-design';
import { CustomError } from '@coze-arch/bot-error';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { DATA_REFACTOR_CLASS_NAME } from '../constant';
export interface IDeleteUnitModalProps {
docId?: string;
onDel?: () => void;
}
export const useDeleteUnitModal = ({ docId, onDel }: IDeleteUnitModalProps) => {
const { run, loading } = useRequest(
() => {
if (!docId) {
throw new CustomError(
REPORT_EVENTS.KnowledgeDeleteDocument,
`${REPORT_EVENTS.KnowledgeDeleteDocument}: missing doc_id`,
);
}
return KnowledgeApi.DeleteDocument({
document_ids: [docId],
});
},
{
onSuccess: () => {
close?.();
onDel?.();
},
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeDeleteDocument,
error,
});
Toast.error({
content: 'update fail',
showClose: false,
});
},
manual: true,
},
);
const { modal, open, close } = useDataModalWithCoze({
width: 320,
title: I18n.t('kl2_007'),
cancelText: I18n.t('Cancel'),
okText: I18n.t('Delete'),
showCloseIcon: false,
okButtonColor: 'red' as ButtonColor,
okButtonProps: {
loading,
type: 'danger',
},
onOk: () => {
run();
},
onCancel: () => close(),
});
return {
node: modal(
<div className={classNames('coz-fg-secondary', DATA_REFACTOR_CLASS_NAME)}>
{I18n.t('dataset_detail_table_deleteModel_description')}
</div>,
),
delete: open,
close,
};
};

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { useDeleteUnitModal, type IDeleteUnitModalProps } from './hooks';

View File

@@ -0,0 +1,20 @@
/* stylelint-disable declaration-no-important */
.create-dataset-modal {
:global {
.semi-form-field {
padding-top: 0;
padding-bottom: 24px !important;
}
.semi-form-field-label {
margin-bottom: 0;
padding: 0 8px 6px;
font-size: 12px;
font-weight: 500;
font-style: normal;
line-height: 16px; /* 133.333% */
color: var(--coz-fg-secondary);
}
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useCallback, useRef, useState } from 'react';
import {
CozeFormTextArea,
CozeInputWithCountField,
useDataModalWithCoze,
} from '@coze-data/utils';
import { KnowledgeE2e } from '@coze-data/e2e';
import {
PictureUpload,
type RenderAutoGenerateParams,
} from '@coze-common/biz-components/picture-upload';
import { I18n } from '@coze-arch/i18n';
import { FileBizType, IconType } from '@coze-arch/bot-api/developer_api';
import { Form } from '@coze-arch/coze-design';
import { DATA_REFACTOR_CLASS_NAME } from '../constant';
import styles from './index.module.less';
export interface EditModalData {
id?: string;
icon_uri?: { uid?: string; url?: string }[];
name?: string;
description?: string;
}
export interface UseEditKnowledgeModalProps {
onOk: (formValue: EditModalData) => void;
renderAutoGenerateKnowledgeIcon?: (
props: RenderAutoGenerateParams,
) => React.ReactNode;
}
export function useEditKnowledgeModal(props: UseEditKnowledgeModalProps) {
const formRef = useRef<Form<EditModalData>>(null);
const initVal = useRef<EditModalData>({});
const id = useRef<string | undefined>('');
const [contentCheckErrorMsg, setContentCheckErrorMsg] = useState('');
const [iconInfoGenerate, setIconInfoGenerate] = useState<{
name: string;
desc: string;
}>({
name: '',
desc: '',
});
const getFormValues = () => {
const values = formRef.current?.formApi.getValues();
if (values) {
return { ...values, icon_uri: values.icon_uri };
}
};
const { open, close, modal, disableOk, enableOk, canOk } =
useDataModalWithCoze({
title: I18n.t('datasets_editProfile_title'),
cancelText: I18n.t('Cancel'),
centered: true,
okText: I18n.t('Confirm'),
onOk: () => {
props.onOk({
...getFormValues(),
id: id.current,
});
close();
},
onCancel: () => {
close();
},
});
const validateNL2SqlName = useCallback(() => {
const currentUnitName = formRef.current?.formApi.getValue('name');
const notationReg = /["'`\\]+/g;
if (!currentUnitName) {
return I18n.t('dataset-name-empty-tooltip');
}
if (notationReg.test(currentUnitName)) {
return I18n.t('dataset-name-has-wrong-word-tooltip');
}
return '';
}, []);
return {
node: modal(
<div
className={`${DATA_REFACTOR_CLASS_NAME} ${styles['create-dataset-modal']}`}
>
<Form<EditModalData>
ref={formRef}
showValidateIcon={false}
initValues={initVal.current}
onValueChange={({ name, description }) => {
setIconInfoGenerate({
name: name?.trim() || '',
desc: description?.trim() || '',
});
setContentCheckErrorMsg('');
if (!name || validateNL2SqlName()) {
disableOk();
} else if (!canOk) {
enableOk();
}
}}
>
<CozeInputWithCountField
data-testid={KnowledgeE2e.KnowledgeEditModalNameInput}
field="name"
label={I18n.t('datasets_model_create_name')}
maxLength={100}
rules={[
{
required: true,
message: I18n.t('datasets_model_create_name_placeholder'),
},
]}
placeholder={I18n.t('datasets_model_create_name_placeholder')}
validate={validateNL2SqlName}
/>
<CozeFormTextArea
data-testid={KnowledgeE2e.KnowledgeEditModalDescInput}
field="description"
label={I18n.t('datasets_model_create_description')}
rows={2}
maxCount={2000}
maxLength={2000}
placeholder={I18n.t(
'datasets_model_create_description_placeholder',
)}
/>
{contentCheckErrorMsg ? (
<Form.ErrorMessage error={contentCheckErrorMsg} />
) : null}
<PictureUpload
label={I18n.t('datasets_model_create_avatar')}
withAutoGenerate
renderAutoGenerate={props.renderAutoGenerateKnowledgeIcon}
field="icon_uri"
generateInfo={iconInfoGenerate}
generateTooltip={{
generateBtnText: I18n.t(
'dataset_create_knowledge_generate_avatar_tips',
),
contentNotLegalText: I18n.t(
'dataset_create_knowledge_generate_content_tips',
),
}}
initValue={initVal.current.icon_uri}
iconType={IconType.Dataset}
fileBizType={FileBizType.BIZ_DATASET_ICON}
/>
</Form>
</div>,
),
open,
edit: (info: EditModalData) => {
initVal.current = info;
id.current = info.id;
setIconInfoGenerate({
name: info.name || '',
desc: info.description || '',
});
open();
},
close: () => {
close();
},
};
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect, useState } from 'react';
import { useDataModalWithCoze } from '@coze-data/utils';
import { I18n } from '@coze-arch/i18n';
import { TextArea } from '@coze-arch/coze-design';
export interface IEditUnitNameProps {
name: string;
onOk?: (val: string) => void;
}
export const useEditUnitNameModal = (props: IEditUnitNameProps) => {
const { name, onOk } = props;
const [value, setValue] = useState(name);
useEffect(() => {
setValue(name);
}, [name]);
const onColse = () => {
close();
setValue(name);
};
const { modal, open, close } = useDataModalWithCoze({
width: 480,
title: I18n.t('knowledge_edit_unit_name_title'),
cancelText: I18n.t('Cancel'),
okText: I18n.t('Confirm'),
okButtonProps: {
disabled: !value,
},
onOk: () => {
onColse();
onOk?.(value);
},
onCancel: onColse,
});
return {
node: modal(
<TextArea
value={value}
onChange={setValue}
maxCount={100}
maxLength={100}
rows={3}
/>,
),
open,
};
};

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { useEditUnitNameModal } from './hooks';

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useRequest } from 'ahooks';
import { useDataModalWithCoze } from '@coze-data/utils';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { type DocumentInfo } from '@coze-arch/bot-api/knowledge';
import { KnowledgeApi } from '@coze-arch/bot-api';
export interface IDeleteUnitModalProps {
documentInfo: DocumentInfo;
onfinish: () => void;
}
export const useFetchSliceModal = ({
documentInfo,
onfinish,
}: IDeleteUnitModalProps) => {
const { loading, run } = useRequest(
async () => {
await KnowledgeApi.FetchWebUrl({
document_ids: documentInfo.document_id
? [documentInfo.document_id]
: [],
});
},
{
onSuccess: () => {
// Toast.success(I18n.t('Update_success'));
close();
onfinish();
},
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeFetchWebUrl,
error,
});
},
manual: true,
},
);
const { modal, open, close } = useDataModalWithCoze({
width: 320,
title: I18n.t('knowledge_optimize_005'),
cancelText: I18n.t('Cancel'),
okText: I18n.t('knowledge_optimize_007'),
okButtonColor: 'yellow',
okButtonProps: {
loading,
type: 'warning',
},
onOk: () => {
run();
},
onCancel: () => close(),
});
return {
node: modal(
<div className="coz-fg-secondary">
{I18n.t('knowledge_optimize_006')}
</div>,
),
open,
close,
};
};

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { useFetchSliceModal } from './hooks';

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types='@coze-arch/bot-typings' />

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {
useEditKnowledgeModal,
type EditModalData,
type UseEditKnowledgeModalProps,
} from './edit-knowledge-modal';
export {
DATA_REFACTOR_CLASS_NAME,
KNOWLEDGE_UNIT_NAME_MAX_LEN,
KNOWLEDGE_MAX_DOC_SIZE,
KNOWLEDGE_MAX_SLICE_COUNT,
} from './constant';
export { RagModeConfiguration } from './rag-mode-configuration';
export { type IDataSetInfo } from './rag-mode-configuration/type';
export { useSliceDeleteModal } from './slice-delete-modal';
export {
useDeleteUnitModal,
type IDeleteUnitModalProps,
} from './delete-unit-modal';
export {
useTableSegmentModal,
type UseTableSegmentModalParams,
type TableDataItem,
ModalActionType,
getSrcFromImg,
} from './table-segment-modal';
export {
useKnowledgeListModal,
type UseKnowledgeListModalParams,
type UseKnowledgeListReturnValue,
useKnowledgeListModalContent,
KnowledgeListModalContent,
KnowledgeCard,
KnowledgeCardListVertical,
} from './knowledge-list-modal';
export { type DataSetModalContentProps } from './knowledge-list-modal/use-content';
export {
useUpdateFrequencyModal,
type UseUpdateFrequencyModalProps,
} from './update-frequency-modal';
export {
transSliceContentOutput,
transSliceContentInput,
imageOnLoad,
imageOnError,
} from './utils';
export { useFetchSliceModal } from './fetch-slice-modal';
export {
useBatchFrequencyModal,
type TBatchFrequencyModalProps,
} from './batch-frequency-modal';
export {
useBatchFetchModal,
type TBatchFetchModalProps,
} from './batch-fetch-modal';
export { useTextResegmentModal } from './text-resegment-modal';
export { useEditUnitNameModal } from './edit-unit-name-modal';
export { FilterKnowledgeType } from '@coze-data/utils';
export { useSetAppendFrequencyModal } from './set-append-frequency-modal';

View File

@@ -0,0 +1,18 @@
/* stylelint-disable declaration-no-important */
.dataset-header {
padding-right: 12px !important;
padding-left: 0 !important;
}
.modal.upgrade-level {
:global {
.semi-modal-body {
height: 0;
padding-bottom: 0;
}
.semi-modal-content {
@apply bg-white-1;
}
}
}

View File

@@ -0,0 +1,184 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useState } from 'react';
import classNames from 'classnames';
import { FilterKnowledgeType } from '@coze-data/utils';
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
import { I18n } from '@coze-arch/i18n';
import {
type UIModalProps,
UICompositionModal,
UICompositionModalSider,
UICompositionModalMain,
} from '@coze-arch/bot-semi';
import { type Dataset } from '@coze-arch/bot-api/knowledge';
import { DATA_REFACTOR_CLASS_NAME } from '@/constant';
import {
useKnowledgeListModalContent,
KnowledgeListModalContent,
} from './use-content';
import SiderCategory from './sider-category';
import styles from './index.module.less';
export interface UseKnowledgeListModalParams {
datasetList: Dataset[];
onDatasetListChange: (list: Dataset[]) => void;
onClickAddKnowledge?: (
datasetId: string,
type: UnitType,
shouldUpload?: boolean,
) => void;
beforeCreate?: (shouldUpload: boolean) => void;
onClickKnowledgeDetail?: (knowledgeID: string) => void;
modalProps?: UIModalProps;
canCreate?: boolean;
defaultType?: FilterKnowledgeType;
knowledgeTypeConfigList?: FilterKnowledgeType[];
projectID?: string;
hideCreate?: boolean;
createKnowledgeModal?: {
modal: React.ReactNode;
open: () => void;
close: () => void;
};
}
export interface UseKnowledgeListReturnValue {
node: JSX.Element;
open: () => void;
close: () => void;
}
export const useKnowledgeListModal = ({
datasetList,
onDatasetListChange,
onClickAddKnowledge,
beforeCreate,
onClickKnowledgeDetail,
modalProps,
canCreate = true,
defaultType,
knowledgeTypeConfigList,
projectID,
hideCreate,
createKnowledgeModal,
}: UseKnowledgeListModalParams): UseKnowledgeListReturnValue => {
const [visible, setVisible] = useState(false);
const [category, setCategory] = useState<'library' | 'project'>(
projectID ? 'project' : 'library',
);
const handleClose = () => {
setVisible(false);
};
const handleOpen = () => {
setVisible(true);
};
const { renderContent, renderSearch, renderCreateBtn, renderFilters } =
useKnowledgeListModalContent({
hideHeader: true,
showFilters: ['scope-type', 'search-type'],
datasetList,
onDatasetListChange,
onClickAddKnowledge,
beforeCreate,
onClickKnowledgeDetail,
canCreate,
defaultType,
knowledgeTypeConfigList,
// 需要优化属性选择方式
projectID: category === 'project' ? projectID : '',
createKnowledgeModal,
});
return {
node: (
<UICompositionModal
type="base-composition"
header={I18n.t('dataset_set_title')}
visible={visible}
className={classNames(
styles.modal,
styles['upgrade-level'],
DATA_REFACTOR_CLASS_NAME,
)}
centered
onCancel={handleClose}
filter={
<div className="flex justify-between gap-[24px]">
{renderFilters()}
</div>
}
sider={
<UICompositionModalSider className="!pt-[16px]">
<UICompositionModalSider.Header className="flex flex-col gap-[16px]">
{renderSearch()}
{hideCreate ? null : renderCreateBtn()}
</UICompositionModalSider.Header>
<UICompositionModalSider.Content className="flex flex-col gap-[4px] mt-[16px]">
<SiderCategory
label={I18n.t('project_resource_modal_library_resources', {
resource: I18n.t('resource_type_knowledge'),
})}
onClick={() => {
setCategory('library');
}}
selected={category === 'library'}
/>
{projectID ? (
<SiderCategory
label={I18n.t('project_resource_modal_project_resources', {
resource: I18n.t('resource_type_knowledge'),
})}
onClick={() => {
setCategory('project');
}}
selected={category === 'project'}
/>
) : null}
</UICompositionModalSider.Content>
</UICompositionModalSider>
}
content={
<UICompositionModalMain className="px-[12px]">
{renderContent()}
</UICompositionModalMain>
}
{...modalProps}
></UICompositionModal>
),
close: handleClose,
open: handleOpen,
};
};
export { KnowledgeCard } from './knowledge-card';
export {
KnowledgeListModalContent,
useKnowledgeListModalContent,
FilterKnowledgeType,
};
export { KnowledgeCardListVertical } from './knowledge-card-list';

View File

@@ -0,0 +1,9 @@
.popover {
padding: 16px;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: #2e3238;
white-space: pre-line;
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC, type PropsWithChildren } from 'react';
import { I18n } from '@coze-arch/i18n';
import { Popover } from '@coze-arch/bot-semi';
import styles from './index.module.less';
export const FilePopover: FC<
PropsWithChildren<{
fileNames: string[];
showTitle?: boolean;
}>
> = ({ fileNames = [], showTitle = true, children }) => (
<Popover
className={styles.popover}
content={
<div>
{showTitle ? <p>{I18n.t('datasets_processing_notice')}</p> : null}
<p>{fileNames.join('\n')}</p>
</div>
}
>
{children}
</Popover>
);

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { FilePopover } from './file-popover';

View File

@@ -0,0 +1,220 @@
/* stylelint-disable max-nesting-depth */
/* stylelint-disable no-descending-specificity */
/* stylelint-disable selector-class-pattern */
.container {
display: flex;
flex-direction: column;
}
.item:hover {
cursor: pointer;
background: var(--light-usage-fill-color-fill-0, rgb(46 47 56 / 5%));
border-bottom: 1px solid transparent;
border-radius: var(--spacing-tight, 8px);
}
.item:hover::before {
content: '';
position: absolute;
top: -1px;
left: 0;
width: 100%;
border-top: 1px solid rgb(245 247 250);
}
.item {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: 10px 8px;
border-bottom: 1px solid rgba(29, 28, 35, 8%);
.left {
box-sizing: border-box;
width: 36px;
height: 36px;
background-color: #fff;
border: 1px solid rgb(237 237 238);
border-radius: 6px;
&>img {
width: 36px;
height: 36px;
}
}
.content {
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
width: 0;
height: 92px;
margin: 0 16px;
}
.right {
flex-shrink: 0;
}
.title {
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: var(--light-usage-text-color-text-0, #1c1d23);
}
.description {
width: 100%;
margin-top: 4px;
font-size: 12px;
line-height: 16px;
color: var(--light-usage-text-color-text-1, rgb(28 29 35 / 80%));
letter-spacing: 0.12px;
}
.tags-wapper {
margin-top: 8px;
}
.tags {
.file-list {
color: var(--light-color-teal-teal-6, #00a794);
background-color: #e4e6e9;
}
:global {
.semi-tag-square {
border-radius: 4px;
}
}
}
.info {
display: flex;
align-items: center;
margin-top: 8px;
}
.creator {
padding-left: 4px;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: var(--light-usage-text-color-text-3, rgb(28 29 35 / 35%));
}
.border-right {
width: 1px;
height: 8px;
margin: 0 4px 0 8px;
background-color: rgb(28 29 35 / 12%);
}
}
button.button {
flex-shrink: 0;
width: 80px;
&.added {
color: var(--light-usage-primary-color-primary-disabled, #b4baf6);
background: var(--light-usage-bg-color-bg-0, #fff);
border: 1px solid var(--light-usage-disabled-color-disabled-border, #f0f0f5);
}
&.addedMouseIn {
color: var(--light-color-red-red-5, #ff441e);
background: #fff;
border: 1px solid var(--light-usage-border-color-border-1, rgb(29 28 35 / 12%));
}
}
.file-list-details {
max-width: 335px;
.dataset-name {
padding: 9px 12px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: var(--light-usage-text-color-text-0,
var(--light-usage-text-color-text-0, #1c1f23));
}
.file-info {
overflow-y: auto;
max-height: 400px;
&-item {
display: flex;
align-items: center;
padding: 9px 10px;
font-size: 14px;
line-height: 22px;
color: var(--light-usage-text-color-text-0,
var(--light-usage-text-color-text-0, #1c1f23));
.icon-note {
margin-right: 8px;
>svg {
width: 16px;
height: 16px;
>path {
fill: #3370ff;
}
}
}
}
}
}
.popover {
padding: 12px;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: #2e3238;
}
.pointer {
cursor: pointer;
}
.loading-more,
.no-more {
position: relative;
display: flex;
grid-column: 1 / -1;
align-items: center;
justify-content: center;
width: 100%;
padding: 13px 0;
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: var(--light-usage-text-color-text-2,
var(--light-usage-text-color-text-2, rgb(28 31 35 / 60%)));
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {
KnowledgeCardListVertical,
type DatasetCardListVerticalOperations,
type DatasetCardListVerticalProps,
} from './vertical';

View File

@@ -0,0 +1,280 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC } from 'react';
import { unix } from 'dayjs';
import cs from 'classnames';
import { useBoolean } from 'ahooks';
import { IconSpin } from '@douyinfe/semi-icons';
import { BotE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { type ButtonProps } from '@coze-arch/bot-semi/Button';
import {
UITag,
UIButton,
Typography,
Space,
Avatar,
Popover,
} from '@coze-arch/bot-semi';
import { IconNote } from '@coze-arch/bot-icons';
import {
OrderField,
type Dataset,
DatasetStatus,
StorageLocation,
} from '@coze-arch/bot-api/knowledge';
import { SpaceType } from '@coze-arch/bot-api/developer_api';
import { getEllipsisCount, formatBytes } from '../../utils';
import { FilePopover } from './components';
import styles from './index.module.less';
const { Text } = Typography;
export interface DatasetCardListVerticalOperations {
onAdd: (dataset: Dataset) => void | Promise<void>;
onRemove: (dataset: Dataset) => void | Promise<void>;
isAdded: (id: string) => boolean;
}
function AddedButton(buttonProps: ButtonProps) {
const [isMouseIn, { setFalse, setTrue }] = useBoolean(false);
const onMouseEnter = () => {
setTrue();
};
const onMouseLeave = () => {
setFalse();
};
return (
<UIButton
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
{...buttonProps}
className={cs({
[buttonProps.className || '']: Boolean(buttonProps.className),
[styles.addedMouseIn]: isMouseIn,
})}
>
{isMouseIn ? I18n.t('Remove') : I18n.t('Added')}
</UIButton>
);
}
export type DatasetCardListVerticalProps = DatasetCardListVerticalOperations & {
list: Dataset[];
loading: boolean;
noMore: boolean;
searchType: OrderField;
onClickKnowledgeDetail?: (knowledgeID: string) => void;
};
const DEFAULT_BOT_NUM = 99;
const SpaceTags = (item: Dataset) => (
<Space className={styles.tags} wrap>
{item.processing_file_list?.length ? (
<FilePopover fileNames={item.processing_file_list || []}>
<UITag color="teal" className={styles['file-list']}>
{I18n.t('dataset_data_processing_tag', {
num: item.processing_file_list?.length || 0,
})}
</UITag>
</FilePopover>
) : null}
<UITag color="grey">
{formatBytes(parseInt(String(item.all_file_size)))}
</UITag>
{item.file_list?.length ? (
<Popover
trigger="hover"
showArrow
content={
<div className={styles['file-list-details']}>
<div className={styles['dataset-name']}>{item.name || ''}</div>
<div className={styles['file-info']}>
{item.file_list?.map(fileInfo => (
<div className={styles['file-info-item']} key={fileInfo}>
<IconNote className={styles['icon-note']} />
{fileInfo}
</div>
))}
</div>
</div>
}
>
<UITag color="grey">
{I18n.t('dataset_bot_count_tag', {
num: getEllipsisCount(item.file_list?.length || 0, DEFAULT_BOT_NUM),
})}
</UITag>
</Popover>
) : (
<UITag color="grey">
{I18n.t('dataset_bot_count_tag', {
num: getEllipsisCount(item.file_list?.length || 0, DEFAULT_BOT_NUM),
})}
</UITag>
)}
{item.storage_location === StorageLocation.OpenSearch ? (
<UITag color="cyan">{I18n.t('knowledge_es_001')}</UITag>
) : null}
</Space>
);
export const KnowledgeCardListVertical: FC<DatasetCardListVerticalProps> = ({
list,
loading,
noMore,
onAdd,
onRemove,
isAdded,
searchType,
onClickKnowledgeDetail,
}) => {
const { id: spaceId, space_type } = useSpaceStore(s => s.space);
const isPersonal = space_type === SpaceType.Personal;
const handleRow = (e: { stopPropagation: () => void }, id: string) => {
e.stopPropagation();
if (onClickKnowledgeDetail) {
onClickKnowledgeDetail(id);
} else {
window.open(`/space/${spaceId}/knowledge/${id}`);
}
};
return (
<div className={styles.container}>
{list.map(item => (
<div
className={styles.item}
key={item.dataset_id || ''}
onClick={e => handleRow(e, item?.dataset_id || '')}
>
<Avatar shape="square" src={item.icon_url} className={styles.left} />
<div
className={styles.content}
data-testid={`${BotE2e.BotKnowledgeSelectListModalName}.${item.name}`}
data-dtestid={`${BotE2e.BotKnowledgeSelectListModalName}.${item.name}`}
>
<Text className={styles.title} ellipsis={{ showTooltip: true }}>
{item.name || ''}
</Text>
{item.description ? (
<Typography.Text
className={styles.description}
ellipsis={{ rows: 1 }}
>
{item.description}
</Typography.Text>
) : null}
{!item.description && !!item.file_list?.length && (
<Typography.Text
className={styles.description}
ellipsis={{ rows: 1 }}
>
{item.file_list?.join('、')}
</Typography.Text>
)}
<div className={styles['tags-wapper']}>
<SpaceTags {...item}></SpaceTags>
<div className={styles.info}>
{!isPersonal && (
<>
<Avatar
src={item.avatar_url}
style={{ width: 14, height: 14 }}
/>
<Text
className={cs(styles.creator)}
ellipsis={{ showTooltip: true }}
>
{item.creator_name || ''}
</Text>
<span className={styles['border-right']}></span>
</>
)}
{searchType === OrderField.CreateTime ? (
<span className={styles.creator}>
{I18n.t('dataset_bot_create_time_knowledge', {
time: unix(item.create_time || 0).format(
'YYYY-MM-DD HH:mm',
),
})}
</span>
) : (
<span className={styles.creator}>
{I18n.t('dataset_bot_update_time_knowledge', {
time: unix(item.update_time || 0).format(
'YYYY-MM-DD HH:mm',
),
})}
</span>
)}
</div>
</div>
</div>
<div
className={styles.right}
onClick={e => e.stopPropagation()}
data-testid={`${BotE2e.BotKnowledgeSelectListModalAddBtn}.${item.name}`}
>
{isAdded(item.dataset_id || '') ? (
<AddedButton
className={cs(styles.button, styles.added)}
onClick={() => onRemove(item)}
>
{I18n.t('Added')}
</AddedButton>
) : (
<UIButton
disabled={item.status === DatasetStatus.DatasetForbid}
className={styles.button}
onClick={() => onAdd(item)}
data-testid="bot.database.add.modal.add.button"
>
{I18n.t('Add_2')}
</UIButton>
)}
</div>
</div>
))}
{loading ? (
<div className={styles['loading-more']}>
<IconSpin spin style={{ marginRight: '4px' }} />
<div>{I18n.t('Loading')}</div>
</div>
) : null}
{noMore ? (
<div className={styles['no-more']}>
<div>{I18n.t('No_more')}</div>
</div>
) : null}
</div>
);
};

View File

@@ -0,0 +1,266 @@
@common-box-shadow: 0px 2px 8px 0px rgba(31, 35, 41, 0.02),
0px 2px 4px 0px rgba(31, 35, 41, 0.02), 0px 2px 2px 0px rgba(31, 35, 41, 0.02);
.common-svg-icon(@size: 14px, @color: #3370ff) {
>svg {
width: @size;
height: @size;
>path {
fill: @color;
}
}
}
.data-set-item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 48px;
margin-bottom: 4px;
padding: 8px;
background: rgba(6, 7, 9, 2%);
border-radius: var(--default, 8px);
.data-set-item-right {
display: none;
}
&:hover {
background: rgba(6, 7, 9, 14%);
.data-set-item-right {
display: flex;
gap: 4px;
align-items: center;
}
}
}
.data-set-item-left {
cursor: pointer;
display: flex;
align-items: center;
width: calc(100% - 60px);
margin-right: 20px;
.minus {
flex-shrink: 0;
margin-left: auto;
}
.data-set-name {
overflow: hidden;
flex: 1;
/* 142.857% */
padding-left: 8px;
font-size: 12px;
font-weight: 500;
line-height: 16px;
color: rgba(6, 7, 9, 80%);
text-overflow: ellipsis;
}
.data-set-desc {
overflow: hidden;
padding-left: 8px;
font-size: 12px;
font-weight: 400;
font-style: normal;
line-height: 16px;
color: rgba(6, 7, 9, 50%);
text-overflow: ellipsis;
}
}
.icon-note {
/* stylelint-disable-next-line declaration-no-important */
width: 24px !important;
/* stylelint-disable-next-line declaration-no-important */
height: 24px !important;
/* stylelint-disable-next-line declaration-no-important */
border-radius: 6px !important;
}
.card-content {
display: flex;
flex-direction: column;
max-width: calc(100% - 24px);
}
.icon-no {
.common-svg-icon(14px, rgba(107, 109, 117, 1));
&:hover {
background-color: var(--semi-color-fill-0);
}
}
.between {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.icon-copy {
.common-svg-icon(14px, rgba(107, 109, 117, 1));
&:hover {
background-color: var(--semi-color-fill-0);
}
}
.data-set-content {
.dataset-setting-tip {
margin: 8px 0 20px;
padding: 8px;
font-size: 12px;
line-height: 16px;
color: var(--light-usage-text-color-text-1, rgb(28 29 35 / 80%));
background: var(--light-usage-fill-color-fill-0, rgb(46 46 56 / 4%));
border-radius: 8px;
.copy-trigger {
cursor: pointer;
margin: 0 4px;
font-size: 12px;
font-weight: 500;
line-height: 16px;
color: var(--light-color-brand-brand-5, #4d53e8);
.icon-copy {
.common-svg-icon(14px, var(--light-color-brand-brand-5, #4d53e8));
/* stylelint-disable-next-line declaration-no-important */
margin-right: 0 !important;
}
}
:global {
.semi-tag-grey-light {
/* stylelint-disable-next-line declaration-no-important */
background: #fff !important;
}
}
}
}
.failed-tag,
.processing-tag {
font-weight: 500;
line-height: 16px;
}
.processing-tag {
color: var(--light-color-green-green-6, #32A247);
background: var(--light-color-green-green-1, #D2F3D5);
}
.failed-tag {
color: var(--light-color-red-red-6, #DB2E13);
background: var(--light-color-red-red-1, #FFE0D2);
}
// .default-text {
// .tip-text;
// }
.setting-trigger {
cursor: pointer;
display: flex;
column-gap: 4px;
align-items: center;
margin-left: 8px;
font-size: 12px;
font-weight: 600;
font-style: normal;
line-height: 16px;
color: var(--light-color-brand-brand-5, #4d53e8);
&-icon {
svg {
width: 10px;
height: 10px;
}
}
:global {
.semi-button-content-right {
display: flex;
align-items: center;
}
}
}
.setting-content-popover {
background: #f7f7fa;
border-radius: 12px;
}
.setting {
overflow-y: auto;
height: 454px;
padding: 24px;
font-size: 14px;
line-height: 20px;
color: var(--light-usage-text-color-text-0, #1f2329);
.setting-title {
font-size: 18px;
font-weight: 600;
line-height: 24px;
}
.setting-item {
display: flex;
align-items: self-start;
margin-top: 16px;
.setting-item-copy {
cursor: pointer;
margin: 0 4px;
padding: 2px 4px 2px 8px;
font-size: 12px;
font-weight: 500;
line-height: 16px;
color: var(--light-color-brand-brand-5, #4d53e8);
border-radius: 6px;
.icon-copy {
.common-svg-icon(14px, var(--light-color-brand-brand-5, #4d53e8));
margin: 0 0 0 4px;
}
}
:global {
.semi-tag-grey-light {
/* stylelint-disable-next-line declaration-no-important */
background: var(--light-color-brand-brand-1, #d9dcfa) !important;
}
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import copy from 'copy-to-clipboard';
import { useDataNavigate } from '@coze-data/knowledge-stores';
import { REPORT_EVENTS as ReportEventNames } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { UIIconButton, Typography, Toast, Avatar } from '@coze-arch/bot-semi';
import { CustomError } from '@coze-arch/bot-error';
import { type Dataset } from '@coze-arch/bot-api/knowledge';
import { IconCozCopy, IconCozMinusCircle } from '@coze-arch/coze-design/icons';
import { Tooltip } from '@coze-arch/coze-design';
import styles from './index.module.less';
export interface DataSetItemProps {
dataSet: Dataset;
isReadonly?: boolean;
onRemove: () => void;
onClick?: (datasetID: string) => void;
}
export const KnowledgeCard: React.FC<DataSetItemProps> = ({
dataSet,
isReadonly,
onRemove,
onClick,
}) => {
const { name, description, icon_url, dataset_id: id } = dataSet;
const resourceNavigate = useDataNavigate();
const navigateToKnowledgePage = (): void => {
resourceNavigate.toResource?.('knowledge', id);
};
const onCopy = (text: string) => {
const res = copy(text);
if (!res) {
throw new CustomError(ReportEventNames.parmasValidation, 'empty copy');
}
Toast.success({
content: I18n.t('copy_success'),
showClose: false,
id: 'dataset_copy_id',
});
};
return (
<div className={styles['data-set-item']}>
<div
className={styles['data-set-item-left']}
onClick={() => {
if (!id) {
return;
}
onClick ? onClick(id) : navigateToKnowledgePage();
}}
>
<Avatar shape="square" src={icon_url} className={styles['icon-note']} />
<div className={styles['card-content']}>
<Typography.Text
className={styles['data-set-name']}
ellipsis={{ showTooltip: true }}
>
{name}
</Typography.Text>
<Typography.Text
className={styles['data-set-desc']}
ellipsis={{ showTooltip: true }}
>
{description}
</Typography.Text>
</div>
</div>
<div className={styles['data-set-item-right']}>
{!isReadonly && (
<Tooltip content={I18n.t('Copy_name')}>
<UIIconButton
// wrapperClass={commonStyles['icon-button-16']}
iconSize="small"
icon={<IconCozCopy className={styles['icon-copy']} />}
onClick={() => name && onCopy(name)}
/>
</Tooltip>
)}
{!isReadonly && (
<Tooltip content={I18n.t('remove_dataset')}>
<UIIconButton
// wrapperClass={commonStyles['icon-button-16']}
iconSize="small"
icon={<IconCozMinusCircle className={styles['icon-no']} />}
onClick={onRemove}
/>
</Tooltip>
)}
</div>
</div>
);
};

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import classNames from 'classnames';
import { IconCozKnowledgeFill } from '@coze-arch/coze-design/icons';
interface SiderCategoryProps {
label: string;
selected: boolean;
onClick?: React.MouseEventHandler<HTMLDivElement>;
}
const SiderCategory = ({ label, onClick, selected }: SiderCategoryProps) => (
<div
onClick={onClick}
className={classNames([
'flex items-center gap-[8px] px-[12px]',
'px-[12px] py-[6px] rounded-[8px]',
'cursor-pointer',
'hover:text-[var(--light-usage-text-color-text-0,#1c1f23)]',
'hover:bg-[var(--light-usage-fill-color-fill-0,rgba(46,50,56,5%))]',
selected &&
'text-[var(--light-usage-text-color-text-0,#1c1d23)] bg-[var(--light-usage-fill-color-fill-0,rgba(46,47,56,5%))]',
])}
>
<IconCozKnowledgeFill />
{label}
</div>
);
export default SiderCategory;

View File

@@ -0,0 +1,177 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { FC, ReactNode } from 'react';
import classNames from 'classnames';
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
import { type FilterKnowledgeType } from '@coze-data/utils';
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { type Dataset } from '@coze-arch/bot-api/knowledge';
import { DATA_REFACTOR_CLASS_NAME } from '@/constant';
import {
useKnowledgeFilter,
Scene,
type DatasetFilterType,
} from './use-knowledge-filter';
import { KnowledgeCardListVertical } from './knowledge-card-list';
import s from './index.module.less';
export interface DataSetModalContentProps {
datasetList: Dataset[];
onDatasetListChange: (list: Dataset[]) => void;
onClickAddKnowledge?: (
datasetId: string,
type: UnitType,
shouldUpload?: boolean,
) => void;
beforeCreate?: (shouldUpload: boolean) => void;
onClickKnowledgeDetail?: (knowledgeID: string) => void;
canCreate?: boolean;
defaultType?: FilterKnowledgeType;
knowledgeTypeConfigList?: FilterKnowledgeType[];
projectID?: string;
showFilters?: DatasetFilterType[];
hideHeader?: boolean;
createKnowledgeModal?: {
modal: ReactNode;
open: () => void;
close: () => void;
};
}
const useKnowledgeListModalContent = ({
datasetList,
onDatasetListChange,
onClickAddKnowledge,
beforeCreate,
onClickKnowledgeDetail,
canCreate = true,
defaultType,
knowledgeTypeConfigList,
projectID,
showFilters = ['scope-type', 'search-type', 'query-input'],
hideHeader,
createKnowledgeModal,
}: DataSetModalContentProps) => {
const botId = useBotInfoStore(state => state.botId);
const { renderContentFilter, renderSearch, renderCreateBtn, renderFilters } =
useKnowledgeFilter({
hideHeader,
showFilters,
scene: Scene.MODAL,
headerClassName: classNames(
s['dataset-header'],
DATA_REFACTOR_CLASS_NAME,
),
onClickAddKnowledge,
beforeCreate,
canCreate,
defaultType,
knowledgeTypeConfigList,
projectID,
createKnowledgeModal,
children: ({ list, loading, noMore, searchType }) => (
<KnowledgeCardListVertical
searchType={searchType}
noMore={noMore}
list={list}
loading={loading}
onAdd={async dataset => {
await onDatasetListChange([...datasetList, dataset]);
sendTeaEvent(EVENT_NAMES.click_database_select, {
operation: 'add',
bot_id: botId,
});
// Toast.success({
// showClose: false,
// content: I18n.t('bot_edit_dataset_added_toast', {
// dataset_name: dataset.name || '',
// }),
// style: {
// wordWrap: 'break-word',
// },
// });
}}
onRemove={dataset => {
onDatasetListChange(
datasetList.filter(
item => item.dataset_id !== dataset.dataset_id,
),
);
sendTeaEvent(EVENT_NAMES.click_database_select, {
operation: 'remove',
bot_id: botId,
});
// Toast.success({
// showClose: false,
// content: I18n.t('bot_edit_dataset_removed_toast', {
// dataset_name: dataset.name || '',
// }),
// style: {
// wordWrap: 'break-word',
// },
// });
}}
isAdded={id => datasetList.some(dataset => dataset.dataset_id === id)}
onClickKnowledgeDetail={onClickKnowledgeDetail}
/>
),
});
return {
renderContent: renderContentFilter,
renderSearch,
renderCreateBtn,
renderFilters,
};
};
const KnowledgeListModalContent: FC<DataSetModalContentProps> = ({
datasetList,
onDatasetListChange,
onClickAddKnowledge,
beforeCreate,
onClickKnowledgeDetail,
canCreate = true,
defaultType,
knowledgeTypeConfigList,
projectID,
createKnowledgeModal,
}) => {
const { renderContent } = useKnowledgeListModalContent({
datasetList,
onDatasetListChange,
onClickAddKnowledge,
beforeCreate,
onClickKnowledgeDetail,
canCreate,
defaultType,
knowledgeTypeConfigList,
projectID,
createKnowledgeModal,
});
return <>{renderContent()}</>;
};
export { useKnowledgeListModalContent, KnowledgeListModalContent };

View File

@@ -0,0 +1,120 @@
/* stylelint-disable declaration-no-important */
.spin {
display: flex;
flex: 1;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100% !important;
height: 100% !important;
:global {
.semi-spin-children {
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
height: 100%;
}
}
}
.empty {
&-image {
width: 200px;
height: 200px;
}
&-content {
width: 100%;
text-align: center;
}
}
.container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
&>* {
width: 100%;
}
.new-filter-header {
justify-content: space-between !important;
}
.header {
display: flex;
flex-direction: row;
flex-shrink: 0;
justify-content: flex-end;
height: fit-content;
padding: 0 36px 8px;
.select {
width: 160px;
:global {
.semi-select-selection-text {
color: rgba(28, 31, 35, 60%);
}
}
}
.input {
width: 260px;
background: #fff;
}
.tab-select {
margin-right: 10px;
}
}
.content {
flex: 1;
&.scrollable {
overflow: auto;
}
&.centered {
display: flex;
flex-direction: column;
justify-content: center;
}
}
.footer {
display: flex;
flex-direction: row;
flex-shrink: 0;
justify-content: flex-end;
height: fit-content;
padding: 24px;
}
}
.file-type-tab {
display: flex;
margin-left: 8px;
padding: 6px 0;
&-item {
cursor: pointer;
font-weight: 600;
color: #1D1C2399;
&-active {
cursor: pointer;
font-weight: 600;
color: #4D53E8;
}
}
}

View File

@@ -0,0 +1,629 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable max-lines-per-function */
/* eslint-disable max-lines -- 待拆分 */
/* eslint-disable @coze-arch/max-line-per-function */
import {
type FC,
useEffect,
useState,
useRef,
type ReactNode,
useMemo,
} from 'react';
import { isFunction, uniq, debounce } from 'lodash-es';
import cs from 'classnames';
import {
useInfiniteScroll,
useUpdateEffect,
useDocumentVisibility,
} from 'ahooks';
import { FilterKnowledgeType } from '@coze-data/utils';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
import { BotE2e } from '@coze-data/e2e';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import {
UIButton,
UIEmpty,
UISelect,
Spin,
UISearch,
Divider,
} from '@coze-arch/bot-semi';
import {
OrderField,
type Dataset,
DatasetScopeType,
FormatType,
} from '@coze-arch/bot-api/knowledge';
import { SpaceType } from '@coze-arch/bot-api/developer_api';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { Input } from '@coze-arch/coze-design';
import { DATA_REFACTOR_CLASS_NAME } from '../../constant';
import styles from './index.module.less';
interface GetDatasetListData {
list: Dataset[];
nextPageIndex: number;
total: number;
}
const DEFAULT_PAGE_SIZE = 20;
const getDatasetList = async (
props: {
query?: string;
search_type?: OrderField;
space_id: string;
scope_type?: DatasetScopeType;
format_type?: FormatType;
projectID?: string;
},
pageIndex = 1,
) => {
const { query, search_type, space_id, scope_type, format_type, projectID } =
props;
const resp = await KnowledgeApi.ListDataset({
space_id,
page: pageIndex,
size: DEFAULT_PAGE_SIZE,
filter: {
name: query,
scope_type,
format_type,
},
order_field: search_type,
project_id: projectID,
});
return {
list: resp?.dataset_list || [],
nextPageIndex: pageIndex + 1,
total: Number(resp?.total),
};
};
const DEFAULT_SEARCH_TYPE = OrderField.CreateTime;
interface CreateKnowledgeModalProps {
modal: ReactNode;
open: () => void;
close: () => void;
}
const EmptyToCreate: FC<{
onAdd: () => void;
scene: Scene;
canCreate: boolean;
createKnowledgeModal?: CreateKnowledgeModalProps;
}> = ({ onAdd, scene, canCreate, createKnowledgeModal }) => {
const handleAdd = () => {
if (scene === Scene.MODAL) {
onAdd();
return;
}
createKnowledgeModal?.open();
};
return (
<>
<div className={cs(styles.content, styles.centered)}>
<UIEmpty
className={styles.empty}
empty={{
...(canCreate
? {
btnText: I18n.t('datasets_create_btn'),
btnOnClick: handleAdd,
}
: {}),
title: I18n.t('datasets_empty_title'),
description: I18n.t('datasets_empty_description'),
}}
/>
</div>
{createKnowledgeModal?.modal}
</>
);
};
export interface DatasetFilterAction {
list: Dataset[];
size: number;
query: string | undefined;
searchType: OrderField;
loading: boolean;
noMore: boolean;
resetFilter: () => void;
refresh: () => void;
createDataset?: (name: string, source_type: number) => Promise<void>;
deleteDataset?: (id: string) => Promise<void>;
updateDataset?: (id: string, name: string) => Promise<void>;
}
export type DatasetFilterType = 'scope-type' | 'search-type' | 'query-input';
export interface DatasetFilterProps {
hideHeader?: boolean;
children:
| ((action: DatasetFilterAction) => React.ReactNode)
| React.ReactNode;
showFilters?: DatasetFilterType[];
headerClassName?: string;
scene?: Scene;
onClickAddKnowledge?: (
datasetId: string,
type: UnitType,
shouldUpload?: boolean,
) => void;
beforeCreate?: (shouldUpload: boolean) => void;
canCreate: boolean;
defaultType?: FilterKnowledgeType;
knowledgeTypeConfigList?: FilterKnowledgeType[];
projectID?: string;
createKnowledgeModal?: CreateKnowledgeModalProps;
}
export enum Scene {
PAGE = 'page',
MODAL = 'modal',
}
const defaultKnowledgeTypeFallback = (param: FilterKnowledgeType[]) => {
if (param.includes(FilterKnowledgeType.ALL)) {
return FilterKnowledgeType.ALL;
}
return param.at(0) ?? FilterKnowledgeType.ALL;
};
const useKnowledgeFilter = ({
hideHeader,
children,
showFilters,
headerClassName,
scene = Scene.PAGE,
onClickAddKnowledge,
canCreate,
defaultType,
knowledgeTypeConfigList = [
FilterKnowledgeType.ALL,
FilterKnowledgeType.TEXT,
FilterKnowledgeType.TABLE,
FilterKnowledgeType.IMAGE,
],
projectID,
beforeCreate,
createKnowledgeModal,
}: DatasetFilterProps) => {
const uniqKnowledgeTypeConfigList = uniq(knowledgeTypeConfigList);
const [currentKnowledgeType, setCurrentKnowledgeType] = useState(
defaultType || defaultKnowledgeTypeFallback(uniqKnowledgeTypeConfigList),
);
const [query, setQuery] = useState<string>();
const [searchType, setSearchType] = useState<OrderField>(DEFAULT_SEARCH_TYPE);
const [scopeType, setScopeType] = useState<DatasetScopeType>(
projectID ? DatasetScopeType.ScopeSelf : DatasetScopeType.ScopeAll,
);
const scopeOptions = [
{
label: I18n.t('scope_all'),
value: DatasetScopeType.ScopeAll,
},
{
label: I18n.t('scope_self'),
value: DatasetScopeType.ScopeSelf,
},
];
const { id, space_type } = useSpaceStore(s => s.space);
const isPersonal = space_type === SpaceType.Personal;
const containerRef = useRef<HTMLDivElement>(null);
const { loading, data, loadingMore, noMore, reload } =
useInfiniteScroll<GetDatasetListData>(
(newData?: GetDatasetListData): Promise<GetDatasetListData> => {
if (!newData || newData.nextPageIndex === 1) {
containerRef.current?.scroll(0, 0);
}
return getDatasetList(
{
space_id: id || '',
query,
search_type: searchType,
scope_type: isPersonal ? DatasetScopeType.ScopeSelf : scopeType,
format_type:
currentKnowledgeType === FilterKnowledgeType.ALL
? undefined
: {
[FilterKnowledgeType.TABLE]: FormatType.Table,
[FilterKnowledgeType.TEXT]: FormatType.Text,
[FilterKnowledgeType.IMAGE]: FormatType.Image,
}[currentKnowledgeType],
projectID,
},
newData?.nextPageIndex,
);
},
{
manual: true,
isNoMore: newData =>
Boolean(
!newData?.total ||
(newData.nextPageIndex - 1) * DEFAULT_PAGE_SIZE >= newData.total,
),
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeGetDataSetList,
error,
});
},
target: containerRef,
reloadDeps: [query, searchType, scopeType, projectID],
},
);
useUpdateEffect(() => {
handleResetFilter();
}, [id]);
const documentVisibility = useDocumentVisibility();
useEffect(() => {
if (documentVisibility === 'visible') {
reload();
}
}, [documentVisibility]);
const handleResetFilter = () => {
setQuery(undefined);
setSearchType(DEFAULT_SEARCH_TYPE);
};
const handleSearchTypeChange = (value: OrderField) => {
setSearchType(value);
};
const handleQueryChange = (value = '') => {
setQuery(value);
};
const handleAdd = () => {
createKnowledgeModal?.open();
};
const renderContent = () => {
/** 有数据则展示列表 */
if (data?.total) {
return (
<>
<div
className={cs(styles.content, styles.scrollable)}
ref={containerRef}
>
{isFunction(children)
? children({
size: DEFAULT_PAGE_SIZE,
query,
searchType,
loading: loadingMore,
list: data.list,
noMore,
resetFilter: handleResetFilter,
refresh: reload,
})
: children}
</div>
</>
);
}
/** 无数据且未在加载则展示空状态 */
if (!loading) {
return (
<EmptyToCreate
scene={scene}
onAdd={() => {
handleAdd();
}}
canCreate={canCreate}
createKnowledgeModal={createKnowledgeModal}
/>
);
}
/** 无数据且加载中则不展示 */
return null;
};
const renderSearch = useMemo(
() => () => (
<Input
autoFocus
key="query-input"
placeholder={I18n.t('db2_014')}
onChange={debounce(handleQueryChange, 500)}
/>
),
[],
);
const renderCreateBtn = useMemo(
() => () => (
<UIButton
theme="solid"
onClick={handleAdd}
data-testid={BotE2e.BotKnowledgeSelectListModalCreateBtn}
>
{I18n.t('datasets_create_btn')}
</UIButton>
),
[handleAdd],
);
const renderFilters = useMemo(
() => () => (
<>
<div className={styles['file-type-tab']}>
{uniqKnowledgeTypeConfigList.reduce<ReactNode[]>(
(
accumulator: ReactNode[],
currentValue: FilterKnowledgeType,
currentIndex: number,
) => {
const reactNode = renderKnowledgeTypeConfigNode(currentValue);
if (currentIndex !== 0) {
return accumulator.concat([
<Divider layout="vertical" margin="12px" />,
reactNode,
]);
}
return accumulator.concat([reactNode]);
},
[],
)}
</div>
<div className={'flex'}>
{uniq(showFilters).map((filterType: DatasetFilterType) => {
if (filterType === 'scope-type') {
return !isPersonal ? (
<UISelect
label={I18n.t('Creator')}
showClear={false}
value={scopeType}
optionList={scopeOptions}
onChange={v => {
setScopeType(v as DatasetScopeType);
}}
/>
) : null;
} else if (filterType === 'search-type') {
return (
<UISelect
data-testid={
BotE2e.BotKnowledgeSelectListModalCreateDateSelect
}
label={I18n.t('Sort')}
showClear={false}
value={searchType}
optionList={[
{
label: I18n.t('Create_time'),
value: OrderField.CreateTime,
},
{
label: I18n.t('Update_time'),
value: OrderField.UpdateTime,
},
]}
onChange={v => {
handleSearchTypeChange(v as OrderField);
}}
/>
);
}
})}
</div>
</>
),
[
headerClassName,
handleSearchTypeChange,
scopeType,
scopeOptions,
isPersonal,
showFilters,
uniqKnowledgeTypeConfigList,
],
);
useEffect(() => {
reload();
}, [currentKnowledgeType]);
const renderKnowledgeTypeConfigNode = (type: FilterKnowledgeType) => {
if (type === FilterKnowledgeType.ALL) {
return (
<div
data-testid={BotE2e.BotKnowledgeSelectListModalAllTab}
key={FilterKnowledgeType.ALL}
onClick={() => setCurrentKnowledgeType(FilterKnowledgeType.ALL)}
className={
currentKnowledgeType === FilterKnowledgeType.ALL
? styles['file-type-tab-item-active']
: styles['file-type-tab-item']
}
>
{I18n.t('kl2_010')}
</div>
);
}
if (type === FilterKnowledgeType.TEXT) {
return (
<div
data-testid={BotE2e.BotKnowledgeSelectListModalTextTab}
key={FilterKnowledgeType.TEXT}
onClick={() => setCurrentKnowledgeType(FilterKnowledgeType.TEXT)}
className={
currentKnowledgeType === FilterKnowledgeType.TEXT
? styles['file-type-tab-item-active']
: styles['file-type-tab-item']
}
>
{I18n.t('kl2_011')}
</div>
);
}
if (type === FilterKnowledgeType.TABLE) {
return (
<div
data-testid={BotE2e.BotKnowledgeSelectListModalTableTab}
key={FilterKnowledgeType.TABLE}
onClick={() => setCurrentKnowledgeType(FilterKnowledgeType.TABLE)}
className={
currentKnowledgeType === FilterKnowledgeType.TABLE
? styles['file-type-tab-item-active']
: styles['file-type-tab-item']
}
>
{I18n.t('kl2_012')}
</div>
);
}
if (type === FilterKnowledgeType.IMAGE) {
return (
<div
data-testid={BotE2e.BotKnowledgeSelectListModalPhotoTab}
key={FilterKnowledgeType.IMAGE}
onClick={() => setCurrentKnowledgeType(FilterKnowledgeType.IMAGE)}
className={
currentKnowledgeType === FilterKnowledgeType.IMAGE
? styles['file-type-tab-item-active']
: styles['file-type-tab-item']
}
>
{I18n.t('knowledge_photo_025')}
</div>
);
}
return null;
};
const renderContentFilter = () => (
<Spin spinning={loading} wrapperClassName={styles.spin}>
<div className={cs(styles.container, DATA_REFACTOR_CLASS_NAME)}>
{!hideHeader && showFilters?.length ? (
<div
className={cs(
styles.header,
headerClassName,
styles['new-filter-header'],
)}
>
<div className={styles['file-type-tab']}>
{uniqKnowledgeTypeConfigList.reduce<ReactNode[]>(
(
accumulator: ReactNode[],
currentValue: FilterKnowledgeType,
currentIndex: number,
) => {
const reactNode = renderKnowledgeTypeConfigNode(currentValue);
if (currentIndex !== 0) {
return accumulator.concat([
<Divider layout="vertical" margin="12px" />,
reactNode,
]);
}
return accumulator.concat([reactNode]);
},
[],
)}
</div>
<div className="flex gap-[8px]">
{uniq(showFilters).map((filterType: DatasetFilterType) => {
if (filterType === 'scope-type') {
return !isPersonal ? (
<UISelect
label={I18n.t('Creator')}
showClear={false}
value={scopeType}
optionList={scopeOptions}
onChange={v => {
setScopeType(v as DatasetScopeType);
}}
/>
) : null;
} else if (filterType === 'search-type') {
return (
<UISelect
data-testid={
BotE2e.BotKnowledgeSelectListModalCreateDateSelect
}
label={I18n.t('Sort')}
showClear={false}
value={searchType}
optionList={[
{
label: I18n.t('Create_time'),
value: OrderField.CreateTime,
},
{
label: I18n.t('Update_time'),
value: OrderField.UpdateTime,
},
]}
onChange={v => {
handleSearchTypeChange(v as OrderField);
}}
/>
);
} else if (filterType === 'query-input') {
return (
<UISearch
key="filterType"
loading={loading}
onSearch={handleQueryChange}
/>
);
}
})}
{scene === Scene.MODAL && canCreate ? (
<UIButton
theme="solid"
onClick={handleAdd}
data-testid={BotE2e.BotKnowledgeSelectListModalCreateBtn}
>
{I18n.t('datasets_create_btn')}
</UIButton>
) : null}
</div>
</div>
) : null}
{renderContent()}
</div>
{createKnowledgeModal?.modal}
</Spin>
);
return { renderContentFilter, renderSearch, renderCreateBtn, renderFilters };
};
export { useKnowledgeFilter };

View File

@@ -0,0 +1,239 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import copy from 'copy-to-clipboard';
import { BotE2e } from '@coze-data/e2e';
import { I18n, getUnReactiveLanguage } from '@coze-arch/i18n';
import { UITag, Toast, Tooltip, Image, Typography } from '@coze-arch/bot-semi';
import { IconCopy } from '@coze-arch/bot-icons';
import {
KnowledgeShowSourceMode,
KnowledgeNoRecallReplyMode,
} from '@coze-arch/bot-api/playground_api';
import ZhCustomizePromptPNG from '../assets/customize-prompt-zh.png';
import EnCustomizePromptPNG from '../assets/customize-prompt-en.png';
import { type RadioItem } from './radio-group-setting';
import styles from './index.module.less';
export const MAX_TOP_K_VALUE = 5;
export const FULL_TEXT_SEARCH_KEY = 20;
export const getSearchStrategyOptions = () => [
{
label: I18n.t('knowledge_hybird_search_title'),
value: 1,
tip: I18n.t('knowledge_hybird_search_tooltip'),
},
{
label: I18n.t('knowledge_semantic_search_title'),
value: 0,
tip: I18n.t('knowledge_semantic_search_tooltip'),
},
{
label: I18n.t('knowledge_full_text_search_title'),
value: 20,
tip: I18n.t('knowledge_full_text_search_tooltip'),
},
];
export const getAutomaticCallOptions = () => {
const onCopy = (text: string) => {
const res = copy(text);
if (!res) {
return;
}
Toast.success({
content: I18n.t('copy_success'),
showClose: false,
});
};
return [
{
e2e: BotE2e.BotKnowledgeSettingModalAutoRadio,
label: I18n.t('dataset_automatic_call'),
value: 1,
},
{
e2e: BotE2e.BotKnowledgeSettingModalManualRadio,
label: I18n.t('dataset_on_demand_call'),
value: 0,
desc: (
<>
{I18n.t('bot_edit_dataset_on_demand_prompt1')}
<Tooltip content={I18n.t('bot_edit_datasets_copyName')}>
<UITag
onClick={() => onCopy(I18n.t('dataset_recall_copy_value'))}
type="light"
className={styles['setting-item-copy']}
>
{I18n.t('dataset_recall_copy_label')}
<IconCopy className={styles['icon-copy']} />
</UITag>
</Tooltip>
{I18n.t('bot_edit_dataset_on_demand_prompt2')}
</>
),
},
];
};
export const getNoRecallReplyOptions = (): RadioItem[] => [
{
e2e: BotE2e.BotKnowledgeSettingNoRecallReplyModeDefaultRadio,
label: I18n.t('No_recall_003'),
value: KnowledgeNoRecallReplyMode.Default,
},
{
e2e: BotE2e.BotKnowledgeSettingNoRecallReplyModeCustomizePromptRadio,
label: I18n.t('No_recall_004'),
value: KnowledgeNoRecallReplyMode.CustomizePrompt,
tip: (
<>
<div
style={{
lineHeight: '20px',
color: 'rgba(29, 28, 35, 1)',
marginBottom: '8px',
}}
>
{I18n.t('No_recall_007')}
</div>
<Image
width={344}
preview={false}
src={
I18n.language === 'zh-CN'
? ZhCustomizePromptPNG
: EnCustomizePromptPNG
}
/>
</>
),
tipStyle: {
backgroundColor: '#fff',
padding: '16px',
minWidth: '376px',
maxWidth: '376px',
},
},
];
export const localeMapLink: Record<string, string> = {
'zh-CN': '/docs/guides/knowledge',
en: '/docs/guides/knowledge_overview?_lang=en',
};
export const getShowSourceModeOptions = (): RadioItem[] => {
const language = getUnReactiveLanguage();
const goToGuides = (module = 'knowledge') => {
window.open(
`${window.location.origin}${
localeMapLink[language] || `/docs/guides/${module}`
}`,
);
};
return [
{
e2e: BotE2e.BotKnowledgeSettingShowSourceModeCardRadio,
label: I18n.t('knowledge_source_card_0002'),
value: KnowledgeShowSourceMode.CardList,
tip: (
<div className={styles['show-source-mode-tip']}>
<div className={styles.title}>
{I18n.t('knowledge_source_card_0004')}
</div>
<div className={styles.space}>
{[
{
title: I18n.t('what_is_coze'),
content: I18n.t('landingpage_description'),
guideModule: 'welcome',
},
{
title: I18n.t('knowledge_source_display_tooltip_link'),
content: I18n.t('knowledge_source_display_tooltip_content'),
guideModule: 'knowledge',
},
].map(i => (
<div
className={styles.card}
onClick={e => {
goToGuides(i.guideModule);
}}
>
<div className={styles.title}>
<div>{i.title}</div>
</div>
<Typography.Text
className={styles.content}
ellipsis={{
rows: 3,
showTooltip: false,
}}
>
{i.content}
</Typography.Text>
</div>
))}
</div>
</div>
),
tipStyle: {
backgroundColor: '#fff',
maxWidth: '436px',
minWidth: '436px',
padding: '16px',
},
},
{
e2e: BotE2e.BotKnowledgeSettingShowSourceModeTextRadio,
label: I18n.t('knowledge_source_card_0001'),
value: KnowledgeShowSourceMode.ReplyBottom,
tip: (
<div className={styles['show-source-mode-tip']}>
<div className={styles.title}>
{I18n.t('knowledge_source_card_0003')}
</div>
<div className={styles.main}>
<Typography.Text className={styles.content}>
{I18n.t('knowledge_source_display_tooltip_content')}
</Typography.Text>
<div className={styles.link}>
<div
onClick={e => {
goToGuides();
}}
>
1. {I18n.t('knowledge_source_display_tooltip_link')}
</div>
</div>
</div>
</div>
),
tipStyle: {
backgroundColor: '#fff',
maxWidth: '436px',
minWidth: '436px',
padding: '16px',
},
},
];
};

View File

@@ -0,0 +1,411 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable no-descending-specificity */
/* stylelint-disable selector-class-pattern */
@common-box-shadow: 0px 2px 8px 0px rgba(31, 35, 41, 0.02),
0px 2px 4px 0px rgba(31, 35, 41, 0.02), 0px 2px 2px 0px rgba(31, 35, 41, 0.02);
.common-svg-icon(@size: 14px, @color: #4d53e8) {
>svg {
width: @size;
height: @size;
>path {
fill: @color;
fill-opacity: 1;
}
}
}
.text {
font-size: 12px;
font-weight: 400;
font-style: normal;
line-height: 16px;
}
.icon-copy {
cursor: pointer;
padding: 2px;
border-radius: 4px;
.common-svg-icon(14px, rgba(107, 109, 117, 1));
&:hover {
background-color: var(--semi-color-fill-0);
}
}
.data-set-info {
display: flex;
align-items: center;
}
.default-text {
.text;
padding: 4px 0;
font-size: 14px;
line-height: 16px;
color: var(--light-usage-text-color-text-2, rgb(28 29 35 / 60%));
}
.setting-trigger {
cursor: pointer;
display: flex;
column-gap: 4px;
align-items: center;
margin-left: 8px;
font-size: 12px;
font-weight: 600;
font-style: normal;
line-height: 16px;
color: var(--light-color-brand-brand-5, #4d53e8);
&-icon {
svg {
width: 10px;
height: 10px;
}
}
}
.setting-content-popover {
background: #f7f7fa;
border-radius: 12px;
}
.setting {
overflow-y: auto;
max-width: 611px;
height: 454px;
padding: 24px;
font-size: 14px;
line-height: 20px;
color: var(--light-usage-text-color-text-0, #1f2329);
background-color: #f7f7fa;
border-radius: 12px;
.setting-title {
padding-bottom: 24px;
font-size: 18px;
font-weight: 600;
line-height: 24px;
}
.recall_title {
margin-top: 16px;
font-size: 16px;
font-weight: 600;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1d1c24);
}
.setting-item-container {
display: flex;
align-items: self-start;
justify-content: space-between;
min-height: 32px;
margin-top: 16px;
.setting-item {
width: 346px;
}
.setting-item-copy {
cursor: pointer;
margin: 0 4px;
padding: 2px 4px 2px 8px;
font-size: 12px;
font-weight: 500;
line-height: 16px;
color: var(--light-color-brand-brand-5, #4d53e8);
border-radius: 6px;
.icon-copy {
.common-svg-icon(14px, var(--light-color-brand-brand-5, #4d53e8));
margin: 0 0 0 4px;
}
}
:global {
.semi-tag-grey-light {
background: var(--light-color-brand-brand-1, #d9dcfa) !important;
}
}
}
.setting-source-display {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--Light-usage-border---color-border, rgba(29, 28, 35, 8%));
:global {
.semi-switch-checked {
background-color: var(--light-color-brand-brand-5, #4d53e8);
}
.semi-switch-disabled.semi-switch-checked {
background-color: var(--semi-color-primary-disabled);
}
}
&-title {
font-size: 16px;
font-weight: 600;
line-height: 22px;
}
}
}
.title-area {
display: flex;
align-items: center;
width: 220px;
font-size: 14px;
line-height: 22px;
color: var(--light-usage-text-color-text-0, #1c1d23);
&-icon {
cursor: pointer;
flex-shrink: 0;
margin-left: 8px;
color: #a7a9b0;
}
}
.slider-area {
&:hover {
.slider-boundary {
opacity: 1;
}
:global {
.semi-slider-mark {
display: block !important;
}
}
}
.slider-wrapper {
display: flex;
align-items: center;
.slider {
width: 210px;
margin-right: 24px;
:global {
.semi-slider-mark {
display: none;
margin-top: 9px;
font-size: 12px;
line-height: 16px;
}
}
}
.input-number {
width: 108px;
}
}
.slider-boundary {
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
width: 200px;
margin-top: 0;
padding: 0 12px;
font-size: 12px;
line-height: 16px;
color: var(--light-color-black-black, #000);
opacity: 0;
}
}
.tip-area {
width: 540px;
margin-top: 16px;
border-radius: 8px;
.icon {
width: 20px;
height: 20px;
}
.desc {
font-size: 14px;
line-height: 20px;
color: var(--Light-usage-text---color-text-0, #1c1f23);
}
}
.radio-area {
width: 346px;
:global {
.semi-radioGroup-horizontal {
gap: 24px;
}
}
.radio-item {
display: flex;
align-items: center;
.radio-item-desc {
margin: 4px 0 0 24px;
font-size: 14px;
line-height: 22px;
color: var(--light-usage-text-color-text-2, rgb(28 29 35 / 60%));
}
:global {
.semi-radio-addon {
font-weight: 600;
}
}
}
.radio-item-icon {
cursor: pointer;
flex-shrink: 0;
margin-left: 4px;
color: #a7a9b0;
}
.radio-desc {
box-sizing: border-box;
width: 346px;
margin: 8px 0;
padding: 12px;
font-size: 12px;
background: var(--Light-color-brand---brand-0, #f1f2fd);
border: 1px solid var(--Light-color-brand---brand-1, #d9dcfa);
border-radius: 8px;
}
}
.display_tooltip {
.display_tooltip_content {
margin-top: 16px;
padding: 12px;
background: var(--Light-usage-fill---color-fill-0, rgba(46, 46, 57, 4%));
border-radius: var(--default, 8px);
.display_tooltip_content_link {
// text-decoration: underline;
cursor: pointer;
display: flex;
margin-top: 8px;
color: var(--Light-usage-primary---color-primary, #4c54f0);
.link_num {
margin-right: 10px;
color: var(--Light-usage-text---color-text-0, #1d1c24);
text-decoration: none !important;
}
.display_tooltip_link {
text-decoration: underline;
}
}
}
}
.show-source-mode-tip {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
.title {
font-size: 14px;
font-weight: 600;
line-height: 20px;
color: rgba(29, 28, 35, 100%);
}
// Footer展示
.main {
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px;
background: #f9f9f9;
border: 1px solid rgba(6, 7, 9, 10%);
border-radius: 16px;
.content {
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: rgba(6, 7, 9, 80%);
}
.link {
cursor: pointer;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: #543ef7;
}
}
// Card展示
.space {
display: flex;
gap: 8px;
justify-content: center;
.card {
cursor: pointer;
display: flex;
flex-direction: column;
gap: 8px;
width: 177px;
padding: 16px;
background-color: rgba(244, 244, 246, 100%);
border-radius: 8px;
.content {
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: rgba(6, 7, 9, 50%);
}
.title {
font-size: 12px;
font-weight: 500;
line-height: 16px;
color: rgba(6, 7, 9, 80%);
}
}
}
}

View File

@@ -0,0 +1,392 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useRef } from 'react';
import { debounce, isEmpty } from 'lodash-es';
import { produce } from 'immer';
import classNames from 'classnames';
import { BotE2e } from '@coze-data/e2e';
import { RerankTips, RewriteTips } from '@coze-common/biz-tooltip-ui';
import { I18n, getUnReactiveLanguage } from '@coze-arch/i18n';
import { Banner, Form, Switch } from '@coze-arch/bot-semi';
import { IconWarningInfo } from '@coze-arch/bot-icons';
import { getFlags } from '@coze-arch/bot-flags';
import {
KnowledgeShowSourceMode,
KnowledgeNoRecallReplyMode,
} from '@coze-arch/bot-api/playground_api';
import { recallStrategyUpdater } from './utils';
import { type RagModeConfigurationProps } from './type';
import { SliderSetting } from './slider-setting';
import { SettingItem } from './setting-item';
import { RadioGroupSetting } from './radio-group-setting';
import {
MAX_TOP_K_VALUE,
getAutomaticCallOptions,
getShowSourceModeOptions,
getNoRecallReplyOptions,
getSearchStrategyOptions,
localeMapLink,
} from './constant';
import styles from './index.module.less';
const DATASET_INFO_MIN_SCORE = 0.01;
/* eslint-disable @coze-arch/max-line-per-function*/
// eslint-disable-next-line complexity, max-lines-per-function
export function RagModeConfiguration({
dataSetInfo,
onDataSetInfoChange,
showTitle = true,
isReadonly = false,
showNL2SQLConfig,
showAuto = true,
showSourceDisplay = true,
}: RagModeConfigurationProps): JSX.Element {
const {
auto,
min_score: minScore,
top_k: topK,
search_strategy: searchStrategy,
show_source,
no_recall_reply_mode,
no_recall_reply_customize_prompt,
show_source_mode,
recall_strategy = {},
} = dataSetInfo;
// undefined 默认为 true
const {
use_nl2sql = true,
use_rerank = true,
use_rewrite = true,
} = recall_strategy;
const language = getUnReactiveLanguage();
const FLAGS = getFlags();
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const debounceOnNoRecallReplyCustomizePromptChange = debounce(v => {
onDataSetInfoChange({
...dataSetInfo,
no_recall_reply_customize_prompt: v,
});
}, 300);
return (
<div className={styles.setting}>
{showTitle ? (
<div
data-testid={BotE2e.BotKnowledgeSettingModalTitle}
className={classNames(
styles['setting-title'],
'dataset-setting-content-title',
)}
>
{I18n.t('dataset_settings_title')}
</div>
) : null}
<div className={styles.recall_title}>
{I18n.t('dataset-setting_recall_title')}
</div>
{showAuto ? (
<SettingItem
title={I18n.t('dataset_call_method')}
tip={I18n.t('knowledge_call_method_tooltip')}
>
<RadioGroupSetting
options={getAutomaticCallOptions()}
value={auto ? 1 : 0}
onChange={v => onDataSetInfoChange({ ...dataSetInfo, auto: !!v })}
disabled={isReadonly}
/>
</SettingItem>
) : null}
<SettingItem
title={I18n.t('knowledge_search_strategy_title')}
tip={I18n.t('knowledge_search_strategy_tooltip')}
>
<RadioGroupSetting
options={getSearchStrategyOptions()}
value={searchStrategy ?? 0}
onChange={v =>
onDataSetInfoChange({ ...dataSetInfo, search_strategy: v })
}
disabled={isReadonly}
/>
</SettingItem>
<SettingItem
title={I18n.t('dataset_max_recall')}
tip={I18n.t('bot_edit_datasetsSettings_MaxTip')}
>
<SliderSetting
min={1}
max={10}
step={1}
precision={0}
value={topK}
marks={{ 3: I18n.t('dataset_max_recall_default') }}
onChange={v => {
onDataSetInfoChange({
...dataSetInfo,
top_k: v,
});
}}
disabled={isReadonly}
/>
</SettingItem>
{topK > MAX_TOP_K_VALUE && (
<Banner
bordered
type="warning"
fullMode={false}
closeIcon={null}
className={classNames(
styles['tip-area'],
'dataset-setting-content-tip-area',
)}
icon={<IconWarningInfo className={styles.icon} />}
description={
<span className={styles.desc}>
{I18n.t('dataset_max_recall_desc')}
</span>
}
/>
)}
{recall_strategy.use_rerank ? (
<SettingItem
title={I18n.t('dataset_min_degree')}
tip={I18n.t('bot_edit_datasetsSettings_MinTip')}
>
<SliderSetting
min={DATASET_INFO_MIN_SCORE}
max={0.99}
step={0.01}
precision={2}
value={minScore}
marks={{ 0.5: I18n.t('dataset_min_degree_default') }}
disabled={isReadonly}
onChange={v => {
onDataSetInfoChange({
...dataSetInfo,
min_score: v,
});
}}
/>
</SettingItem>
) : null}
{showNL2SQLConfig ? (
<SettingItem
title={I18n.t('kl_write_022')}
tip={I18n.t('kl_write_023')}
>
<Switch
checked={use_nl2sql}
onChange={value => {
onDataSetInfoChange(
produce(dataSetInfo, draft =>
recallStrategyUpdater({
datasetInfo: draft,
field: 'use_nl2sql',
value,
}),
),
);
}}
/>
</SettingItem>
) : null}
<SettingItem title={I18n.t('kl_write_024')} tip={<RewriteTips />}>
<Switch
checked={use_rewrite}
onChange={value => {
onDataSetInfoChange(
produce(dataSetInfo, draft =>
recallStrategyUpdater({
datasetInfo: draft,
field: 'use_rewrite',
value,
}),
),
);
}}
/>
</SettingItem>
<SettingItem title={I18n.t('kl_write_026')} tip={<RerankTips />}>
<Switch
checked={use_rerank}
onChange={value => {
onDataSetInfoChange(
produce(dataSetInfo, draft => {
const nextState = {
datasetInfo: draft,
field: 'use_rerank',
value,
} as const;
if (!value) {
nextState.datasetInfo.min_score = 0;
} else if (!nextState.datasetInfo.min_score) {
nextState.datasetInfo.min_score = DATASET_INFO_MIN_SCORE;
}
return recallStrategyUpdater(nextState);
}),
);
}}
/>
</SettingItem>
{FLAGS['bot.data.no_recall_reply'] ? (
<div className={styles['setting-source-display']}>
<div className={styles['setting-source-display-title']}>
{I18n.t('No_recall_001')}
</div>
<SettingItem
title={I18n.t('No_recall_002')}
tip={
<div className={styles.display_tooltip}>
{I18n.t('No_recall_005')}
</div>
}
>
<RadioGroupSetting
options={getNoRecallReplyOptions()}
value={no_recall_reply_mode ?? KnowledgeNoRecallReplyMode.Default}
onChange={v =>
onDataSetInfoChange({
...dataSetInfo,
no_recall_reply_mode: v,
no_recall_reply_customize_prompt:
v === KnowledgeNoRecallReplyMode.CustomizePrompt &&
isEmpty(no_recall_reply_customize_prompt)
? I18n.t('No_recall_006')
: no_recall_reply_customize_prompt,
})
}
disabled={isReadonly}
/>
</SettingItem>
{no_recall_reply_mode ===
KnowledgeNoRecallReplyMode.CustomizePrompt ? (
<Form<Record<string, unknown>>
initValues={{
no_recall_reply_customize_prompt:
no_recall_reply_customize_prompt ?? I18n.t('No_recall_006'),
}}
>
<Form.TextArea
maxLength={500}
maxCount={500}
ref={textAreaRef}
onChange={debounceOnNoRecallReplyCustomizePromptChange}
rows={2}
disabled={isReadonly}
placeholder={I18n.t(
'card_builder_dataEditor_get_errormsg_please_enter',
)}
pure
field="no_recall_reply_customize_prompt"
/>
</Form>
) : null}
</div>
) : null}
{FLAGS['bot.data.source_display'] && showSourceDisplay ? (
<div className={styles['setting-source-display']}>
<div
className={styles['setting-source-display-title']}
data-testid={BotE2e.BotKnowledgeSettingShowSourceDisplayTitle}
>
{I18n.t('knowledge_source_display_title')}
</div>
<SettingItem
title={I18n.t('knowledge_source_display_status')}
tipStyle={{
backgroundColor: '#fff',
color: 'var(--Light-usage-text---color-text-0, #1D1C24)',
maxWidth: '453px',
minWidth: '453px',
}}
tip={
<div className={styles.display_tooltip}>
<div className={styles.display_tooltip_title}>
{I18n.t('knowledge_source_display_tooltip_title')}
</div>
<div className={styles.display_tooltip_content}>
<div>
{I18n.t('knowledge_source_display_tooltip_content')}
</div>
<div className={styles.display_tooltip_content_link}>
<div className={styles.link_num}>1.</div>
<div
className={styles.display_tooltip_link}
onClick={() =>
window.open(
`${window.location.origin}${
localeMapLink[language] || '/docs/guides/knowledge'
}`,
)
}
>
{I18n.t('knowledge_source_display_tooltip_link')}
</div>
</div>
</div>
</div>
}
>
<Switch
data-testid={BotE2e.BotKnowledgeSettingShowSourceDisplaySwitch}
className={styles.display_status}
checked={show_source}
disabled={isReadonly}
onChange={v => {
onDataSetInfoChange({
...dataSetInfo,
show_source: v,
// 展示方式没有值且打开展示来源,默认选中卡片
...(!show_source_mode && v
? {
show_source_mode: KnowledgeShowSourceMode.CardList,
}
: {}),
});
}}
/>
</SettingItem>
{show_source ? (
<SettingItem title={I18n.t('Display format')}>
<RadioGroupSetting
options={getShowSourceModeOptions()}
value={show_source_mode ?? KnowledgeShowSourceMode.ReplyBottom}
onChange={v =>
onDataSetInfoChange({ ...dataSetInfo, show_source_mode: v })
}
disabled={isReadonly}
/>
</SettingItem>
) : null}
</div>
) : null}
</div>
);
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { type CSSProperties, type ReactNode } from 'react';
import classNames from 'classnames';
import { Radio, RadioGroup, Popover } from '@coze-arch/bot-semi';
import { IconInfo } from '@coze-arch/bot-icons';
import styles from './index.module.less';
export interface RadioItem {
label: string;
value: number;
e2e?: string;
tip?: ReactNode;
tipStyle?: CSSProperties;
desc?: string | ReactNode;
}
export interface RadioGroupSettingProps {
options: RadioItem[];
value: number;
disabled?: boolean;
onChange: (value: number) => void;
}
export function RadioGroupSetting({
options,
value,
disabled,
onChange,
}: RadioGroupSettingProps) {
const desc = options.find(v => v.value === value)?.desc;
return (
<div className={styles['radio-area']}>
<RadioGroup
onChange={e => onChange(e.target.value as number)}
value={value}
disabled={disabled}
>
{options.map(item => (
<div
data-testid={item.e2e}
key={item.value}
className={classNames(
styles['radio-item'],
value === item.value ? styles.active : styles.normal,
)}
>
<Radio value={item.value}>{item.label}</Radio>
{!!item.tip && (
<Popover
showArrow
position="top"
zIndex={1031}
style={{
backgroundColor: '#41464c',
color: '#fff',
maxWidth: '276px',
...(item.tipStyle || {}),
}}
content={item.tip}
>
<IconInfo className={styles['radio-item-icon']} />
</Popover>
)}
</div>
))}
</RadioGroup>
{desc ? <div className={styles['radio-desc']}>{desc}</div> : null}
</div>
);
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import classNames from 'classnames';
import { type I18nKeysNoOptionsType } from '@coze-arch/i18n';
import { I18n } from '@coze-arch/i18n';
import { TitleArea } from './title-area';
import styles from './index.module.less';
export interface SettingItemProps {
title: string;
tip?: string | React.ReactNode;
children: React.ReactNode;
className?: string;
tipStyle?: Record<string, string | number>;
}
export const SettingItem = ({
title,
tip,
children,
className,
tipStyle,
}: SettingItemProps) => (
<div className={classNames(styles['setting-item-container'], className)}>
<TitleArea
title={I18n.t(title as unknown as I18nKeysNoOptionsType)}
tip={tip || ''}
tipStyle={tipStyle}
/>
<div
className={classNames(
styles['setting-item'],
'dataset-setting-content-item',
)}
>
{children}
</div>
</div>
);

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { InputNumber, Slider } from '@coze-arch/bot-semi';
import styles from './index.module.less';
interface SliderSettingProps {
min: number;
max: number;
step: number;
precision: number;
value: number;
marks: Record<number, string>;
onChange: (value: number) => void;
disabled: boolean;
}
export const SliderSetting = ({
min = 0,
max = 100,
step = 1,
precision = 0,
value,
marks,
onChange,
disabled,
}: SliderSettingProps) => (
<div className={styles['slider-area']}>
<div className={styles['slider-wrapper']}>
<div className={styles.slider}>
<Slider
step={step}
min={min}
max={max}
value={value}
marks={marks}
disabled={disabled}
onChange={v => onChange(v as number)}
></Slider>
</div>
<InputNumber
className={styles['input-number']}
step={step}
precision={precision}
onChange={v => {
let inputValue = Number(v);
if (isNaN(inputValue)) {
inputValue = value;
} else {
inputValue = inputValue || value;
inputValue = Math.max(inputValue, 0);
}
if (inputValue > max) {
inputValue = max;
}
onChange(inputValue);
}}
value={value}
min={min}
max={max}
disabled={disabled}
/>
</div>
<div className={styles['slider-boundary']}>
<div className={styles.min}>{min}</div>
<div className={styles.max}>{max}</div>
</div>
</div>
);

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { IconInfo } from '@coze-arch/bot-icons';
import { Popover } from '@coze-arch/coze-design';
import styles from './index.module.less';
interface TitleAreaProps {
title: string;
tipStyle?: Record<string, string | number>;
tip?: string | React.ReactNode;
}
export function TitleArea({ title, tip, tipStyle = {} }: TitleAreaProps) {
return (
<div className={styles['title-area']}>
{title}
{!!tip && (
<Popover
showArrow
position="top"
zIndex={1031}
style={{
maxWidth: '276px',
...tipStyle,
}}
content={tip}
>
<IconInfo className={styles['title-area-icon']} />
</Popover>
)}
</div>
);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type KnowledgeShowSourceMode,
type KnowledgeNoRecallReplyMode,
type RecallStrategy,
} from '@coze-arch/bot-api/playground_api';
export interface IDataSetInfo {
min_score: number;
top_k: number;
auto: boolean;
search_strategy?: number;
show_source?: boolean;
no_recall_reply_mode?: KnowledgeNoRecallReplyMode;
no_recall_reply_customize_prompt?: string;
show_source_mode?: KnowledgeShowSourceMode;
recall_strategy?: RecallStrategy;
}
export interface RagModeConfigurationProps {
dataSetInfo: IDataSetInfo;
onDataSetInfoChange: (v: IDataSetInfo) => void;
showTitle?: boolean;
isReadonly?: boolean;
showNL2SQLConfig?: boolean;
showAuto?: boolean;
showSourceDisplay?: boolean;
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type RecallStrategy } from '@coze-arch/bot-api/playground_api';
import { type IDataSetInfo } from './type';
export const recallStrategyUpdater: (params: {
datasetInfo: IDataSetInfo;
field: keyof RecallStrategy;
value: boolean;
}) => IDataSetInfo = ({ datasetInfo, field, value }) => {
if (!datasetInfo.recall_strategy) {
datasetInfo.recall_strategy = {
[field]: value,
};
} else {
datasetInfo.recall_strategy[field] = value;
}
return datasetInfo;
};

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useState } from 'react';
import { I18n } from '@coze-arch/i18n';
import { FrequencyType } from '@coze-arch/bot-api/memory';
import { type AuthFrequencyInfo } from '@coze-arch/bot-api/knowledge';
import { Select } from '@coze-arch/coze-design';
interface AccountFrequencyItemProps {
accountInfo: AuthFrequencyInfo;
onFrequencyChange: (account: AuthFrequencyInfo) => void;
}
// TODO: hzf 需要修改为i18n
const FREQUENCY_OPTIONS = [
{ label: I18n.t('knowledge_weixin_015'), value: FrequencyType.None },
{ label: I18n.t('knowledge_weixin_016'), value: FrequencyType.EveryDay },
{ label: I18n.t('knowledge_weixin_017'), value: FrequencyType.EveryThreeDay },
{ label: I18n.t('knowledge_weixin_018'), value: FrequencyType.EverySevenDay },
];
export const AccountFrequencyItem = ({
accountInfo,
onFrequencyChange,
}: AccountFrequencyItemProps) => {
const [frequency, setFrequency] = useState<FrequencyType>(
accountInfo.auth_frequency_type,
);
const handleFrequencyChange = (value: FrequencyType) => {
setFrequency(value);
onFrequencyChange({
...accountInfo,
auth_frequency_type: value,
});
};
return (
<div className="flex flex-col">
<div className="text-[14px] coz-fg-primary mb-1 font-medium">
{accountInfo.auth_name}
</div>
<Select
value={frequency}
onChange={value => handleFrequencyChange(value as FrequencyType)}
optionList={FREQUENCY_OPTIONS}
className="w-full"
/>
</div>
);
};

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useState, useEffect } from 'react';
import { I18n } from '@coze-arch/i18n';
import { type AuthFrequencyInfo } from '@coze-arch/bot-api/knowledge';
import { Toast, Modal } from '@coze-arch/coze-design';
import { saveSettingChange } from '../service/use-case/save-setting-change';
import { useInit } from '../hooks/life-cycle/use-init';
import { AccountFrequencyItem } from './account-frequency-item';
export const SetAppendFrequencyModal = (props: {
datasetId: string;
onFinish: () => void;
onClose?: () => void;
}) => {
const { datasetId, onFinish, onClose } = props;
const [pendingAccounts, setPendingAccounts] = useState<AuthFrequencyInfo[]>(
[],
);
const [loading, setLoading] = useState(false);
const { initAccountList, initLoading } = useInit(datasetId);
useEffect(() => {
if (initAccountList) {
setPendingAccounts(initAccountList);
}
}, [initAccountList]);
return (
<Modal
// @ts-expect-error --TODO:hzf 需要修改为i18n
title={I18n.t('设置追加频率')}
className="w-[520px]"
centered
visible
cancelText={I18n.t('Cancel')}
okText={I18n.t('knowledge_optimize_007')}
okButtonProps={{ loading: loading || initLoading }}
onOk={async () => {
try {
setLoading(true);
await saveSettingChange({
datasetId,
pendingAccounts,
});
Toast.success(I18n.t('Update_success'));
onClose?.();
onFinish();
} catch {
Toast.error(I18n.t('Update_failed'));
} finally {
setLoading(false);
}
}}
onCancel={() => {
onClose?.();
if (initAccountList) {
setPendingAccounts(initAccountList);
}
}}
>
<>
<div className="text-[14px] coz-fg-primary mb-[30px]">
{/**@ts-expect-error --TODO:hzf 需要修改为i18n */}
{I18n.t('设置追加频率后,当前频率自动追加')}
</div>
<div className="flex flex-col gap-2">
{pendingAccounts.map(account => (
<AccountFrequencyItem
key={account.auth_id}
accountInfo={account}
onFrequencyChange={newAccount => {
setPendingAccounts(prev =>
prev.map(item =>
item.auth_id === newAccount.auth_id ? newAccount : item,
),
);
}}
/>
))}
</div>
</>
</Modal>
);
};

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useRequest } from 'ahooks';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { KnowledgeApi } from '@coze-arch/bot-api';
export const useInit = (datasetId: string) => {
const { data: initAccountList, loading: initLoading } = useRequest(
async () => {
const response = await KnowledgeApi.GetAppendFrequency({
dataset_id: datasetId,
});
return response.auth_frequency_info;
},
{
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeGetAuthList,
error,
});
},
},
);
return {
initAccountList,
initLoading,
};
};

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useState } from 'react';
import { SetAppendFrequencyModal } from '../../components/main';
export const useSetAppendFrequencyModal = (modalProps: {
datasetId: string;
onFinish: () => void;
}) => {
const [visible, setVisible] = useState(false);
const open = () => {
setVisible(true);
};
const close = () => {
setVisible(false);
};
const node = visible ? (
<SetAppendFrequencyModal
datasetId={modalProps.datasetId}
onFinish={modalProps.onFinish}
onClose={close}
/>
) : null;
return {
node,
open,
close,
};
};

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { useSetAppendFrequencyModal } from './hooks/use-case/use-set-append-frequency-modal';

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { type AuthFrequencyInfo } from '@coze-arch/bot-api/knowledge';
import { KnowledgeApi } from '@coze-arch/bot-api';
export const saveSettingChange = async (params: {
datasetId: string;
pendingAccounts: AuthFrequencyInfo[];
}) => {
const { datasetId, pendingAccounts } = params;
try {
await KnowledgeApi.SetAppendFrequency({
dataset_id: datasetId,
auth_frequency_info: pendingAccounts.map(account => ({
auth_id: account.auth_id,
auth_frequency_type: account.auth_frequency_type,
})),
});
} catch (error) {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeUpdateWechatFrequency,
error: error as Error,
});
throw error;
}
};

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useDataModalWithCoze } from '@coze-data/utils';
import { I18n } from '@coze-arch/i18n';
import { type ButtonColor } from '@coze-arch/coze-design/types';
export interface IDeleteModalProps {
onDel: () => void | Promise<void>;
}
export const useSliceDeleteModal = ({ onDel }: IDeleteModalProps) => {
const { modal, open, close } = useDataModalWithCoze({
title: I18n.t('delete_title'),
cancelText: I18n.t('Cancel'),
okText: I18n.t('Delete'),
showCloseIcon: false,
okButtonColor: 'red' as ButtonColor,
okButtonProps: {
type: 'danger',
},
onOk: async () => {
await onDel?.();
close?.();
},
onCancel: () => close(),
});
return {
node: modal(
<div className={'coz-fg-secondary'}>{I18n.t('delete_desc')}</div>,
),
delete: open,
close,
};
};

View File

@@ -0,0 +1,301 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @coze-arch/max-line-per-function */
import { useEffect, useMemo, useRef, useState } from 'react';
import { useRequest } from 'ahooks';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { CustomError } from '@coze-arch/bot-error';
import { type DocTableColumn, ColumnType } from '@coze-arch/bot-api/memory';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { transSliceContentOutput } from '../utils';
import { TableSegmentModal } from './modal';
export enum ModalActionType {
Create,
Edit,
}
export interface DocTableColumnExt extends DocTableColumn {
error: string;
value: string;
column_id: string;
}
export interface UseTableSegmentModalParams {
title: string | JSX.Element;
meta: DocTableColumn[];
disabled?: boolean;
canEdit?: boolean;
loading?: boolean;
onSubmit?: (actionType: ModalActionType, data: TableDataItem[]) => void;
onFinish: (actionType: ModalActionType, data: TableDataItem[]) => void;
}
interface UseTableSegmentModalReturnValue {
node: JSX.Element | null;
create: () => void;
edit: (data: TableDataItem[] | string) => void;
close: () => void;
fetchCreateTableSegment: (
docId: string,
createContent: TableDataItem[],
) => void;
fetchUpdateTableSegment: (
sliceId: string,
updateContent: TableDataItem[],
) => void;
}
export interface TableDataItem {
column_id: string;
column_name?: string;
column_type?: ColumnType;
is_semantic?: boolean;
value: string;
error?: string;
}
const CONTENT_MAX_LENGTH = 2000;
const getFormData = (data: TableDataItem[]) => {
const formData: Record<string, string> = {};
data.map(item => {
formData[item.column_id] = item.value || '';
});
return JSON.stringify(formData);
};
const updateWithSliceList = (
meta: DocTableColumn[],
slices: TableDataItem[],
) => {
const extendedTableMeta: DocTableColumnExt[] = meta.map(column => ({
...column,
error: '',
value: '',
column_id: column.id || '',
column_name: column.column_name || '',
is_semantic: Boolean(column.is_semantic),
}));
if (slices.length === 0) {
return extendedTableMeta;
}
slices.forEach(slice => {
const column = extendedTableMeta.find(col => col.id === slice.column_id);
if (column) {
column.value = slice.value || '';
}
});
return extendedTableMeta;
};
export const useTableSegmentModal = ({
title,
meta = [],
canEdit,
disabled,
onSubmit,
onFinish,
loading,
}: UseTableSegmentModalParams): UseTableSegmentModalReturnValue => {
const [visible, setVisible] = useState(false);
const [tableData, setTableData] = useState<TableDataItem[]>([]);
const tableMeta = useRef<DocTableColumn[]>(meta);
const actionTypeRef = useRef<ModalActionType>(ModalActionType.Create);
const getFormVerification = () => {
let isValid = true;
const newTextAreas = tableData.map(textArea => {
const newTextArea = { ...textArea };
if (newTextArea?.is_semantic) {
if (newTextArea.value.length === 0) {
newTextArea.error = I18n.t('knowledge_table_content_empty');
isValid = false;
} else if (newTextArea.value.length > CONTENT_MAX_LENGTH) {
newTextArea.error = I18n.t('knowledge_table_content_limt', {
number: CONTENT_MAX_LENGTH,
});
isValid = false;
}
}
return newTextArea;
});
if (!isValid) {
setTableData(newTextAreas);
}
return isValid;
};
const { run: fetchCreateTableSegment, loading: createLoading } = useRequest(
async (docId: string, createContent: TableDataItem[]) => {
if (!docId) {
throw new CustomError('normal_error', 'missing doc_id');
}
await KnowledgeApi.CreateSlice({
document_id: docId,
raw_text: getFormData(createContent),
});
return createContent;
},
{
manual: true,
onSuccess: data => {
onFinish(actionTypeRef.current, data);
onCancel();
},
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeCreateSlice,
error,
});
},
},
);
const { run: fetchUpdateTableSegment, loading: uploadLoading } = useRequest(
async (sliceId: string, updateContent: TableDataItem[]) => {
if (!sliceId) {
throw new CustomError('normal_error', 'missing slice_id');
}
const formatRecord = updateContent.map(colData => {
if (colData.column_type === ColumnType.Image) {
return {
...colData,
value: transSliceContentOutput(colData.value),
};
}
return colData;
});
await KnowledgeApi.UpdateSlice({
slice_id: sliceId,
raw_text: getFormData(formatRecord),
});
return updateContent;
},
{
manual: true,
onSuccess: data => {
onFinish(actionTypeRef.current, data);
onCancel();
},
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeUpdateSlice,
error,
});
},
},
);
const modalLoading = useMemo(
() => createLoading || uploadLoading || loading,
[createLoading, uploadLoading, loading],
);
useEffect(() => {
tableMeta.current = meta;
}, [meta]);
const handleSubmit = () => {
const isValid = getFormVerification();
if (isValid) {
if (typeof onSubmit === 'function') {
onSubmit(actionTypeRef.current, tableData);
} else {
setVisible(false);
onFinish(actionTypeRef.current, tableData);
}
}
};
const onCancel = () => {
setVisible(false);
};
const onOpen = (newTableData?: TableDataItem[]) => {
if (newTableData && newTableData.length) {
setTableData(updateWithSliceList(tableMeta.current, newTableData));
setVisible(true);
} else {
setTableData(updateWithSliceList(tableMeta.current, []));
setVisible(true);
}
};
const handleTextAreaChange = (index: number, newValue: string) => {
const newData = [...tableData];
newData[index].value = newValue;
// 针对语义匹配行进行检验
if (newData[index]?.is_semantic) {
if (newValue.length === 0) {
newData[index].error = I18n.t('knowledge_table_content_empty');
} else if (newValue.length > CONTENT_MAX_LENGTH) {
newData[index].error = I18n.t('knowledge_table_content_limt', {
number: CONTENT_MAX_LENGTH,
});
} else {
newData[index].error = '';
}
}
setTableData(newData);
};
return {
fetchCreateTableSegment,
fetchUpdateTableSegment,
edit: originTableData => {
let newTableData = originTableData;
if (typeof originTableData === 'string') {
newTableData = JSON.parse(originTableData) as TableDataItem[];
}
actionTypeRef.current = ModalActionType.Edit;
if (Array.isArray(newTableData)) {
onOpen(newTableData);
}
},
create: () => {
actionTypeRef.current = ModalActionType.Create;
onOpen();
},
close: onCancel,
node: visible ? (
<TableSegmentModal
title={title}
visible={visible}
onSubmit={handleSubmit}
onCancel={onCancel}
canEdit={canEdit ?? true}
tableData={tableData}
loading={modalLoading}
handleTextAreaChange={handleTextAreaChange}
/>
) : null,
};
};

View File

@@ -0,0 +1,95 @@
.table-content-modal {
&.has-preview-modal {
padding-bottom: 40px;
}
.table-row {
display: flex;
gap: 8px;
align-items: center;
align-self: stretch;
}
.table-column {
display: -webkit-box;
flex-shrink: 0;
min-width: 88px;
padding-right: 8px;
padding-left: 8px;
-webkit-box-orient: vertical;
}
.column_name {
.table-column();
flex-shrink: 0;
width: 240px;
min-width: 88px;
}
.is_semantic {
.table-column();
width: 120px;
}
.value {
.table-column();
flex: 1 0 0;
:global {
.modal-image-render {
height: 34px;
}
.modal-empty-image-render {
cursor: pointer;
height: 34px;
}
}
}
.table-header {
font-size: 12px;
font-weight: 500;
color: var(--coz-fg-secondary);
}
.header-row {
padding: 4px 8px;
border-bottom: 1px solid var(--coz-stroke-primary)
}
.table-body {
color: var(--coz-fg-secondary);
}
.tbody-row {
padding: 10px 8px;
&:last-child {
margin-bottom: 0;
}
}
.error-tips {
margin-top: 4px;
font-size: 12px;
line-height: 16px;
color: #F93920;
}
}
.image-render-wrapper {
&:hover {
:global {
.modal-empty-image-render {
background-color: transparent;
}
}
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {
useTableSegmentModal,
type UseTableSegmentModalParams,
type TableDataItem,
ModalActionType,
} from './hooks';
export { getSrcFromImg } from './modal';

View File

@@ -0,0 +1,254 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState, type ComponentProps } from 'react';
import { ImageRender } from '@coze-common/table-view';
import { I18n } from '@coze-arch/i18n';
import { ColumnType } from '@coze-arch/bot-api/memory';
import { IconCozImage } from '@coze-arch/coze-design/icons';
import { TextArea, Button, Modal } from '@coze-arch/coze-design';
import { type TableDataItem } from './hooks';
import styles from './index.module.less';
export interface TableSegmentModalProps extends ComponentProps<typeof Modal> {
tableData: TableDataItem[];
canEdit: boolean;
handleTextAreaChange: (index: number, value: string) => void;
onCancel: (e: React.MouseEvent<Element, MouseEvent>) => void;
onSubmit: () => void;
loading?: boolean;
}
interface RenderFooterProps {
loading?: boolean;
onCancel: (e: React.MouseEvent<Element, MouseEvent>) => void;
onSubmit: (e: React.MouseEvent<Element, MouseEvent>) => void;
}
interface TableColumn {
key: string;
title: string;
render?: (item: TableDataItem, index: number) => React.ReactNode;
}
interface TableSegmentContentProps {
columns: TableColumn[];
tableData: TableDataItem[];
canEdit: boolean;
handleTextAreaChange: (index: number, value: string) => void;
}
export const getSrcFromImg = (str: string): string[] => {
if (!str) {
return [];
}
const imgRegx = /<img[^>]+src\s*=\s*['"]([^'"]+)['"][^>]*>/g;
// 使用正则表达式进行匹配
const matches = str.match(imgRegx);
// 提取匹配结果中的src属性值
const srcList: string[] = [];
if (matches) {
for (const match of matches) {
const src = match.match(/src\s*=\s*['"]([^'"]+)['"]/)?.[1];
if (src) {
srcList.push(src);
}
}
}
return srcList;
};
const TableSegmentContent: React.FC<TableSegmentContentProps> = ({
columns,
tableData,
canEdit,
}) => (
<div
className={`${styles['table-content-modal']} ${
canEdit ? '' : styles['has-preview-modal']
}`}
>
<div className={styles['table-header']}>
<div className={`${styles['table-row']} ${styles['header-row']}`}>
{columns.map(column => (
<div key={column.key} className={styles[column.key]}>
{column.title}
</div>
))}
</div>
</div>
<div className={styles['table-body']}>
{tableData.map((item, index) => (
<div
className={`${styles['table-row']} ${styles['tbody-row']}`}
key={index}
>
{columns.map(column => (
<div key={column.key} className={styles[column.key]}>
{typeof column.render === 'function'
? column.render(item, index)
: item[column.key as keyof TableDataItem]}
</div>
))}
</div>
))}
</div>
</div>
);
const RenderFooter: React.FC<RenderFooterProps> = ({ ...modalProps }) => (
<>
<Button color="primary" onClick={modalProps.onCancel}>
{I18n.t('datasets_createFileModel_CancelBtn')}
</Button>
<Button
loading={modalProps.loading}
onClick={e => {
modalProps.onSubmit?.(e);
}}
>
{I18n.t('datasets_segment_detailModel_save')}
</Button>
</>
);
const OptimizedTextArea: React.FC<{
index: number;
value?: string;
disabled: boolean;
error?: string;
handleTextAreaChange: (index: number, value: string) => void;
}> = React.memo(
({ index, disabled, error, value: initialValue, handleTextAreaChange }) => {
const [value, setValue] = useState(initialValue);
const onBlur = () => handleTextAreaChange(index, value || '');
return (
<TextArea
disabled={disabled}
value={value}
onChange={setValue}
onBlur={onBlur}
autosize={{ minRows: 1, maxRows: 3 }}
maxLength={2000}
style={{ border: error ? '1px solid #F93920' : '' }}
/>
);
},
);
const ImageEmpty = (props: { onClick?: () => void }) => (
<Button
color="highlight"
icon={<IconCozImage className="text-[14px]" />}
{...props}
>
{I18n.t('knowledge_insert_img_002')}
</Button>
);
export const TableSegmentModal: React.FC<TableSegmentModalProps> = ({
onCancel,
onSubmit,
tableData,
canEdit,
handleTextAreaChange,
loading,
...modalProps
}) => {
const columns: TableColumn[] = [
{
key: 'column_name',
title: I18n.t('datasets_segment_tableStructure_field_name'),
},
{
key: 'is_semantic',
title: I18n.t('datasets_segment_tableStructure_semantic_name'),
render: item =>
item.is_semantic
? I18n.t('datasets_segment_tableStructure_semantic_yes')
: I18n.t('datasets_segment_tableStructure_semantic_no'),
},
{
key: 'value',
title: I18n.t('datasets_segment_tableStructure_field_value'),
render: (item, index) =>
item.column_type === ColumnType.Image ? (
<div className={styles['image-render-wrapper']}>
<ImageRender
className={
item.value ? 'modal-image-render' : 'modal-empty-image-render'
}
customEmpty={props => <ImageEmpty {...(props || {})} />}
srcList={getSrcFromImg(item.value)}
onChange={(src, tosKey) => {
let val = '';
if (src || tosKey) {
val = `<img src="${src ?? ''}" ${
tosKey ? `data-tos-key="${tosKey}"` : ''
}>`;
}
handleTextAreaChange(index, val);
}}
/>
</div>
) : (
<div>
<OptimizedTextArea
index={index}
disabled={!canEdit}
value={item.value}
handleTextAreaChange={handleTextAreaChange}
error={item.error}
/>
{item.error ? (
<div className={styles['error-tips']}>{item.error}</div>
) : null}
</div>
),
},
];
return (
<Modal
size="medium"
centered
maskClosable={false}
keepDOM={false}
onCancel={onCancel}
footer={
<RenderFooter
onCancel={onCancel}
loading={loading}
onSubmit={() => {
onSubmit();
}}
/>
}
{...modalProps}
>
<TableSegmentContent
tableData={tableData}
canEdit={canEdit}
columns={columns}
handleTextAreaChange={handleTextAreaChange}
/>
</Modal>
);
};

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useDataModalWithCoze } from '@coze-data/utils';
import { I18n } from '@coze-arch/i18n';
import { type ButtonColor } from '@coze-arch/coze-design/types';
import styles from './index.module.less';
export interface IResegmentModalProps {
onOk: () => void;
}
export const useTextResegmentModal = ({ onOk }: IResegmentModalProps) => {
const { modal, open, close } = useDataModalWithCoze({
width: 320,
title: I18n.t('datasets_segment_resegment'),
className: styles['text-resegment-modal'],
cancelText: I18n.t('Cancel'),
okText: I18n.t('knowledge_optimize_007'),
okButtonColor: 'yellow' as ButtonColor,
okButtonProps: {
type: 'warning',
},
onOk: () => {
onOk();
},
onCancel: () => close(),
});
return {
node: modal(
<div className={styles['text-resegment-content']}>
{I18n.t('kl2_004')}
</div>,
),
open,
close,
};
};

View File

@@ -0,0 +1,16 @@
.text-resegment-modal {
:global {
.semi-modal-content {
color: var(--coz-fg-secondary);
}
.semi-modal-footer .semi-button {
margin-left: 16px;
}
}
.text-resegment-content {
line-height: 20px;
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { useTextResegmentModal } from './hooks';

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare module '*.less' {
const resource: { [key: string]: string };
export = resource;
}
declare module '*.png';
declare module '*.svg';

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useRef, useState } from 'react';
import { useRequest } from 'ahooks';
import {
getUpdateIntervalOptions,
DocumentUpdateInterval,
useDataModalWithCoze,
} from '@coze-data/utils';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { type FormState } from '@coze-arch/bot-semi/Form';
import { CustomError } from '@coze-arch/bot-error';
import { DocumentUpdateType } from '@coze-arch/bot-api/memory';
import {
type FormatType,
type DocumentSource,
UpdateType,
} from '@coze-arch/bot-api/knowledge';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { Form, FormSelect } from '@coze-arch/coze-design';
import { DATA_REFACTOR_CLASS_NAME } from '../constant';
export interface DataType {
updateType?: number;
updateInterval?: number;
}
export interface UseUpdateFrequencyModalProps {
docId?: string;
onFinish?: (formData: DataType) => void;
type?: FormatType;
documentSource?: DocumentSource;
}
export const useUpdateFrequencyModal = (
props: UseUpdateFrequencyModalProps,
) => {
const formRef = useRef<Form>(null);
const { docId, documentSource } = props;
const [content, setContent] = useState<DataType>({
updateType: DocumentUpdateType.NoUpdate,
updateInterval: DocumentUpdateInterval.EveryDay,
});
const [, setDisabled] = useState<boolean>(false);
const { run, loading } = useRequest(
async () => {
if (!docId) {
throw new CustomError(
REPORT_EVENTS.KnowledgeUpdateDocumentFrequency,
`${REPORT_EVENTS.KnowledgeUpdateDocumentFrequency}: missing doc_id`,
);
}
const formData = formRef.current?.formApi.getValues();
await KnowledgeApi.UpdateDocument({
document_id: docId,
update_rule: {
update_type: formData.updateInterval ? UpdateType.Cover : 0,
update_interval: formData.updateInterval,
},
});
return {
updateType: formData.updateInterval ? UpdateType.Cover : 0,
updateInterval: formData.updateInterval,
};
},
{
manual: true,
onSuccess: () => {
close();
props?.onFinish?.(content);
},
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeUpdateDocumentFrequency,
error,
});
},
},
);
const { modal, open, close } = useDataModalWithCoze({
title: I18n.t('datasets_segment_Update'),
centered: true,
cancelText: I18n.t('Cancel'),
okText: I18n.t('Confirm'),
okButtonProps: {
loading,
},
onOk: () => {
run();
},
onCancel: () => close(),
});
const onChange = (object: FormState<DataType>) => {
if (object.values) {
setContent(object.values);
setDisabled(object.values?.updateInterval === 0);
}
};
return {
node: modal(
<Form<DataType>
className={DATA_REFACTOR_CLASS_NAME}
showValidateIcon={false}
labelPosition="top"
ref={formRef}
initValues={content}
onChange={onChange}
>
<FormSelect
field="updateInterval"
label={I18n.t('datasets_frequencyModal_frequency')}
placeholder={I18n.t('datasets_frequencyModal_frequency')}
style={{ width: '100%' }}
optionList={getUpdateIntervalOptions({ documentSource })}
></FormSelect>
{/* <FormSelect
field="updateType"
label={I18n.t('datasets_frequencyModal_whenUpdate')}
placeholder={I18n.t('datasets_update_type')}
style={{ width: '100%', marginBottom: 20 }}
optionList={getUpdateTypeOptions(type)}
disabled={disabled}
></FormSelect> */}
</Form>,
),
edit: (editContent: DataType) => {
setContent(editContent);
setDisabled(editContent.updateInterval === 0);
open();
},
close,
};
};

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {
useUpdateFrequencyModal,
type UseUpdateFrequencyModalProps,
} from './hooks';

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { I18n } from '@coze-arch/i18n';
import { CustomError } from '@coze-arch/bot-error';
import { Toast } from '@coze-arch/coze-design';
export const getEllipsisCount = (num: number, max: number): string =>
num > max ? `${max}+` : `${num}`;
export const formatBytes = (bytes: number, decimals = 2) => {
if (!bytes) {
return '0 Byte';
}
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
const digit = parseFloat((bytes / Math.pow(k, i)).toFixed(dm));
return `${digit} ${sizes[i]}`;
};
export const getBase64 = (file: Blob): Promise<string> =>
new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = event => {
const result = event.target?.result;
if (!result || typeof result !== 'string') {
reject(new CustomError('getBase64', 'file read invalid'));
return;
}
resolve(result.replace(/^.*?,/, ''));
};
fileReader.onerror = () => {
Toast.error(I18n.t('read_file_failed_please_retry'));
reject(new CustomError('getBase64', 'file read fail'));
};
fileReader.onabort = () => {
reject(new CustomError('getBase64', 'file read abort'));
};
fileReader.readAsDataURL(file);
});
export const getUint8Array = (file: Blob): Promise<Uint8Array> =>
new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = event => {
if (event.target?.result) {
const arrayBuffer = event.target.result as ArrayBuffer;
const uint8Array = new Uint8Array(arrayBuffer);
resolve(uint8Array);
} else {
reject(new CustomError('getUint8Array', 'file read invalid'));
}
};
fileReader.readAsArrayBuffer(file);
});
export const getFileExtension = (name: string) => {
const index = name.lastIndexOf('.');
return name.slice(index + 1);
};

Some files were not shown because too many files have changed in this diff Show More