feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
31
frontend/packages/common/prompt-kit/main/.storybook/main.js
Normal file
31
frontend/packages/common/prompt-kit/main/.storybook/main.js
Normal 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;
|
||||
@@ -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;
|
||||
5
frontend/packages/common/prompt-kit/main/.stylelintrc.js
Normal file
5
frontend/packages/common/prompt-kit/main/.stylelintrc.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
16
frontend/packages/common/prompt-kit/main/README.md
Normal file
16
frontend/packages/common/prompt-kit/main/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# @coze-common/prompt-kit
|
||||
|
||||
> 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`
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
101
frontend/packages/common/prompt-kit/main/package.json
Normal file
101
frontend/packages/common/prompt-kit/main/package.json
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"name": "@coze-common/prompt-kit",
|
||||
"version": "0.0.1",
|
||||
"description": "prompt编辑器,基于@coze-editor/editor",
|
||||
"license": "Apache-2.0",
|
||||
"author": "haozhenfei@bytedance.com",
|
||||
"maintainers": [],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/",
|
||||
"./editor": "./src/editor/index.ts",
|
||||
"./prompt-recommend": "./src/prompt-recommend/index.ts",
|
||||
"./create-prompt": "./src/create-prompt/index.ts",
|
||||
"./nl-prompt": "./src/nl-prompt/index.ts"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"editor": [
|
||||
"./src/editor/index.ts"
|
||||
],
|
||||
"prompt-recommend": [
|
||||
"./src/prompt-recommend/index.ts"
|
||||
],
|
||||
"create-prompt": [
|
||||
"./src/create-prompt/index.ts"
|
||||
],
|
||||
"nl-prompt": [
|
||||
"./src/nl-prompt/index.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/view": "^6.34.1",
|
||||
"@coze-agent-ide/bot-editor-context-store": "workspace:*",
|
||||
"@coze-arch/bot-api": "workspace:*",
|
||||
"@coze-arch/bot-error": "workspace:*",
|
||||
"@coze-arch/bot-http": "workspace:*",
|
||||
"@coze-arch/bot-icons": "workspace:*",
|
||||
"@coze-arch/bot-semi": "workspace:*",
|
||||
"@coze-arch/bot-tea": "workspace:*",
|
||||
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
|
||||
"@coze-arch/i18n": "workspace:*",
|
||||
"@coze-arch/idl": "workspace:*",
|
||||
"@coze-common/chat-answer-action": "workspace:*",
|
||||
"@coze-common/chat-area-utils": "workspace:*",
|
||||
"@coze-common/chat-core": "workspace:*",
|
||||
"@coze-common/chat-uikit": "workspace:*",
|
||||
"@coze-common/editor-plugins": "workspace:*",
|
||||
"@coze-common/prompt-kit-adapter": "workspace:*",
|
||||
"@coze-common/prompt-kit-base": "workspace:*",
|
||||
"@coze-common/scroll-view": "workspace:*",
|
||||
"@coze-editor/editor": "0.1.0-alpha.d92d50",
|
||||
"@coze-foundation/local-storage": "workspace:*",
|
||||
"@lezer/common": "^1.2.2",
|
||||
"ahooks": "^3.7.8",
|
||||
"classnames": "^2.3.2",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"immer": "^10.0.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"nanoid": "^4.0.2",
|
||||
"rc-textarea": "^1.6.3",
|
||||
"react-markdown": "^8.0.3",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codemirror/language": "^6.10.1",
|
||||
"@coze-arch/bot-env": "workspace:*",
|
||||
"@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/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": "^4.3.9",
|
||||
"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: 1.3 KiB |
@@ -0,0 +1,145 @@
|
||||
<svg width="356" height="178" viewBox="0 0 356 178" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.2">
|
||||
<mask id="mask0_7107_191040" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="356" height="178">
|
||||
<rect x="0.540771" width="354.918" height="177.459" fill="url(#paint0_linear_7107_191040)"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_7107_191040)">
|
||||
<mask id="mask1_7107_191040" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="356" height="178">
|
||||
<rect x="0.540771" width="354.918" height="177.459" fill="url(#paint1_linear_7107_191040)"/>
|
||||
</mask>
|
||||
<g mask="url(#mask1_7107_191040)">
|
||||
<rect x="0.540771" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="30.1172" width="29.5765" height="29.5765" fill="url(#paint2_linear_7107_191040)" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="59.6938" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="89.2705" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="118.847" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="148.424" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="178" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="207.576" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="237.153" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="266.729" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="296.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="325.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="0.540771" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="30.1172" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="59.6938" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="89.2705" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="118.847" y="29.5767" width="29.5765" height="29.5765" fill="url(#paint3_linear_7107_191040)" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="148.424" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="178" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="207.576" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="237.153" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="266.729" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="296.306" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="325.883" y="29.5767" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="0.540771" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="30.1172" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="59.6938" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="89.2705" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="118.847" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="148.424" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="178" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="207.576" y="59.1528" width="29.5765" height="29.5765" fill="url(#paint4_linear_7107_191040)" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="237.153" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="266.729" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="296.306" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="325.883" y="59.1528" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="0.540771" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="30.1172" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="59.6938" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="89.2705" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="118.847" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="148.424" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="178" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="207.576" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="237.153" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="266.729" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="296.306" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="325.883" y="88.7295" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="0.540771" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="30.1172" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="59.6938" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="89.2705" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="118.847" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="148.424" y="118.306" width="29.5765" height="29.5765" fill="url(#paint5_linear_7107_191040)" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="178" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="207.576" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="237.153" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="266.729" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="296.306" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="325.883" y="118.306" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="0.540771" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="30.1172" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="59.6938" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="89.2705" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="118.847" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="148.424" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="178" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="207.576" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="237.153" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="266.729" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="296.306" y="147.883" width="29.5765" height="29.5765" fill="url(#paint6_linear_7107_191040)" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
<rect x="325.883" y="147.883" width="29.5765" height="29.5765" stroke="#3C445C" stroke-width="0.616178"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<rect x="80.7778" y="82.3135" width="194.444" height="70.8333" rx="5.55556" fill="#F9F9F9"/>
|
||||
<rect x="81.1251" y="82.6607" width="193.75" height="70.1389" rx="5.20833" stroke="url(#paint7_linear_7107_191040)" stroke-opacity="0.24" stroke-width="0.694444"/>
|
||||
<rect opacity="0.12" x="91.8889" y="94.814" width="72.2222" height="9.72222" rx="4.86111" fill="#2B3245"/>
|
||||
<rect opacity="0.12" x="91.8889" y="110.092" width="136.111" height="9.72222" rx="4.86111" fill="#2B3245"/>
|
||||
<g filter="url(#filter0_d_7107_191040)">
|
||||
<path d="M236.333 97.8693C236.333 96.3135 236.333 95.5357 236.636 94.9415C236.902 94.4188 237.327 93.9939 237.85 93.7276C238.444 93.4248 239.222 93.4248 240.778 93.4248H259.667C261.222 93.4248 262 93.4248 262.594 93.7276C263.117 93.9939 263.542 94.4188 263.808 94.9415C264.111 95.5357 264.111 96.3135 264.111 97.8692V116.758C264.111 118.314 264.111 119.092 263.808 119.686C263.542 120.209 263.117 120.634 262.594 120.9C262 121.203 261.222 121.203 259.667 121.203H240.778C239.222 121.203 238.444 121.203 237.85 120.9C237.327 120.634 236.902 120.209 236.636 119.686C236.333 119.092 236.333 118.314 236.333 116.758V97.8693Z" fill="url(#paint8_linear_7107_191040)"/>
|
||||
<path d="M236.403 97.8693C236.403 97.0903 236.403 96.5102 236.44 96.0505C236.478 95.5916 236.552 95.2587 236.698 94.973C236.958 94.4634 237.372 94.0491 237.881 93.7894C238.167 93.6439 238.5 93.5694 238.959 93.5319C239.419 93.4943 239.999 93.4942 240.778 93.4942H259.667C260.446 93.4942 261.026 93.4943 261.485 93.5319C261.944 93.5694 262.277 93.6439 262.563 93.7894C263.072 94.0491 263.487 94.4634 263.746 94.973C263.892 95.2587 263.966 95.5916 264.004 96.0505C264.042 96.5102 264.042 97.0903 264.042 97.8692V116.758C264.042 117.537 264.042 118.117 264.004 118.577C263.966 119.036 263.892 119.369 263.746 119.654C263.487 120.164 263.072 120.578 262.563 120.838C262.277 120.984 261.944 121.058 261.485 121.096C261.026 121.133 260.446 121.133 259.667 121.133H240.778C239.999 121.133 239.419 121.133 238.959 121.096C238.5 121.058 238.167 120.984 237.881 120.838C237.372 120.578 236.958 120.164 236.698 119.654C236.552 119.369 236.478 119.036 236.44 118.577C236.403 118.117 236.403 117.537 236.403 116.758V97.8693Z" stroke="black" stroke-opacity="0.08" stroke-width="0.138889"/>
|
||||
</g>
|
||||
<rect opacity="0.12" x="91.8889" y="132.313" width="172.222" height="9.72222" rx="4.86111" fill="#2B3245"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_7107_191040" x="230.381" y="89.4566" width="39.6826" height="39.6826" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.98413"/>
|
||||
<feGaussianBlur stdDeviation="2.97619"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_7107_191040"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_7107_191040" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_7107_191040" x1="355.459" y1="88.7296" x2="0.540769" y2="88.7296" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D9D9D9" stop-opacity="0"/>
|
||||
<stop offset="0.500048" stop-color="#D9D9D9"/>
|
||||
<stop offset="1" stop-color="#D9D9D9" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_7107_191040" x1="178" y1="0" x2="178" y2="177.459" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D9D9D9" stop-opacity="0"/>
|
||||
<stop offset="0.493585" stop-color="#D9D9D9"/>
|
||||
<stop offset="1" stop-color="#D9D9D9" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_7107_191040" x1="44.9055" y1="0" x2="44.9055" y2="29.5765" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3C445C" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#3C445C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_7107_191040" x1="133.635" y1="29.5767" x2="133.635" y2="59.1532" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3C445C" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#3C445C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_7107_191040" x1="222.365" y1="59.1528" x2="222.365" y2="88.7294" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3C445C" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#3C445C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_7107_191040" x1="163.212" y1="118.306" x2="163.212" y2="147.883" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3C445C" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#3C445C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_7107_191040" x1="311.094" y1="147.883" x2="311.094" y2="177.459" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3C445C" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#3C445C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_7107_191040" x1="178" y1="82.3135" x2="178" y2="153.147" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#38415A"/>
|
||||
<stop offset="1" stop-color="#2B3245" stop-opacity="0.4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_7107_191040" x1="250.222" y1="93.4248" x2="250.222" y2="121.203" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F45D68"/>
|
||||
<stop offset="1" stop-color="#FFCA00"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
19
frontend/packages/common/prompt-kit/main/src/index.ts
Normal file
19
frontend/packages/common/prompt-kit/main/src/index.ts
Normal 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 '@coze-arch/bot-typings';
|
||||
import '@coze-arch/bot-env/typings';
|
||||
export { usePromptLibraryModal } from './prompt-library';
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 { IconCozEmpty } from '@coze-arch/coze-design/icons';
|
||||
import { EmptyState } from '@coze-arch/coze-design';
|
||||
|
||||
import EmptyPromptIcon from '../assets/empty-prompt-icon.svg';
|
||||
|
||||
export const UnselectedPrompt = (props: { className?: string }) => (
|
||||
<div className={props.className}>
|
||||
<EmptyState
|
||||
title={I18n.t('prompt_library_unselected')}
|
||||
icon={<img src={EmptyPromptIcon} alt="empty-prompt" />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const EmptyPrompt = (props: { className?: string }) => (
|
||||
<div className={props.className}>
|
||||
<EmptyState
|
||||
title={I18n.t('prompt_library_prompt_empty')}
|
||||
icon={<IconCozEmpty />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -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 copy from 'copy-to-clipboard';
|
||||
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button, Toast } from '@coze-arch/coze-design';
|
||||
|
||||
export const CopyPrompt = (props: {
|
||||
editor: EditorAPI;
|
||||
onCopyPrompt: () => void;
|
||||
}) => {
|
||||
const { editor, onCopyPrompt } = props;
|
||||
return (
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
const text = editor?.$view.state.doc.toString();
|
||||
const result = copy(text, { format: 'text/plain' });
|
||||
result &&
|
||||
Toast.success(I18n.t('prompt_library_prompt_copied_successfully'));
|
||||
onCopyPrompt?.();
|
||||
}}
|
||||
>
|
||||
{I18n.t('prompt_detail_copy_prompt')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
export { CopyPrompt } from './copy-prompt';
|
||||
export { InsertToEditor } from './insert-to-editor';
|
||||
export { PromptDiff } from './prompt-diff';
|
||||
@@ -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 { type EditorAPI } from '@coze-editor/editor/preset-prompt';
|
||||
import { insertToNewline } from '@coze-common/prompt-kit-base/shared';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
|
||||
export const InsertToEditor = (props: {
|
||||
outerEditor: EditorAPI;
|
||||
prompt: string;
|
||||
onInsertPrompt: (prompt: string) => void;
|
||||
onCancel: (e: React.MouseEvent) => void;
|
||||
}) => {
|
||||
const { outerEditor, prompt, onInsertPrompt, onCancel } = props;
|
||||
return (
|
||||
<Button
|
||||
disabled={!prompt}
|
||||
onClick={async e => {
|
||||
const insertPrompt = await insertToNewline({
|
||||
editor: outerEditor,
|
||||
prompt,
|
||||
});
|
||||
onInsertPrompt(insertPrompt);
|
||||
onCancel?.(e);
|
||||
}}
|
||||
>
|
||||
{I18n.t('prompt_resource_insert_prompt')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 { Button } from '@coze-arch/coze-design';
|
||||
export const PromptDiff = (props: { onDiff: () => void }) => {
|
||||
const { onDiff } = props;
|
||||
return (
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
onDiff?.();
|
||||
}}
|
||||
>
|
||||
{I18n.t('compare_prompt_compare_debug')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
.prompt-library-modal {
|
||||
:global {
|
||||
.semi-modal-body {
|
||||
> div:first-child {
|
||||
> div:first-child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.semi-modal-footer {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* 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, useRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useEditor } from '@coze-editor/editor/react';
|
||||
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozPlus } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Modal,
|
||||
type ModalProps,
|
||||
Search,
|
||||
Button,
|
||||
} from '@coze-arch/coze-design';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
import { LibraryBlockWidget } from '@coze-common/editor-plugins/library-insert';
|
||||
import { InputSlotWidget } from '@coze-common/editor-plugins/input-slot';
|
||||
import {
|
||||
PromptEditorRender,
|
||||
PromptEditorProvider,
|
||||
} from '@coze-common/prompt-kit-base/editor';
|
||||
import { usePromptConfiguratorModal } from '@coze-common/prompt-kit-adapter/create-prompt';
|
||||
|
||||
import { type LibraryInfo, getLibraryListByCategory } from './library-request';
|
||||
import { LibraryList, type InfiniteListRef } from './library-list';
|
||||
import { CopyPrompt, InsertToEditor, PromptDiff } from './footer-actions';
|
||||
import { EmptyPrompt } from './empty';
|
||||
|
||||
import '@coze-common/prompt-kit-base/shared/css';
|
||||
import styles from './index.module.less';
|
||||
|
||||
const getTabLabelMap = (isPersonal: boolean) => ({
|
||||
Recommended: I18n.t('prompt_resource_recommended'),
|
||||
Team: isPersonal
|
||||
? I18n.t('prompt_resource_personal')
|
||||
: I18n.t('prompt_resource_team'),
|
||||
});
|
||||
const LIMIT_LIBRARY_SIZE = 15;
|
||||
|
||||
interface PromptContextInfo {
|
||||
botId?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
contextHistory?: string;
|
||||
}
|
||||
|
||||
export interface ActionExtraInfo {
|
||||
category: 'Recommended' | 'Team';
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface PromptLibraryProps extends ModalProps {
|
||||
spaceId: string;
|
||||
isPersonal?: boolean;
|
||||
editor: EditorAPI;
|
||||
getConversationId?: () => string | undefined;
|
||||
getPromptContextInfo?: () => PromptContextInfo;
|
||||
enableDiff?: boolean;
|
||||
importPromptWhenEmpty?: string;
|
||||
defaultActiveTab?: 'Recommended' | 'Team';
|
||||
tabs?: ('Recommended' | 'Team')[];
|
||||
/** 用于埋点: 页面来源 */
|
||||
source: string;
|
||||
/** 用于埋点: bot_id */
|
||||
botId?: string;
|
||||
/** 用于埋点: project_id */
|
||||
projectId?: string;
|
||||
/** 用于埋点: workflow_id */
|
||||
workflowId?: string;
|
||||
onInsertPrompt?: (prompt: string, selectedLibrary: ActionExtraInfo) => void;
|
||||
onUpdateSuccess?: (
|
||||
mode: 'create' | 'edit' | 'info',
|
||||
selectedLibrary: ActionExtraInfo,
|
||||
) => void;
|
||||
onCopyPrompt?: (selectedLibrary: ActionExtraInfo) => void;
|
||||
onDeletePrompt?: (selectedLibrary: ActionExtraInfo) => void;
|
||||
onDiff?: ({
|
||||
prompt,
|
||||
libraryId,
|
||||
}: {
|
||||
prompt: string;
|
||||
libraryId: string;
|
||||
}) => void;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
export const PromptLibrary = ({
|
||||
spaceId,
|
||||
onCancel,
|
||||
defaultActiveTab = 'Recommended',
|
||||
getConversationId,
|
||||
getPromptContextInfo,
|
||||
editor: outerEditor,
|
||||
source,
|
||||
tabs = ['Recommended', 'Team'],
|
||||
botId,
|
||||
projectId,
|
||||
workflowId,
|
||||
isPersonal = false,
|
||||
importPromptWhenEmpty,
|
||||
enableDiff = false,
|
||||
onInsertPrompt,
|
||||
onUpdateSuccess,
|
||||
onCopyPrompt,
|
||||
onDeletePrompt,
|
||||
onDiff,
|
||||
}: PromptLibraryProps) => {
|
||||
const [activeTab, setActiveTab] = useState<'Recommended' | 'Team'>(
|
||||
defaultActiveTab,
|
||||
);
|
||||
const [prompt, setPrompt] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [dataList, setDataList] = useState<LibraryInfo[]>([]);
|
||||
const [selectedLibraryId, setSelectedLibraryId] = useState<string>('');
|
||||
const [searchWord, setSearchWord] = useState<string | undefined>(undefined);
|
||||
const targetRef = useRef<HTMLDivElement>(null);
|
||||
const listRef = useRef<InfiniteListRef<LibraryInfo>>(null);
|
||||
const editor = useEditor<EditorAPI>();
|
||||
const isEmptyList = !isLoading && dataList.length === 0;
|
||||
|
||||
const { open: openPromptConfiguratorModal, node: PromptConfiguratorModal } =
|
||||
usePromptConfiguratorModal({
|
||||
spaceId,
|
||||
isPersonal,
|
||||
enableDiff,
|
||||
getConversationId,
|
||||
getPromptContextInfo,
|
||||
importPromptWhenEmpty,
|
||||
source,
|
||||
botId,
|
||||
projectId,
|
||||
workflowId,
|
||||
onDiff,
|
||||
onUpdateSuccess: (mode, id) => {
|
||||
if (tabs.includes(activeTab)) {
|
||||
setActiveTab('Team');
|
||||
}
|
||||
onUpdateSuccess?.(mode, {
|
||||
id: id || '',
|
||||
category: 'Team',
|
||||
});
|
||||
listRef.current?.reload();
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
editor.$view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: editor.$view.state.doc.length,
|
||||
insert: prompt,
|
||||
},
|
||||
});
|
||||
}, [editor, prompt]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedLibraryId || isLoading || !dataList.length) {
|
||||
return;
|
||||
}
|
||||
const selectedLibrary = dataList.find(
|
||||
item => item.id === selectedLibraryId,
|
||||
);
|
||||
if (!selectedLibrary) {
|
||||
return;
|
||||
}
|
||||
const { promptText } = selectedLibrary;
|
||||
|
||||
if (promptText) {
|
||||
setPrompt(promptText);
|
||||
return;
|
||||
}
|
||||
PlaygroundApi.GetPromptResourceInfo({
|
||||
prompt_resource_id: selectedLibraryId,
|
||||
}).then(({ data: { prompt_text: newPrompt } = {} }) => {
|
||||
setPrompt(newPrompt ?? '');
|
||||
});
|
||||
}, [selectedLibraryId, dataList, isLoading]);
|
||||
|
||||
// 切换tab、无选中提示词,重置搜索词
|
||||
useEffect(() => {
|
||||
setSelectedLibraryId('');
|
||||
setPrompt('');
|
||||
}, [activeTab, isEmptyList]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title={I18n.t('prompt_library_prompt_library')}
|
||||
visible
|
||||
className={styles['prompt-library-modal']}
|
||||
width="880px"
|
||||
closeOnEsc={false}
|
||||
maskClosable={false}
|
||||
footer={
|
||||
<div className="flex justify-end">
|
||||
{enableDiff ? (
|
||||
<PromptDiff
|
||||
onDiff={() => {
|
||||
onDiff?.({ prompt, libraryId: selectedLibraryId });
|
||||
sendTeaEvent(EVENT_NAMES.prompt_library_front, {
|
||||
source,
|
||||
prompt_id: selectedLibraryId,
|
||||
space_id: spaceId,
|
||||
prompt_type: 'workspace',
|
||||
action: 'compare',
|
||||
bot_id: botId,
|
||||
project_id: projectId,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
sendTeaEvent(EVENT_NAMES.compare_mode_front, {
|
||||
source,
|
||||
action: 'start',
|
||||
compare_type: 'prompts',
|
||||
bot_id: botId,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<CopyPrompt
|
||||
editor={editor}
|
||||
onCopyPrompt={() => {
|
||||
onCopyPrompt?.({ id: selectedLibraryId, category: activeTab });
|
||||
sendTeaEvent(EVENT_NAMES.prompt_library_front, {
|
||||
source,
|
||||
prompt_id: selectedLibraryId,
|
||||
space_id: spaceId,
|
||||
prompt_type: 'workspace',
|
||||
action: 'copy',
|
||||
bot_id: botId,
|
||||
project_id: projectId,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<InsertToEditor
|
||||
outerEditor={outerEditor}
|
||||
prompt={prompt}
|
||||
onInsertPrompt={insertPrompt => {
|
||||
onInsertPrompt?.(insertPrompt, {
|
||||
id: selectedLibraryId,
|
||||
category: activeTab,
|
||||
});
|
||||
sendTeaEvent(EVENT_NAMES.prompt_library_front, {
|
||||
source,
|
||||
prompt_id: selectedLibraryId,
|
||||
space_id: spaceId,
|
||||
prompt_type: 'workspace',
|
||||
action: 'insert',
|
||||
bot_id: botId,
|
||||
project_id: projectId,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
}}
|
||||
onCancel={() => {
|
||||
onCancel?.();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<div className="flex flex-col gap-5 overflow-hidden h-[620px]">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex gap-3 pl-3">
|
||||
{tabs.map(category => (
|
||||
<div
|
||||
key={category}
|
||||
className={classNames(
|
||||
'coz-fg-secondary text-sm cursor-pointer font-medium',
|
||||
{
|
||||
'!coz-fg-hglt': activeTab === category,
|
||||
},
|
||||
)}
|
||||
onClick={() => {
|
||||
setActiveTab(category);
|
||||
}}
|
||||
>
|
||||
{getTabLabelMap(isPersonal)[category]}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Search
|
||||
className="w-[192px]"
|
||||
placeholder={I18n.t('Search')}
|
||||
onSearch={setSearchWord}
|
||||
/>
|
||||
{isEmptyList ? null : (
|
||||
<Button
|
||||
type="primary"
|
||||
className="!coz-mg-hglt !coz-fg-hglt hover:!coz-mg-hglt-hovered active:!coz-mg-hglt-pressed"
|
||||
icon={<IconCozPlus />}
|
||||
onClick={() => {
|
||||
openPromptConfiguratorModal({
|
||||
mode: 'create',
|
||||
});
|
||||
}}
|
||||
>
|
||||
{I18n.t('prompt_library_new_prompt')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 overflow-hidden flex-1">
|
||||
<div
|
||||
className="flex-1 basis-1/3 overflow-y-auto styled-scrollbar hover-show-scrollbar"
|
||||
ref={targetRef}
|
||||
>
|
||||
<LibraryList
|
||||
ref={listRef}
|
||||
targetRef={targetRef}
|
||||
spaceId={spaceId}
|
||||
category={activeTab}
|
||||
size={LIMIT_LIBRARY_SIZE}
|
||||
searchWord={searchWord}
|
||||
getData={getLibraryListByCategory}
|
||||
onChangeState={(newIsLoading, newDataList) => {
|
||||
setIsLoading(newIsLoading);
|
||||
setDataList(newDataList);
|
||||
}}
|
||||
onActive={id => {
|
||||
setSelectedLibraryId(id);
|
||||
}}
|
||||
onDeleteAction={id => {
|
||||
PlaygroundApi.DeletePromptResource({
|
||||
prompt_resource_id: id,
|
||||
}).then(() => {
|
||||
onDeletePrompt?.({ id, category: 'Team' });
|
||||
sendTeaEvent(EVENT_NAMES.prompt_library_front, {
|
||||
source,
|
||||
prompt_id: id,
|
||||
space_id: spaceId,
|
||||
prompt_type: 'workspace',
|
||||
action: 'delete',
|
||||
bot_id: botId,
|
||||
project_id: projectId,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
listRef.current?.reload();
|
||||
});
|
||||
}}
|
||||
onEditAction={id => {
|
||||
openPromptConfiguratorModal({
|
||||
mode: 'edit',
|
||||
editId: id,
|
||||
});
|
||||
}}
|
||||
onEmptyClick={() => {
|
||||
openPromptConfiguratorModal({
|
||||
mode: 'create',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 basis-2/3 coz-bg-max rounded-normal border-solid coz-stroke-primary border-[0.5px] p-2 empty:hidden overflow-y-auto styled-scrollbar hover-show-scrollbar">
|
||||
{isEmptyList ? null : (
|
||||
<>
|
||||
{prompt ? (
|
||||
<div className="relative ">
|
||||
<PromptEditorRender defaultValue={prompt} readonly />
|
||||
<InputSlotWidget mode="input" />
|
||||
<LibraryBlockWidget
|
||||
librarys={[]}
|
||||
readonly
|
||||
spaceId={spaceId}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyPrompt className="flex justify-center items-center h-full w-full" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
{PromptConfiguratorModal}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const usePromptLibraryModal = (
|
||||
props: Omit<PromptLibraryProps, 'onClose'>,
|
||||
) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [dynamicProps, setDynamicProps] = useState<Partial<PromptLibraryProps>>(
|
||||
{},
|
||||
);
|
||||
const close = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
const open = (options?: Partial<PromptLibraryProps>) => {
|
||||
setVisible(true);
|
||||
setDynamicProps(options ?? {});
|
||||
};
|
||||
return {
|
||||
node: visible ? (
|
||||
<PromptEditorProvider>
|
||||
<PromptLibrary {...props} {...dynamicProps} onCancel={close} />
|
||||
</PromptEditorProvider>
|
||||
) : null,
|
||||
close,
|
||||
open,
|
||||
};
|
||||
};
|
||||
@@ -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 React from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozWarningCircle } from '@coze-arch/coze-design/icons';
|
||||
import { EmptyState, Spin } from '@coze-arch/coze-design';
|
||||
|
||||
import { type EmptyProps } from './type';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
/* Plugin header */
|
||||
|
||||
function Index(props: EmptyProps) {
|
||||
const {
|
||||
isLoading,
|
||||
loadRetry,
|
||||
isError,
|
||||
renderEmpty,
|
||||
text,
|
||||
btn,
|
||||
icon,
|
||||
className,
|
||||
size,
|
||||
} = props;
|
||||
return (
|
||||
<div className={s['height-whole-100']}>
|
||||
{renderEmpty?.(props) ||
|
||||
(!isError ? (
|
||||
isLoading ? (
|
||||
<Spin
|
||||
tip={
|
||||
<span className={s['loading-text']}>{I18n.t('Loading')}</span>
|
||||
}
|
||||
wrapperClassName={s.spin}
|
||||
size="middle"
|
||||
/>
|
||||
) : (
|
||||
<div className={className}>
|
||||
<EmptyState
|
||||
title={text?.emptyTitle || I18n.t('inifinit_list_empty_title')}
|
||||
size={size}
|
||||
description={text?.emptyDesc || ''}
|
||||
buttonText={btn?.emptyText}
|
||||
buttonProps={btn?.emptyButtonProps}
|
||||
onButtonClick={btn?.emptyClick}
|
||||
icon={icon}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className={className}>
|
||||
<EmptyState
|
||||
className={s['load-fail']}
|
||||
title={I18n.t('inifinit_list_load_fail')}
|
||||
icon={<IconCozWarningCircle />}
|
||||
buttonText={loadRetry && I18n.t('inifinit_list_retry')}
|
||||
onButtonClick={() => {
|
||||
loadRetry?.();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Index;
|
||||
@@ -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 React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Spin, Button } from '@coze-arch/coze-design';
|
||||
|
||||
import { type FooterProps } from './type';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
/* Plugin header */
|
||||
|
||||
function Index(props: FooterProps) {
|
||||
const {
|
||||
isLoading,
|
||||
loadRetry,
|
||||
isError,
|
||||
renderFooter,
|
||||
isNeedBtnLoadMore,
|
||||
noMore,
|
||||
} = props;
|
||||
return (
|
||||
<div className={classNames(s['footer-container'], 'empty:hidden')}>
|
||||
{renderFooter?.(props) ||
|
||||
(isLoading ? (
|
||||
<>
|
||||
<Spin />
|
||||
<span className={s.loading}>{I18n.t('Loading')}</span>
|
||||
</>
|
||||
) : isError ? (
|
||||
<>
|
||||
<Spin />
|
||||
<span className={s['error-retry']} onClick={loadRetry}>
|
||||
{I18n.t('inifinit_list_retry')}
|
||||
</span>
|
||||
</>
|
||||
) : isNeedBtnLoadMore && !noMore ? (
|
||||
<Button
|
||||
onClick={loadRetry}
|
||||
className={s['load-more-btn']}
|
||||
theme="borderless"
|
||||
>
|
||||
{I18n.t('mkpl_load_btn')}
|
||||
</Button>
|
||||
) : null)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,111 @@
|
||||
.footer-container {
|
||||
padding: 12px 0 28px;
|
||||
text-align: center;
|
||||
|
||||
* {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.error-retry {
|
||||
margin-left: 10px;
|
||||
line-height: 20px;
|
||||
color: var(--semi-color-text-3, rgba(29, 28, 35, 35%));
|
||||
}
|
||||
|
||||
.error-retry {
|
||||
cursor: pointer;
|
||||
color: var(--semi-color-focus-border, #4D53E8);
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-spin-middle>.semi-spin-wrapper {
|
||||
height: 16px;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more-btn {
|
||||
font-weight: 600;
|
||||
background: #FFF;
|
||||
border-radius: 40px;
|
||||
|
||||
span {
|
||||
color: #1D1C23;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #FFF;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.responseive-foot-container {
|
||||
padding: 0 0 16px;
|
||||
|
||||
.load-more-btn {
|
||||
height: 40px;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.height-whole-100 {
|
||||
overflow: visible;
|
||||
height: 100%;
|
||||
|
||||
|
||||
|
||||
.spin {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:global {
|
||||
.semi-spin-wrapper {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.semi-tabs-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.semi-spin-children {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-left: 8px;
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
color: var(--semi-color-text-3, rgba(29, 28, 35, 35%));
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-list-item {
|
||||
padding: 0
|
||||
}
|
||||
|
||||
.semi-list-footer {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 {
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
type RefObject,
|
||||
useEffect,
|
||||
type ForwardedRef,
|
||||
} from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { List } from '@coze-arch/coze-design';
|
||||
|
||||
import useScroll from './use-scroll';
|
||||
import { type InfiniteListProps, type InfiniteListRef } from './type';
|
||||
import Footer from './footer';
|
||||
import Empty from './empty';
|
||||
|
||||
import s from './index.module.less';
|
||||
export type { InfiniteListRef };
|
||||
|
||||
// modify from packages/community/components/src/infinite-list/index.tsx
|
||||
function Index<T extends object>(
|
||||
props: InfiniteListProps<T>,
|
||||
ref: ForwardedRef<InfiniteListRef<T>>,
|
||||
) {
|
||||
const {
|
||||
className,
|
||||
grid,
|
||||
renderItem,
|
||||
itemClassName,
|
||||
renderFooter,
|
||||
scrollConf,
|
||||
onChangeState,
|
||||
isNeedBtnLoadMore = false,
|
||||
retryFunc,
|
||||
containerClassName,
|
||||
emptyConf,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
dataList,
|
||||
isLoading,
|
||||
loadMore,
|
||||
noMore,
|
||||
isLoadingError,
|
||||
reload,
|
||||
getDataList,
|
||||
} = useScroll<T>({ ...scrollConf, isNeedBtnLoadMore });
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
reload,
|
||||
getDataList,
|
||||
}),
|
||||
[reload, getDataList],
|
||||
);
|
||||
useEffect(() => {
|
||||
onChangeState?.(!!isLoading, dataList ?? []);
|
||||
}, [dataList, isLoading]);
|
||||
|
||||
return (
|
||||
<div className={cls(s['height-whole-100'], containerClassName)}>
|
||||
{!dataList?.length ? (
|
||||
/** 数据为空的时候,操作如何显示空页面 */
|
||||
<Empty
|
||||
isError={isLoadingError}
|
||||
isLoading={isLoading}
|
||||
loadRetry={retryFunc || loadMore}
|
||||
{...emptyConf}
|
||||
/>
|
||||
) : (
|
||||
<List
|
||||
{...{ className, grid }}
|
||||
emptyContent={<></>}
|
||||
dataSource={dataList}
|
||||
split={false}
|
||||
renderItem={(item, number) => (
|
||||
<List.Item
|
||||
className={
|
||||
typeof itemClassName === 'string'
|
||||
? itemClassName
|
||||
: itemClassName?.(item) // 支持动态行className
|
||||
}
|
||||
>
|
||||
{renderItem?.(item, number)}
|
||||
</List.Item>
|
||||
)}
|
||||
footer={
|
||||
<Footer
|
||||
isError={isLoadingError}
|
||||
noMore={noMore}
|
||||
isLoading={isLoading}
|
||||
loadRetry={retryFunc || loadMore}
|
||||
renderFooter={renderFooter}
|
||||
isNeedBtnLoadMore={isNeedBtnLoadMore}
|
||||
dataNum={dataList?.length}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const InfiniteList = forwardRef(Index) as <T>(
|
||||
props: InfiniteListProps<T> & { ref?: RefObject<InfiniteListRef<T>> },
|
||||
) => JSX.Element;
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 ReactElement, type RefObject } from 'react';
|
||||
|
||||
import {
|
||||
type ListProps,
|
||||
type EmptyStateProps,
|
||||
type ButtonProps,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
export interface EmptyProps {
|
||||
className?: string;
|
||||
isError?: boolean;
|
||||
isLoading?: boolean;
|
||||
loadRetry?: () => void; //重试加载
|
||||
size?: EmptyStateProps['size'];
|
||||
text?: {
|
||||
emptyTitle?: string;
|
||||
emptyDesc?: string;
|
||||
searchEmptyTitle?: string;
|
||||
};
|
||||
btn?: {
|
||||
emptyClick?: () => void; //
|
||||
emptyText?: string;
|
||||
emptyButtonProps?: ButtonProps;
|
||||
};
|
||||
icon?: ReactElement;
|
||||
|
||||
renderEmpty?: (
|
||||
emptyProps: Omit<EmptyProps, 'renderEmpty'>,
|
||||
) => React.ReactNode | null;
|
||||
}
|
||||
export interface FooterProps {
|
||||
isError?: boolean; // 是否加载出错
|
||||
isLoading?: boolean; // 是否加载中
|
||||
noMore?: boolean; //没有更多数据
|
||||
isNeedBtnLoadMore?: boolean;
|
||||
dataNum?: number;
|
||||
loadRetry?: () => void; //重试加载
|
||||
renderFooter?: (
|
||||
footerProps: Omit<FooterProps, 'renderFooter'>,
|
||||
) => React.ReactNode | null;
|
||||
}
|
||||
|
||||
export interface InfiniteListDataProps<T> {
|
||||
list: T[];
|
||||
hasMore?: boolean;
|
||||
// nextPage: number;
|
||||
cursor: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ScrollProps<T> {
|
||||
threshold?: number; //距离下方多长距离,开始加载数据
|
||||
targetRef?: RefObject<HTMLDivElement>; // 监听滚动的Dom 引用
|
||||
loadData: (
|
||||
current: InfiniteListDataProps<T>,
|
||||
) => Promise<InfiniteListDataProps<T>>; // 加载更多数据
|
||||
reloadDeps?: unknown[]; // 重新加载数据依赖
|
||||
isNeedBtnLoadMore?: boolean;
|
||||
isLoading?: boolean; // 是否加载中
|
||||
resetDataIfReload?: boolean; // 当reload时,是否先reset列表已存在数据,默认为true
|
||||
}
|
||||
|
||||
export interface InfiniteListProps<T>
|
||||
extends Partial<
|
||||
Pick<ListProps<T>, 'className' | 'emptyContent' | 'grid' | 'renderItem'>
|
||||
> {
|
||||
containerClassName?: string;
|
||||
canShowData?: boolean; //是否能够显示数据了
|
||||
isSearching?: boolean; // 是否搜索中,主要是用于错误显示的时候,选择文案使用
|
||||
itemClassName?: string | ((item: T) => string);
|
||||
isNeedBtnLoadMore?: boolean;
|
||||
isResponsive?: boolean;
|
||||
emptyConf?: {
|
||||
className?: string;
|
||||
renderEmpty?: EmptyProps['renderEmpty'];
|
||||
text?: EmptyProps['text'];
|
||||
btn?: EmptyProps['btn'];
|
||||
icon?: EmptyProps['icon'];
|
||||
size?: EmptyProps['size'];
|
||||
};
|
||||
renderFooter?: FooterProps['renderFooter'];
|
||||
scrollConf: ScrollProps<T>;
|
||||
rowKey?: string;
|
||||
retryFunc?: () => void;
|
||||
onChangeState?: (loading: boolean, data: T[]) => void;
|
||||
}
|
||||
|
||||
export interface InfiniteListRef<T> {
|
||||
reload: () => void;
|
||||
getDataList: () => T[]; // 获取当前列表数据
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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,
|
||||
useRef,
|
||||
useEffect,
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
useInfiniteScroll,
|
||||
useUpdateEffect,
|
||||
useMemoizedFn,
|
||||
useDebounceFn,
|
||||
} from 'ahooks';
|
||||
|
||||
import { type ScrollProps, type InfiniteListDataProps } from './type';
|
||||
|
||||
/* 滚动Hooks */
|
||||
|
||||
function useForwardFunc<T>(
|
||||
dataInfo: InfiniteListDataProps<T>,
|
||||
mutate: Dispatch<SetStateAction<InfiniteListDataProps<T>>>,
|
||||
) {
|
||||
// 手动插入数据,不通过接口
|
||||
const insertData = (item: T, index: number) => {
|
||||
dataInfo.list.splice(index, 0, item);
|
||||
mutate({
|
||||
...dataInfo,
|
||||
list: [...dataInfo.list],
|
||||
});
|
||||
};
|
||||
|
||||
// 手动删除数据,不通过接口
|
||||
const removeData = (index: number) => {
|
||||
dataInfo.list.splice(index, 1);
|
||||
mutate({
|
||||
...dataInfo,
|
||||
list: [...dataInfo.list],
|
||||
});
|
||||
};
|
||||
|
||||
const getDataList = () => dataInfo?.list;
|
||||
|
||||
return { insertData, removeData, getDataList };
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function, @coze-arch/max-line-per-function -- 看了下代码行数不太好优化
|
||||
function useScroll<T>(props: ScrollProps<T>) {
|
||||
const {
|
||||
targetRef,
|
||||
loadData,
|
||||
threshold,
|
||||
reloadDeps,
|
||||
isNeedBtnLoadMore,
|
||||
resetDataIfReload = true,
|
||||
} = props;
|
||||
const [isLoadingError, setIsLoadingError] = useState<boolean>(false);
|
||||
const refFetchNo = useRef<number>(0);
|
||||
const refResolve = useRef<(value: InfiniteListDataProps<T>) => void>();
|
||||
const {
|
||||
loading,
|
||||
data: dataInfo,
|
||||
loadingMore,
|
||||
loadMore,
|
||||
noMore,
|
||||
cancel,
|
||||
mutate,
|
||||
reload,
|
||||
} = useInfiniteScroll<InfiniteListDataProps<T>>(
|
||||
async current => {
|
||||
// 此处逻辑如此复杂,是解决Scroll中的bug。
|
||||
// useInfiniteScroll中的cancel只是取消了一次请求,但是数据会根据current重新设置一遍。
|
||||
const defaultData = {
|
||||
cursor: '0',
|
||||
list: [],
|
||||
};
|
||||
const fetchNo = refFetchNo.current;
|
||||
if (refResolve.current) {
|
||||
// 保证顺序执行,如果有当前方法,就取消上一次的请求,防止出现由于网络原因导致数据覆盖问题
|
||||
// 同时发出A1,A2,三次请求,但是A1先到达,然后请求了B1, 但是A1过慢,导致了A1覆盖了B1的请求。
|
||||
refResolve.current({
|
||||
...defaultData,
|
||||
...(current || {}),
|
||||
});
|
||||
}
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
refResolve.current = resolve;
|
||||
loadData(current || defaultData)
|
||||
.then(value => resolve(value))
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
refResolve.current = null;
|
||||
|
||||
// 切换Tab的时候,如果此时正在请求,防止数据的残留界面显示
|
||||
if (refFetchNo.current !== fetchNo) {
|
||||
if (current) {
|
||||
current.list = [];
|
||||
}
|
||||
return {
|
||||
list: [],
|
||||
cursor: '0',
|
||||
};
|
||||
}
|
||||
return result as InfiniteListDataProps<T>;
|
||||
},
|
||||
{
|
||||
target: isLoadingError || isNeedBtnLoadMore ? null : targetRef, //失败的时候,通过去掉target的事件绑定,禁止滚动加载。
|
||||
threshold,
|
||||
onBefore: () => {
|
||||
//setIsLoadingError(false);
|
||||
},
|
||||
isNoMore: data => data?.hasMore !== undefined && !data?.hasMore,
|
||||
onSuccess: () => {
|
||||
if (isLoadingError) {
|
||||
setIsLoadingError(false);
|
||||
}
|
||||
},
|
||||
onError: e => {
|
||||
// 如果在请求第一页数据时发生错误,并且当前列表不为空,则reset数据
|
||||
// 这个case只有当resetDataIfReload设置为false时才会发生
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
if (dataInfo.cursor === '0' && (dataInfo?.list?.length ?? 0) > 0) {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
mutate({
|
||||
...dataInfo,
|
||||
list: [],
|
||||
});
|
||||
}
|
||||
setIsLoadingError(true);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { insertData, removeData, getDataList } = useForwardFunc(
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
dataInfo,
|
||||
mutate,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNeedBtnLoadMore && !(loading || loadingMore)) {
|
||||
reload();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const reloadData = useMemoizedFn(() => {
|
||||
mutate({
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
list: resetDataIfReload ? [] : dataInfo.list,
|
||||
hasMore: undefined,
|
||||
cursor: '0',
|
||||
});
|
||||
cancel();
|
||||
setIsLoadingError(false);
|
||||
reload();
|
||||
});
|
||||
|
||||
useUpdateEffect(() => {
|
||||
refFetchNo.current++;
|
||||
reloadData();
|
||||
}, [...(reloadDeps || [])]);
|
||||
const isLoading = loading || loadingMore || props.isLoading;
|
||||
const { run: loadMoreDebounce } = useDebounceFn(
|
||||
() => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
if (!isNeedBtnLoadMore) {
|
||||
loadMore();
|
||||
}
|
||||
},
|
||||
{ wait: 500 },
|
||||
);
|
||||
useEffect(() => {
|
||||
const resize = () => {
|
||||
loadMoreDebounce();
|
||||
};
|
||||
window.addEventListener('resize', resize);
|
||||
return () => {
|
||||
window.removeEventListener('resize', resize);
|
||||
};
|
||||
}, []);
|
||||
const { list } = dataInfo || {};
|
||||
return {
|
||||
dataList: list,
|
||||
isLoading,
|
||||
loadMore: () => {
|
||||
if (!isLoading) {
|
||||
//如果已经有数据加载中了,需要禁止重复加载。
|
||||
loadMore();
|
||||
}
|
||||
},
|
||||
reload: reloadData,
|
||||
noMore,
|
||||
cancel,
|
||||
isLoadingError,
|
||||
mutate,
|
||||
insertData,
|
||||
removeData,
|
||||
getDataList,
|
||||
};
|
||||
}
|
||||
|
||||
export default useScroll;
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 cls from 'classnames';
|
||||
import { ActionKey } from '@coze-arch/idl/resource';
|
||||
import { type ResourceAction } from '@coze-arch/idl/plugin_develop';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozMore } from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip, Typography } from '@coze-arch/coze-design';
|
||||
const { Text } = Typography;
|
||||
|
||||
export interface LibraryItemProps {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
isSelected?: boolean;
|
||||
onActive?: (id: string) => void;
|
||||
actions?: ResourceAction[];
|
||||
onDeleteAction?: (id: string) => void;
|
||||
onEditAction?: (id: string) => void;
|
||||
}
|
||||
const actionsMap: {
|
||||
[key in ActionKey.Delete | ActionKey.Edit]: string;
|
||||
} = {
|
||||
[ActionKey.Delete]: I18n.t('Delete'),
|
||||
[ActionKey.Edit]: I18n.t('Edit'),
|
||||
};
|
||||
|
||||
export const LibraryItem = ({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
actions,
|
||||
isSelected,
|
||||
onActive,
|
||||
onDeleteAction,
|
||||
onEditAction,
|
||||
}: LibraryItemProps) => {
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
const handleActions = (action: ActionKey) => {
|
||||
if (action === ActionKey.Delete) {
|
||||
onDeleteAction?.(id);
|
||||
return;
|
||||
}
|
||||
if (action === ActionKey.Edit) {
|
||||
onEditAction?.(id);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cls(
|
||||
'w-full flex flex-row justify-between items-center overflow-hidden px-3 h-[64px]',
|
||||
'relative',
|
||||
'after:content-[""] after:absolute after:left-0 after:right-0',
|
||||
'after:bottom-0 after:h-[1px] after:coz-mg-primary',
|
||||
'hover:coz-mg-secondary-hovered hover:rounded hover:after:hidden',
|
||||
'cursor-pointer',
|
||||
{
|
||||
'rounded coz-mg-primary after:hidden': isSelected,
|
||||
},
|
||||
)}
|
||||
onClick={() => {
|
||||
onActive?.(id);
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setIsHover(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setIsHover(false);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-1 min-w-[0px] w-0 flex-col gap-[2px]">
|
||||
<Text className="text-lg flex-1 font-medium" ellipsis>
|
||||
{title}
|
||||
</Text>
|
||||
<Text
|
||||
className="text-base"
|
||||
ellipsis={{
|
||||
rows: 1,
|
||||
showTooltip: {
|
||||
opts: {
|
||||
position: 'right',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</div>
|
||||
<Tooltip
|
||||
position="bottom"
|
||||
className="!p-1"
|
||||
content={
|
||||
<div className="flex flex-col gap-[2px] w-[120px]">
|
||||
{actions
|
||||
?.filter(action => action.enable)
|
||||
?.filter(action => action.key in actionsMap)
|
||||
.map(action => (
|
||||
<div
|
||||
key={action.key}
|
||||
onClick={() => handleActions(action.key as ActionKey)}
|
||||
className="w-full text-sm h-[32px] p-2 flex items-center cursor-pointer hover:coz-mg-primary-hovered hover:rounded-mini"
|
||||
>
|
||||
{actionsMap[action.key as keyof typeof actionsMap]}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={cls(
|
||||
'w-6 h-6 rounded-little coz-mg-secondary-hovered flex items-center justify-center',
|
||||
{
|
||||
hidden: !actions?.length || !isHover,
|
||||
},
|
||||
)}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<IconCozMore />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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,
|
||||
type RefObject,
|
||||
useState,
|
||||
useRef,
|
||||
type ForwardedRef,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozPlus } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import EmptyLibraryIcon from '../assets/empty-library-icon.svg';
|
||||
import {
|
||||
type LibraryInfo,
|
||||
type LibraryListRequest,
|
||||
type LibraryListResponse,
|
||||
} from './library-request';
|
||||
import { LibraryItem } from './library-item';
|
||||
import { InfiniteList, type InfiniteListRef } from './infinite-list';
|
||||
export type { InfiniteListRef };
|
||||
interface LibraryListProps {
|
||||
searchWord?: string | undefined;
|
||||
category: 'Recommended' | 'Team';
|
||||
spaceId: string;
|
||||
size: number;
|
||||
targetRef: RefObject<HTMLDivElement>;
|
||||
onActive: (id: string) => void;
|
||||
onEditAction: (id: string) => void;
|
||||
onDeleteAction: (id: string) => void;
|
||||
getData: (req: LibraryListRequest) => Promise<LibraryListResponse>;
|
||||
onChangeState?: (isLoading: boolean, dataList: LibraryInfo[]) => void;
|
||||
onEmptyClick?: () => void;
|
||||
}
|
||||
|
||||
export const Index = (
|
||||
props: LibraryListProps,
|
||||
ref: ForwardedRef<InfiniteListRef<LibraryInfo>>,
|
||||
) => {
|
||||
const {
|
||||
getData,
|
||||
onActive,
|
||||
searchWord,
|
||||
category,
|
||||
targetRef,
|
||||
size,
|
||||
onChangeState,
|
||||
onDeleteAction,
|
||||
onEditAction,
|
||||
spaceId,
|
||||
onEmptyClick,
|
||||
} = props;
|
||||
const listRef = useRef<InfiniteListRef<LibraryInfo>>(null);
|
||||
const [dataList, setDataList] = useState<LibraryInfo[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedLibraryId, setSelectedLibraryId] = useState<string>('');
|
||||
|
||||
// 切换tab,默认选中第一个
|
||||
useEffect(() => {
|
||||
if (!dataList.length || isLoading) {
|
||||
return;
|
||||
}
|
||||
const firstLibraryId = dataList[0].id;
|
||||
setSelectedLibraryId(firstLibraryId);
|
||||
onActive?.(firstLibraryId);
|
||||
}, [dataList, isLoading]);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
reload: listRef.current?.reload ?? (() => void 0),
|
||||
getDataList: listRef.current?.getDataList ?? (() => []),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
return (
|
||||
<InfiniteList<LibraryInfo>
|
||||
ref={listRef}
|
||||
isNeedBtnLoadMore={false}
|
||||
onChangeState={(newIsLoading, newDataList) => {
|
||||
onChangeState?.(newIsLoading, newDataList);
|
||||
setDataList(newDataList);
|
||||
setIsLoading(newIsLoading);
|
||||
}}
|
||||
renderItem={(card: LibraryInfo) => (
|
||||
<LibraryItem
|
||||
key={card.id}
|
||||
id={card.id}
|
||||
title={card.name}
|
||||
description={card.description}
|
||||
actions={card.actions}
|
||||
isSelected={selectedLibraryId === card.id}
|
||||
onActive={id => {
|
||||
onActive?.(id);
|
||||
setSelectedLibraryId(id);
|
||||
}}
|
||||
onEditAction={id => {
|
||||
onEditAction?.(id);
|
||||
}}
|
||||
onDeleteAction={id => {
|
||||
onDeleteAction?.(id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
scrollConf={{
|
||||
reloadDeps: [searchWord, category],
|
||||
targetRef,
|
||||
loadData: current =>
|
||||
getData({
|
||||
cursor: current?.cursor ?? '0',
|
||||
searchWord: searchWord ?? '',
|
||||
category,
|
||||
spaceId,
|
||||
size,
|
||||
}),
|
||||
}}
|
||||
emptyConf={{
|
||||
className: 'flex flex-col items-center justify-center h-full',
|
||||
icon: <img src={EmptyLibraryIcon} alt="empty-library" />,
|
||||
size: 'full_screen',
|
||||
text: {
|
||||
emptyTitle: I18n.t('prompt_library_empty_title'),
|
||||
emptyDesc: I18n.t('prompt_library_empty_describe'),
|
||||
},
|
||||
btn: {
|
||||
emptyText: I18n.t('prompt_library_new_prompt'),
|
||||
emptyButtonProps: {
|
||||
type: 'primary',
|
||||
className:
|
||||
'!coz-mg-hglt !coz-fg-hglt hover:!coz-mg-hglt-hovered active:!coz-mg-hglt-pressed',
|
||||
icon: <IconCozPlus />,
|
||||
onClick: () => {
|
||||
onEmptyClick?.();
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const LibraryList = forwardRef(Index) as <T>(
|
||||
props: LibraryListProps & { ref?: RefObject<InfiniteListRef<T>> },
|
||||
) => JSX.Element;
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 ResourceAction, ResType } from '@coze-arch/idl/plugin_develop';
|
||||
import { PlaygroundApi, PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
export interface LibraryInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
actions?: ResourceAction[];
|
||||
promptText?: string;
|
||||
}
|
||||
export interface LibraryListRequest {
|
||||
searchWord: string;
|
||||
cursor: string;
|
||||
category: 'Recommended' | 'Team';
|
||||
spaceId: string;
|
||||
size: number;
|
||||
}
|
||||
export interface LibraryListResponse {
|
||||
list: LibraryInfo[];
|
||||
hasMore: boolean;
|
||||
cursor: string;
|
||||
code: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export const getTeamLibraryRequest = async (req: LibraryListRequest) => {
|
||||
const res = await PluginDevelopApi.LibraryResourceList({
|
||||
space_id: req.spaceId,
|
||||
size: req.size,
|
||||
cursor: req.cursor,
|
||||
name: req.searchWord,
|
||||
search_keys: ['full_text'],
|
||||
res_type_filter: [ResType.Prompt],
|
||||
});
|
||||
return {
|
||||
list:
|
||||
res.resource_list?.map(item => ({
|
||||
id: item.res_id ?? '',
|
||||
name: item.name ?? '',
|
||||
description: item.desc ?? '',
|
||||
actions: item?.actions ?? [],
|
||||
})) ?? [],
|
||||
hasMore: res.has_more ?? false,
|
||||
cursor: res.cursor ?? '',
|
||||
code: Number(res.code) ?? 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const getRecommendLibraryRequest = async (req: LibraryListRequest) => {
|
||||
const res = await PlaygroundApi.GetOfficialPromptResourceList({
|
||||
keyword: req.searchWord,
|
||||
});
|
||||
return {
|
||||
list:
|
||||
res.data?.map(item => ({
|
||||
id: item.id ?? '',
|
||||
name: item.name ?? '',
|
||||
description: item.description ?? '',
|
||||
promptText: item.prompt_text ?? '',
|
||||
})) ?? [],
|
||||
hasMore: false,
|
||||
cursor: '0',
|
||||
code: Number(res.code) ?? 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const getLibraryListByCategory = (
|
||||
req: LibraryListRequest,
|
||||
): Promise<LibraryListResponse> => {
|
||||
if (req.category === 'Team') {
|
||||
return getTeamLibraryRequest(req);
|
||||
}
|
||||
return getRecommendLibraryRequest(req);
|
||||
};
|
||||
@@ -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 { useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface ScrollControlProps {
|
||||
activeTab: string;
|
||||
tabs: string[];
|
||||
loading?: boolean;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export const useScrollControl = ({
|
||||
activeTab,
|
||||
tabs,
|
||||
loading,
|
||||
data,
|
||||
}: ScrollControlProps) => {
|
||||
const scrollRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
||||
const [canScrollRight, setCanScrollRight] = useState(false);
|
||||
|
||||
const getTabIndex = (tab: string) => tabs.indexOf(tab);
|
||||
|
||||
const checkScrollable = (index: number) => {
|
||||
const scrollRef = scrollRefs.current[index];
|
||||
if (scrollRef) {
|
||||
const { scrollLeft, scrollWidth, clientWidth } = scrollRef;
|
||||
setCanScrollLeft(scrollLeft > 0);
|
||||
setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 10);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScroll = (direction: 'left' | 'right') => {
|
||||
const index = getTabIndex(activeTab);
|
||||
if (scrollRefs.current[index]) {
|
||||
const scrollAmount = 300;
|
||||
const newScrollLeft =
|
||||
scrollRefs.current[index].scrollLeft +
|
||||
(direction === 'left' ? -scrollAmount : scrollAmount);
|
||||
scrollRefs.current[index].scrollTo({
|
||||
left: newScrollLeft,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => checkScrollable(getTabIndex(activeTab));
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && data?.[activeTab]) {
|
||||
setTimeout(() => checkScrollable(getTabIndex(activeTab)), 0);
|
||||
}
|
||||
}, [loading, data, activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
const scrollElement = scrollRefs.current[getTabIndex(activeTab)];
|
||||
if (!scrollElement) {
|
||||
return;
|
||||
}
|
||||
const onScroll = () => checkScrollable(getTabIndex(activeTab));
|
||||
scrollElement.addEventListener('scroll', onScroll);
|
||||
checkScrollable(getTabIndex(activeTab));
|
||||
return () => scrollElement.removeEventListener('scroll', onScroll);
|
||||
}, [data, activeTab]);
|
||||
|
||||
return {
|
||||
scrollRefs,
|
||||
canScrollLeft,
|
||||
canScrollRight,
|
||||
handleScroll,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 {
|
||||
type LibraryResourceListResponse,
|
||||
type LibraryResourceListRequest,
|
||||
ResType,
|
||||
} from '@coze-arch/idl/plugin_develop';
|
||||
import { type GetOfficialPromptResourceListResponse } from '@coze-arch/idl/playground_api';
|
||||
import { PlaygroundApi, PluginDevelopApi } from '@coze-arch/bot-api';
|
||||
|
||||
interface LibraryInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
promptText?: string;
|
||||
}
|
||||
export const useGetLibrarys = () => {
|
||||
const {
|
||||
runAsync: runRecommendLibrary,
|
||||
loading: loadingRecommendLibrary,
|
||||
data: dataRecommendLibrary,
|
||||
} = useGetRecommendLibrarys();
|
||||
const {
|
||||
runAsync: runTeamLibrary,
|
||||
loading: loadingTeamLibrary,
|
||||
data: dataTeamLibrary,
|
||||
} = useGetTeamLibrarys();
|
||||
return {
|
||||
loading: loadingRecommendLibrary || loadingTeamLibrary,
|
||||
data: {
|
||||
Recommended: dataRecommendLibrary ?? [],
|
||||
Team: dataTeamLibrary ?? [],
|
||||
},
|
||||
runAsync: (
|
||||
type: 'Recommended' | 'Team',
|
||||
options: LibraryResourceListRequest,
|
||||
) => {
|
||||
if (type === 'Recommended') {
|
||||
return runRecommendLibrary({ size: options.size });
|
||||
}
|
||||
if (type === 'Team') {
|
||||
return runTeamLibrary(options);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
export const useGetRecommendLibrarys = (): {
|
||||
data: LibraryInfo[] | undefined;
|
||||
runAsync: (options: {
|
||||
size?: number;
|
||||
}) => Promise<GetOfficialPromptResourceListResponse>;
|
||||
loading: boolean;
|
||||
} => {
|
||||
const size = useRef<number | undefined>();
|
||||
const [slicedData, setSlicedData] =
|
||||
useState<GetOfficialPromptResourceListResponse['data']>();
|
||||
|
||||
const { runAsync, loading } = useRequest(
|
||||
() => PlaygroundApi.GetOfficialPromptResourceList(),
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: res => {
|
||||
const processedData = size.current
|
||||
? res.data?.slice(0, size.current)
|
||||
: res.data;
|
||||
setSlicedData(processedData);
|
||||
return res;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const runAsyncHandler = async (options: { size?: number }) => {
|
||||
size.current = options.size;
|
||||
return runAsync();
|
||||
};
|
||||
|
||||
const commonData = slicedData?.map(
|
||||
({
|
||||
id = '',
|
||||
name = '',
|
||||
description = '',
|
||||
prompt_text: promptText = '',
|
||||
}) => ({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
promptText,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
data: commonData,
|
||||
runAsync: runAsyncHandler,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
export const useGetTeamLibrarys = (): {
|
||||
data: LibraryInfo[] | undefined;
|
||||
runAsync: (
|
||||
options: LibraryResourceListRequest,
|
||||
) => Promise<LibraryResourceListResponse>;
|
||||
loading: boolean;
|
||||
} => {
|
||||
const { data, runAsync, loading } = useRequest(
|
||||
(options: LibraryResourceListRequest) =>
|
||||
PluginDevelopApi.LibraryResourceList({
|
||||
...options,
|
||||
res_type_filter: [ResType.Prompt],
|
||||
}),
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
const commonData = data?.resource_list?.map(
|
||||
({ res_id: id = '', name = '', desc = '' }) => ({
|
||||
id,
|
||||
name,
|
||||
description: desc,
|
||||
}),
|
||||
);
|
||||
return { data: commonData, runAsync, loading };
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 { RecommendCard } from './recommend-card/index';
|
||||
export { RecommendPannel } from './recommend-pannel/index';
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { Skeleton } from '@coze-arch/coze-design';
|
||||
export const RecommendCardLoading = () => (
|
||||
<div className="flex flex-col flex-shrink-0 flex-nowrap px-3 py-2 aspect-[180/120] rounded-lg border coz-stroke-primary coz-bg-max">
|
||||
<Skeleton
|
||||
placeholder={<Skeleton.Title />}
|
||||
className="mb-3 w-2/3"
|
||||
></Skeleton>
|
||||
<Skeleton
|
||||
placeholder={<Skeleton.Paragraph rows={3} />}
|
||||
className="w-full"
|
||||
></Skeleton>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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 cls from 'classnames';
|
||||
import { useEditor } from '@coze-editor/editor/react';
|
||||
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
|
||||
import { Popover, Button, Typography } from '@coze-arch/coze-design';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
import { ThemeExtension } from '@coze-common/editor-plugins/theme';
|
||||
import { LibraryBlockWidget } from '@coze-common/editor-plugins/library-insert';
|
||||
import { InputSlotWidget } from '@coze-common/editor-plugins/input-slot';
|
||||
import {
|
||||
PromptEditorRender,
|
||||
PromptEditorProvider,
|
||||
} from '@coze-common/prompt-kit-base/editor';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
|
||||
import '@coze-common/prompt-kit-base/shared/css';
|
||||
|
||||
interface RecommendCardProps {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
position?: 'topLeft' | 'top';
|
||||
prompt?: string;
|
||||
spaceId: string;
|
||||
onInsertPrompt?: (prompt: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const RecommendCard = (props: RecommendCardProps) => {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
prompt,
|
||||
onInsertPrompt,
|
||||
spaceId,
|
||||
className,
|
||||
position,
|
||||
} = props;
|
||||
const [promptText, setPromptText] = useState(prompt ?? '');
|
||||
const [isPopoverVisible, setIsPopoverVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (prompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlaygroundApi.GetPromptResourceInfo({
|
||||
prompt_resource_id: id,
|
||||
}).then(({ data: { prompt_text } = {} }) => {
|
||||
setPromptText(prompt_text ?? '');
|
||||
});
|
||||
}, [prompt, id]);
|
||||
|
||||
return (
|
||||
<PromptEditorProvider>
|
||||
<Popover
|
||||
position={position}
|
||||
visible={isPopoverVisible}
|
||||
onVisibleChange={setIsPopoverVisible}
|
||||
trigger="hover"
|
||||
key={id}
|
||||
className="rounded"
|
||||
showArrow
|
||||
// mouseLeaveDelay={150}
|
||||
// mouseEnterDelay={150}
|
||||
autoAdjustOverflow
|
||||
content={
|
||||
isPopoverVisible ? (
|
||||
<UsePromptPopoverContent
|
||||
prompt={promptText}
|
||||
title={title}
|
||||
spaceId={spaceId}
|
||||
onInsertPrompt={value => {
|
||||
onInsertPrompt?.(value);
|
||||
setIsPopoverVisible(false);
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={cls(
|
||||
'flex flex-col flex-shrink-0 flex-nowrap gap-1 px-3 py-2 relative',
|
||||
'aspect-[180/120] overflow-hidden',
|
||||
'rounded-lg border coz-stroke-primary coz-bg-max cursor-pointer',
|
||||
'coz-stroke-primary border-[0.5px] border-solid',
|
||||
'hover:coz-mg-secondary-hovered',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Typography.Text
|
||||
className="font-medium text-lg"
|
||||
ellipsis={{ rows: 1 }}
|
||||
>
|
||||
{title}
|
||||
</Typography.Text>
|
||||
<Typography.Text className="text-base" ellipsis={{ rows: 3 }}>
|
||||
{description ?? prompt?.slice(0, 50)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Popover>
|
||||
</PromptEditorProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const UsePromptPopoverContent: React.FC<{
|
||||
prompt?: string;
|
||||
title: string;
|
||||
spaceId: string;
|
||||
onInsertPrompt?: (prompt: string) => void;
|
||||
}> = ({ prompt = '', title, spaceId, onInsertPrompt }) => {
|
||||
const editor = useEditor<EditorAPI>();
|
||||
|
||||
useEffect(() => {
|
||||
editor?.$view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: editor?.$view.state.doc.length,
|
||||
insert: prompt,
|
||||
},
|
||||
});
|
||||
}, [editor, prompt]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-between w-[300px] h-[300px] gap-3">
|
||||
<div className="flex flex-col gap-1 overflow-y-auto styled-scrollbar hover-show-scrollbar">
|
||||
<div className="text-sm font-medium coz-fg-primary">{title}</div>
|
||||
<PromptEditorRender defaultValue={prompt} readonly />
|
||||
<InputSlotWidget mode="input" />
|
||||
<LibraryBlockWidget librarys={[]} readonly spaceId={spaceId} />
|
||||
<ThemeExtension
|
||||
themes={[
|
||||
EditorView.theme({
|
||||
'.cm-line': {
|
||||
paddingLeft: '0 !important',
|
||||
},
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="coz-mg-hglt hover:!coz-mg-hglt-hovered rounded">
|
||||
<Button
|
||||
color="primary"
|
||||
className="w-full font-sm font-medium !bg-transparent !coz-fg-hglt "
|
||||
onClick={() => {
|
||||
onInsertPrompt?.(prompt);
|
||||
}}
|
||||
>
|
||||
{I18n.t('prompt_resource_insert_prompt')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ViewAll = ({
|
||||
onClick,
|
||||
className,
|
||||
}: {
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
}) => (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={cls(
|
||||
'flex flex-col flex-shrink-0 flex-nowrap gap-1 px-3 py-2 items-center justify-center',
|
||||
'aspect-[180/120]',
|
||||
'rounded-lg border coz-stroke-primary coz-bg-max cursor-pointer text-sm',
|
||||
'coz-stroke-primary border-[0.5px] border-solid',
|
||||
'hover:coz-mg-secondary-hovered',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="coz-fg-primary font-medium">
|
||||
{I18n.t('prompt_resource_view_all')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { IconCozEmpty } from '@coze-arch/coze-design/icons';
|
||||
import { EmptyState } from '@coze-arch/coze-design';
|
||||
|
||||
export const EmptyRecommend = () => (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<EmptyState
|
||||
title={I18n.t('prompt_library_empty_title')}
|
||||
icon={<IconCozEmpty />}
|
||||
style={{ maxWidth: '300px' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,59 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.recommend-pannel {
|
||||
.semi-tabs-tab-button.semi-tabs-tab {
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: rgba(6, 7, 9, 80%);
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tabs-tab-button {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
|
||||
height: 32px;
|
||||
padding: 6px 8px;
|
||||
|
||||
font-weight: 500;
|
||||
color: rgba(6, 7, 9, 80%);
|
||||
}
|
||||
|
||||
.semi-tabs-tab-button.semi-tabs-tab-active {
|
||||
color: #4E40E5 !important;
|
||||
background: rgba(186, 192, 255, 20%);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.semi-tabs-tab-button.semi-tabs-tab:hover:not(.semi-tabs-tab-active) {
|
||||
background: rgba(6, 7, 9, 8%);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.semi-tabs-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.semi-tabs-bar {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.semi-tabs-pane-motion-overlay {
|
||||
box-sizing: content-box;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.semi-tabs-bar-extra {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* 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 ForwardedRef,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { useEditor } from '@coze-editor/editor/react';
|
||||
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozArrowRightFill } from '@coze-arch/coze-design/icons';
|
||||
import { Tabs, TabPane, Button } from '@coze-arch/coze-design';
|
||||
import {
|
||||
insertToNewline,
|
||||
type PromptContextInfo,
|
||||
} from '@coze-common/prompt-kit-base/shared';
|
||||
|
||||
import { RecommendCardLoading } from '../recommend-card/card-loading';
|
||||
import { ViewAll, RecommendCard } from '../recommend-card';
|
||||
import { useGetLibrarys } from '../hooks/use-get-librarys';
|
||||
import { useScrollControl } from '../hooks/use-case/use-scroll-control';
|
||||
import { usePromptLibraryModal } from '../../prompt-library';
|
||||
import { EmptyRecommend } from './empty';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
import '@coze-common/prompt-kit-base/shared/css';
|
||||
import { LeftScrollButton, RightScrollButton } from './scroll-button';
|
||||
const LIMIT_LIBRARY_SIZE = 6;
|
||||
|
||||
type TabType = 'Recommended' | 'Team';
|
||||
const getTabLabelMap = (isPersonal: boolean) => ({
|
||||
Recommended: I18n.t('prompt_resource_recommended'),
|
||||
Team: isPersonal
|
||||
? I18n.t('prompt_resource_personal')
|
||||
: I18n.t('prompt_resource_team'),
|
||||
});
|
||||
|
||||
interface ActionExtraInfo {
|
||||
id: string;
|
||||
category: string;
|
||||
}
|
||||
|
||||
interface RecommendPannelProps {
|
||||
className?: string;
|
||||
cardClassName?: string;
|
||||
listContainerClassName?: string;
|
||||
tabs: TabType[];
|
||||
/** 用于埋点: 页面来源 */
|
||||
source: string;
|
||||
importPromptWhenEmpty?: string;
|
||||
spaceId: string;
|
||||
/** 用于埋点: bot_id */
|
||||
botId?: string;
|
||||
/** 用于埋点: project_id */
|
||||
projectId?: string;
|
||||
/** 用于埋点: workflow_id */
|
||||
workflowId?: string;
|
||||
isPersonal?: boolean;
|
||||
enableLibrary?: boolean;
|
||||
getConversationId?: () => string | undefined;
|
||||
getPromptContextInfo?: () => PromptContextInfo;
|
||||
onInsertPrompt?: (prompt: string, info?: ActionExtraInfo) => void;
|
||||
onUpdateSuccess?: (
|
||||
mode: 'create' | 'edit' | 'info',
|
||||
info: ActionExtraInfo,
|
||||
) => void;
|
||||
onCopyPrompt?: (info: ActionExtraInfo) => void;
|
||||
onDeletePrompt?: (info: ActionExtraInfo) => void;
|
||||
ref: ForwardedRef<RecommendPannelRef>;
|
||||
}
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
export const Index = (props: RecommendPannelProps) => {
|
||||
const domRef = useRef<HTMLDivElement | null>(null);
|
||||
const {
|
||||
className,
|
||||
cardClassName,
|
||||
listContainerClassName,
|
||||
onInsertPrompt,
|
||||
tabs,
|
||||
spaceId,
|
||||
enableLibrary = false,
|
||||
getConversationId,
|
||||
getPromptContextInfo,
|
||||
importPromptWhenEmpty,
|
||||
source,
|
||||
botId,
|
||||
projectId,
|
||||
workflowId,
|
||||
ref,
|
||||
isPersonal = false,
|
||||
onCopyPrompt,
|
||||
onDeletePrompt,
|
||||
onUpdateSuccess,
|
||||
} = props;
|
||||
const [activeTab, setActiveTab] = useState<(typeof tabs)[number]>(tabs[0]);
|
||||
const editor = useEditor<EditorAPI>();
|
||||
|
||||
const handleInsertPrompt = async (prompt: string, id: string) => {
|
||||
const insertPrompt = await insertToNewline({ editor, prompt });
|
||||
onInsertPrompt?.(insertPrompt, { id, category: activeTab });
|
||||
};
|
||||
|
||||
const { loading, data, runAsync } = useGetLibrarys();
|
||||
const isEmpty = !loading && data?.[activeTab]?.length === 0;
|
||||
const { open, node: PromptLibrary } = usePromptLibraryModal({
|
||||
spaceId,
|
||||
getConversationId,
|
||||
editor,
|
||||
isPersonal,
|
||||
source,
|
||||
botId,
|
||||
projectId,
|
||||
workflowId,
|
||||
getPromptContextInfo,
|
||||
importPromptWhenEmpty,
|
||||
onInsertPrompt,
|
||||
onUpdateSuccess: (mode, selectedLibrary) => {
|
||||
runAsync(activeTab, {
|
||||
space_id: spaceId,
|
||||
size: LIMIT_LIBRARY_SIZE,
|
||||
});
|
||||
onUpdateSuccess?.(mode, selectedLibrary);
|
||||
},
|
||||
onCopyPrompt,
|
||||
onDeletePrompt,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!spaceId) {
|
||||
return;
|
||||
}
|
||||
runAsync(activeTab, {
|
||||
space_id: spaceId,
|
||||
size: LIMIT_LIBRARY_SIZE,
|
||||
});
|
||||
}, [spaceId, activeTab]);
|
||||
|
||||
const { scrollRefs, canScrollLeft, canScrollRight, handleScroll } =
|
||||
useScrollControl({
|
||||
activeTab,
|
||||
tabs,
|
||||
loading,
|
||||
data,
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
refresh: (tab: 'Recommended' | 'Team') => {
|
||||
runAsync(tab, {
|
||||
space_id: spaceId,
|
||||
size: LIMIT_LIBRARY_SIZE,
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={el => {
|
||||
if (typeof ref === 'function') {
|
||||
ref(null);
|
||||
}
|
||||
domRef.current = el;
|
||||
}}
|
||||
className={cls(
|
||||
styles['recommend-pannel'],
|
||||
'flex flex-col justify-between w-full',
|
||||
'absolute bottom-0 left-0 right-0',
|
||||
'py-3 px-5',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Tabs
|
||||
type="button"
|
||||
activeKey={activeTab}
|
||||
onChange={key => setActiveTab(key as (typeof tabs)[number])}
|
||||
tabBarExtraContent={
|
||||
enableLibrary ? (
|
||||
<div
|
||||
className="coz-fg-primary text-sm flex items-center cursor-pointer font-medium"
|
||||
onClick={() => open({ defaultActiveTab: activeTab })}
|
||||
>
|
||||
<Button
|
||||
icon={<IconCozArrowRightFill className="!coz-fg-primary" />}
|
||||
color="secondary"
|
||||
iconPosition="right"
|
||||
>
|
||||
<span className="coz-fg-primary">
|
||||
{I18n.t('workflow_prompt_editor_view_library')}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{tabs.map((item, index) => (
|
||||
<TabPane
|
||||
itemKey={item}
|
||||
tab={getTabLabelMap(isPersonal)[item]}
|
||||
className="relative"
|
||||
>
|
||||
{canScrollLeft ? (
|
||||
<LeftScrollButton handleScroll={() => handleScroll('left')} />
|
||||
) : null}
|
||||
<div className="relative">
|
||||
<div
|
||||
ref={el => (scrollRefs.current[index] = el)}
|
||||
className={cls(
|
||||
'relative overflow-x-auto styled-scrollbar h-[120px] box-content hover-show-scrollbar',
|
||||
'flex-1',
|
||||
listContainerClassName,
|
||||
)}
|
||||
>
|
||||
{isEmpty ? (
|
||||
<EmptyRecommend />
|
||||
) : (
|
||||
<div className="flex gap-3 flex-row flex-nowrap overflow-visible h-full min-w-min">
|
||||
{loading
|
||||
? Array.from({ length: LIMIT_LIBRARY_SIZE }).map(
|
||||
(_, _index) => <RecommendCardLoading key={_index} />,
|
||||
)
|
||||
: null}
|
||||
{data?.[item]?.map((card, _index) => (
|
||||
<RecommendCard
|
||||
className={cls(cardClassName)}
|
||||
key={card.id}
|
||||
id={card.id}
|
||||
position={_index === 0 ? 'topLeft' : 'top'}
|
||||
spaceId={spaceId}
|
||||
title={card.name}
|
||||
description={card.description}
|
||||
prompt={card.promptText}
|
||||
onInsertPrompt={prompt =>
|
||||
handleInsertPrompt(prompt, card.id)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<ViewAll onClick={() => open({ defaultActiveTab: item })} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{canScrollRight ? (
|
||||
<RightScrollButton handleScroll={() => handleScroll('right')} />
|
||||
) : null}
|
||||
</div>
|
||||
</TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
{PromptLibrary}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface RecommendPannelRef {
|
||||
refresh: (tab: 'Recommended' | 'Team') => void;
|
||||
}
|
||||
export const RecommendPannel = forwardRef<
|
||||
RecommendPannelRef,
|
||||
RecommendPannelProps
|
||||
>((props, ref) => <Index {...props} ref={ref} />);
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 {
|
||||
IconCozArrowLeftFill,
|
||||
IconCozArrowRightFill,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
|
||||
export const LeftScrollButton = ({
|
||||
handleScroll,
|
||||
}: {
|
||||
handleScroll: () => void;
|
||||
}) => (
|
||||
<div
|
||||
className="absolute bottom-0 left-0 top-0 w-8 z-10"
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(90deg, #F9F9F9 0%, rgba(249, 249, 249, 0.00) 100%)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onClick={handleScroll}
|
||||
className="w-6 h-6 coz-bg-max flex justify-center items-center absolute left-0 top-1/2 -translate-y-1/2 z-20 cursor-pointer rounded-lg coz-stroke-primary coz-shadow-small"
|
||||
>
|
||||
<IconCozArrowLeftFill className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const RightScrollButton = ({
|
||||
handleScroll,
|
||||
}: {
|
||||
handleScroll: () => void;
|
||||
}) => (
|
||||
<div
|
||||
className="absolute bottom-0 right-0 top-0 w-8"
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(270deg, #F9F9F9 0%, rgba(249, 249, 249, 0.00) 100%)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onClick={handleScroll}
|
||||
className="w-6 h-6 coz-bg-max flex justify-center items-center absolute right-0 top-1/2 -translate-y-1/2 z-20 cursor-pointer rounded-lg coz-stroke-primary coz-shadow-small"
|
||||
>
|
||||
<IconCozArrowRightFill className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
0
frontend/packages/common/prompt-kit/main/src/typings.d.ts
vendored
Normal file
0
frontend/packages/common/prompt-kit/main/src/typings.d.ts
vendored
Normal file
92
frontend/packages/common/prompt-kit/main/tsconfig.build.json
Normal file
92
frontend/packages/common/prompt-kit/main/tsconfig.build.json
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"types": [],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "bundler",
|
||||
"module": "ESNext",
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../agent-ide/bot-editor-context-store/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-api/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-env/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-error/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-http/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-tea/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-typings/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/i18n/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/idl/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../base/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../chat-area/chat-answer-action/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../chat-area/chat-core/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../chat-area/chat-uikit/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../chat-area/utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../components/bot-icons/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../components/bot-semi/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../components/scroll-view/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/eslint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/stylelint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/ts-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/vitest-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../editor-plugins/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../foundation/local-storage/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
frontend/packages/common/prompt-kit/main/tsconfig.json
Normal file
15
frontend/packages/common/prompt-kit/main/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"exclude": ["**/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.misc.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
23
frontend/packages/common/prompt-kit/main/tsconfig.misc.json
Normal file
23
frontend/packages/common/prompt-kit/main/tsconfig.misc.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"include": ["__tests__", "stories", "vitest.config.ts", "tailwind.config.ts"],
|
||||
"exclude": ["./dist"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"types": ["vitest/globals"],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "bundler",
|
||||
"module": "ESNext"
|
||||
}
|
||||
}
|
||||
22
frontend/packages/common/prompt-kit/main/vitest.config.ts
Normal file
22
frontend/packages/common/prompt-kit/main/vitest.config.ts
Normal 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.
|
||||
*/
|
||||
|
||||
import { defineConfig } from '@coze-arch/vitest-config';
|
||||
|
||||
export default defineConfig({
|
||||
dirname: __dirname,
|
||||
preset: 'web',
|
||||
});
|
||||
Reference in New Issue
Block a user