From 77f4984fed9730534ad0d17642e527f824868315 Mon Sep 17 00:00:00 2001 From: bill Date: Fri, 24 Jan 2025 11:37:16 +0800 Subject: [PATCH 01/10] Feat: Add VariableNode #4225 --- web/package-lock.json | 305 ++++++++++++++++++ web/package.json | 2 + web/src/components/prompt-editor/index.css | 92 ++++++ web/src/components/prompt-editor/index.tsx | 94 ++++++ web/src/components/prompt-editor/theme.ts | 43 +++ .../prompt-editor/variable-node.tsx | 45 +++ .../prompt-editor/variable-picker-plugin.tsx | 161 +++++++++ .../pages/flow/form/generate-form/index.tsx | 36 ++- .../pages/flow/hooks/use-get-begin-query.tsx | 3 +- 9 files changed, 778 insertions(+), 3 deletions(-) create mode 100644 web/src/components/prompt-editor/index.css create mode 100644 web/src/components/prompt-editor/index.tsx create mode 100644 web/src/components/prompt-editor/theme.ts create mode 100644 web/src/components/prompt-editor/variable-node.tsx create mode 100644 web/src/components/prompt-editor/variable-picker-plugin.tsx diff --git a/web/package-lock.json b/web/package-lock.json index 3b5d1d4cc1b..d1b1247d924 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -13,6 +13,7 @@ "@antv/g6": "^5.0.10", "@hookform/resolvers": "^3.9.1", "@js-preview/excel": "^1.7.8", + "@lexical/react": "^0.23.1", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-aspect-ratio": "^1.1.0", @@ -57,6 +58,7 @@ "input-otp": "^1.4.1", "js-base64": "^3.7.5", "jsencrypt": "^3.3.2", + "lexical": "^0.23.1", "lodash": "^4.17.21", "lucide-react": "^0.454.0", "mammoth": "^1.7.2", @@ -4093,6 +4095,256 @@ "resolved": "https://registry.npmmirror.com/@js-preview/excel/-/excel-1.7.8.tgz", "integrity": "sha512-pLJTDIhbzqaiH3kUPnbeWLsBFeCAHjnBwloMvoREdW4YUYTcsHDQ5h41QTyRJWSYRJBCcsy6Kt7KeDHOHDbVEw==" }, + "node_modules/@lexical/clipboard": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/clipboard/-/clipboard-0.23.1.tgz", + "integrity": "sha512-MT8IXl1rhTe8VcwnkhgFtWra6sRYNsl/I7nE9aw6QxwvPReKmRDmyBmEIeXwnKSGHRe19OJhu4/A9ciKPyVdMA==", + "dependencies": { + "@lexical/html": "0.23.1", + "@lexical/list": "0.23.1", + "@lexical/selection": "0.23.1", + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/code": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/code/-/code-0.23.1.tgz", + "integrity": "sha512-TOxaFAwoewrX3rHp4Po+u1LJT8oteP/6Kn2z6j9DaynBW62gIqTuSAFcMPysVx/Puq5hhJHPRD/be9RWDteDZw==", + "dependencies": { + "@lexical/utils": "0.23.1", + "lexical": "0.23.1", + "prismjs": "^1.27.0" + } + }, + "node_modules/@lexical/devtools-core": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/devtools-core/-/devtools-core-0.23.1.tgz", + "integrity": "sha512-QsgcrECy11ZHhWAfyNW/ougXFF1o0EuQnhFybgTdqQmw0rJ2ZgPLpPjD5lws3CE8mP8g5knBV4/cyxvv42fzzg==", + "dependencies": { + "@lexical/html": "0.23.1", + "@lexical/link": "0.23.1", + "@lexical/mark": "0.23.1", + "@lexical/table": "0.23.1", + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/dragon": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/dragon/-/dragon-0.23.1.tgz", + "integrity": "sha512-ZoY9VJDrTpO69sinRhIs3RlPAWviy4mwnC7lqtM77/pVK0Kaknv7z2iDqv+414PKQCgUhyoXp7PfYXu/3yb6LQ==", + "dependencies": { + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/hashtag": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/hashtag/-/hashtag-0.23.1.tgz", + "integrity": "sha512-EkRCHV/IQwKlggy3VQDF9b4Krc9DKNZEjXe84CkEVrRpQSOwXi0qORzuaAipARyN632WKLSXOZJmNzkUNocJ6A==", + "dependencies": { + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/history": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/history/-/history-0.23.1.tgz", + "integrity": "sha512-5Vro4bIePw37MwffpvPm56WlwPdlY/u+fVkvXsxdhK9bqiFesmLZhBirokDPvJEMP35V59kzmN5mmWXSYfuRpg==", + "dependencies": { + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/html": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/html/-/html-0.23.1.tgz", + "integrity": "sha512-kNkDUaDe/Awypaw8JZn65BzT1gwNj2bNkaGFcmIkXUrTtiqlvgYvKvJeOKLkoAb/i2xq990ZAbHOsJrJm1jMbw==", + "dependencies": { + "@lexical/selection": "0.23.1", + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/link": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/link/-/link-0.23.1.tgz", + "integrity": "sha512-HRaOp7prtcbHjbgq8AjJ4O02jYb8pTeS8RrGcgIRhCOq3/EcsSb1dXMwuraqmh9oxbuFyEu/JE31EFksiOW6qA==", + "dependencies": { + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/list": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/list/-/list-0.23.1.tgz", + "integrity": "sha512-TI3WyWk3avv9uaJwaq8V+m9zxLRgnzXDYNS0rREafnW09rDpaFkpVmDuX+PZVR3NqPlwVt+slWVSBuyfguAFbA==", + "dependencies": { + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/mark": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/mark/-/mark-0.23.1.tgz", + "integrity": "sha512-E7cMOBVMrNGMw0LsyWKNFQZ5Io3bUIHCC3aCUdH24z1XWnuTmDFKMqNrphywPniO7pzSgVyGpkQBZIAIN76+YA==", + "dependencies": { + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/markdown": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/markdown/-/markdown-0.23.1.tgz", + "integrity": "sha512-TQx8oXenaiVYffBPxD85m4CydbDAuYOonATiABAFG6CHkA6vi898M1TCTgVDS6/iISjtjQpqHo0SW7YjLt14jw==", + "dependencies": { + "@lexical/code": "0.23.1", + "@lexical/link": "0.23.1", + "@lexical/list": "0.23.1", + "@lexical/rich-text": "0.23.1", + "@lexical/text": "0.23.1", + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/offset": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/offset/-/offset-0.23.1.tgz", + "integrity": "sha512-ylw5egME/lldacVXDoRsdGDXPuk9lGmYgcqx/aITGrSymav+RDjQoAapHbz1HQqGmm/m18+VLaWTdjtkbrIN6g==", + "dependencies": { + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/overflow": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/overflow/-/overflow-0.23.1.tgz", + "integrity": "sha512-WubTqozpxOeyTm/tKIHXinsjuRcgPESacOvu93dS+sC7q3n+xeBIu5FL7lM6bbsk3zNtNJQ9sG0svZngmWRjCw==", + "dependencies": { + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/plain-text": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/plain-text/-/plain-text-0.23.1.tgz", + "integrity": "sha512-tM4DJw+HyT9XV4BKGVECDnejcC//jsFggjFmJgwIMTCxJPiGXEEZLZTXmGqf8QdFZ6cH1I5bhreZPQUWu6dRvg==", + "dependencies": { + "@lexical/clipboard": "0.23.1", + "@lexical/selection": "0.23.1", + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/react": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/react/-/react-0.23.1.tgz", + "integrity": "sha512-g5CQMOiK+Djqp75UaSFUceHZEUQVIXBzWBuVR69pCiptCgNqN3CNAoIxy0hTTaVrLq6S0SCjUOduBDtioN0bLA==", + "dependencies": { + "@lexical/clipboard": "0.23.1", + "@lexical/code": "0.23.1", + "@lexical/devtools-core": "0.23.1", + "@lexical/dragon": "0.23.1", + "@lexical/hashtag": "0.23.1", + "@lexical/history": "0.23.1", + "@lexical/link": "0.23.1", + "@lexical/list": "0.23.1", + "@lexical/mark": "0.23.1", + "@lexical/markdown": "0.23.1", + "@lexical/overflow": "0.23.1", + "@lexical/plain-text": "0.23.1", + "@lexical/rich-text": "0.23.1", + "@lexical/selection": "0.23.1", + "@lexical/table": "0.23.1", + "@lexical/text": "0.23.1", + "@lexical/utils": "0.23.1", + "@lexical/yjs": "0.23.1", + "lexical": "0.23.1", + "react-error-boundary": "^3.1.4" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/react/node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/@lexical/rich-text": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/rich-text/-/rich-text-0.23.1.tgz", + "integrity": "sha512-Y77HGxdF5aemjw/H44BXETD5KNeaNdwMRu9P7IrlK7cC1dvvimzL2D6ezbub5i7F1Ef5T0quOXjwK056vrqaKQ==", + "dependencies": { + "@lexical/clipboard": "0.23.1", + "@lexical/selection": "0.23.1", + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/selection": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/selection/-/selection-0.23.1.tgz", + "integrity": "sha512-xoehAURMZJZYf046GHUXiv8FSv5zTobhwDD2dML4fmNHPp9NxugkWHlNUinTK/b+jGgjSYVsqpEKPBmue4ZHdQ==", + "dependencies": { + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/table": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/table/-/table-0.23.1.tgz", + "integrity": "sha512-Qs+iuwSVkV4OGTt+JdL9hvyl/QO3X9waH70L5Fxu9JmQk/jLl02tIGXbE38ocJkByfpyk4PrphoXt6l7CugJZA==", + "dependencies": { + "@lexical/clipboard": "0.23.1", + "@lexical/utils": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/text": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/text/-/text-0.23.1.tgz", + "integrity": "sha512-aOuuAhmc+l2iSK99uP0x/Zg9LSQswQdNG3IxzGa0rTx844mWUHuEbAUaOqqlgDA1/zZ0WjObyhPfZJL775y63g==", + "dependencies": { + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/utils": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/utils/-/utils-0.23.1.tgz", + "integrity": "sha512-yXEkF6fj32+mJblCoP0ZT/vA0S05FA0nRUkVrvGX6sbZ9y+cIzuIbBoHi4z1ytutcWHQrwCK4TsN9hPYBIlb2w==", + "dependencies": { + "@lexical/list": "0.23.1", + "@lexical/selection": "0.23.1", + "@lexical/table": "0.23.1", + "lexical": "0.23.1" + } + }, + "node_modules/@lexical/yjs": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/@lexical/yjs/-/yjs-0.23.1.tgz", + "integrity": "sha512-ygodSxmC65srNicMIhqBRIXI2LHhmnHcR1EO9fLO7flZWGCR1HIoeGmwhHo9FLgJoc5LHanV+dE0z1onFo1qqQ==", + "dependencies": { + "@lexical/offset": "0.23.1", + "@lexical/selection": "0.23.1", + "lexical": "0.23.1" + }, + "peerDependencies": { + "yjs": ">=13.5.22" + } + }, "node_modules/@ljharb/resumer": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/@ljharb/resumer/-/resumer-0.0.1.tgz", @@ -17204,6 +17456,16 @@ "unfetch": "^5.0.0" } }, + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "peer": true, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -20060,6 +20322,32 @@ "node": ">= 0.8.0" } }, + "node_modules/lexical": { + "version": "0.23.1", + "resolved": "https://registry.npmmirror.com/lexical/-/lexical-0.23.1.tgz", + "integrity": "sha512-iuS72HcAYUemsCRQCm4XZzkGhZb8a9KagW+ee2TFfkkf9f3ZpUYSrobMpjYVZRkgMOx7Zk5VCPMxm1nouJTfnQ==" + }, + "node_modules/lib0": { + "version": "0.2.99", + "resolved": "https://registry.npmmirror.com/lib0/-/lib0-0.2.99.tgz", + "integrity": "sha512-vwztYuUf1uf/1zQxfzRfO5yzfNKhTtgOByCruuiQQxWQXnPb8Itaube5ylofcV0oM0aKal9Mv+S1s1Ky0UYP1w==", + "peer": true, + "dependencies": { + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", @@ -31314,6 +31602,23 @@ "node": ">=12" } }, + "node_modules/yjs": { + "version": "13.6.23", + "resolved": "https://registry.npmmirror.com/yjs/-/yjs-13.6.23.tgz", + "integrity": "sha512-ExtnT5WIOVpkL56bhLeisG/N5c4fmzKn4k0ROVfJa5TY2QHbH7F0Wu2T5ZhR7ErsFWQEFafyrnSI8TPKVF9Few==", + "peer": true, + "dependencies": { + "lib0": "^0.2.99" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz", diff --git a/web/package.json b/web/package.json index 63f40145151..1ab894f5ee2 100644 --- a/web/package.json +++ b/web/package.json @@ -24,6 +24,7 @@ "@antv/g6": "^5.0.10", "@hookform/resolvers": "^3.9.1", "@js-preview/excel": "^1.7.8", + "@lexical/react": "^0.23.1", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-aspect-ratio": "^1.1.0", @@ -68,6 +69,7 @@ "input-otp": "^1.4.1", "js-base64": "^3.7.5", "jsencrypt": "^3.3.2", + "lexical": "^0.23.1", "lodash": "^4.17.21", "lucide-react": "^0.454.0", "mammoth": "^1.7.2", diff --git a/web/src/components/prompt-editor/index.css b/web/src/components/prompt-editor/index.css new file mode 100644 index 00000000000..cec1af5105f --- /dev/null +++ b/web/src/components/prompt-editor/index.css @@ -0,0 +1,92 @@ +.typeahead-popover { + background: #fff; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3); + border-radius: 8px; + position: fixed; + z-index: 1000; +} + +.typeahead-popover ul { + padding: 0; + list-style: none; + margin: 0; + border-radius: 8px; + max-height: 200px; + overflow-y: scroll; +} + +.typeahead-popover ul::-webkit-scrollbar { + display: none; +} + +.typeahead-popover ul { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.typeahead-popover ul li { + margin: 0; + min-width: 180px; + font-size: 14px; + outline: none; + cursor: pointer; + border-radius: 8px; +} + +.typeahead-popover ul li.selected { + background: #eee; +} + +.typeahead-popover li { + margin: 0 8px 0 8px; + padding: 8px; + color: #050505; + cursor: pointer; + line-height: 16px; + font-size: 15px; + display: flex; + align-content: center; + flex-direction: row; + flex-shrink: 0; + background-color: #fff; + border-radius: 8px; + border: 0; +} + +.typeahead-popover li.active { + display: flex; + width: 20px; + height: 20px; + background-size: contain; +} + +.typeahead-popover li:first-child { + border-radius: 8px 8px 0px 0px; +} + +.typeahead-popover li:last-child { + border-radius: 0px 0px 8px 8px; +} + +.typeahead-popover li:hover { + background-color: #eee; +} + +.typeahead-popover li .text { + display: flex; + line-height: 20px; + flex-grow: 1; + min-width: 150px; +} + +.typeahead-popover li .icon { + display: flex; + width: 20px; + height: 20px; + user-select: none; + margin-right: 8px; + line-height: 16px; + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} diff --git a/web/src/components/prompt-editor/index.tsx b/web/src/components/prompt-editor/index.tsx new file mode 100644 index 00000000000..48c861d1fb1 --- /dev/null +++ b/web/src/components/prompt-editor/index.tsx @@ -0,0 +1,94 @@ +import { CodeHighlightNode, CodeNode } from '@lexical/code'; +import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; +import { + InitialConfigType, + LexicalComposer, +} from '@lexical/react/LexicalComposer'; +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { ContentEditable } from '@lexical/react/LexicalContentEditable'; +import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; +import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; +import { HeadingNode, QuoteNode } from '@lexical/rich-text'; +import { EditorState, Klass, LexicalNode } from 'lexical'; +import { useEffect, useState } from 'react'; + +import theme from './theme'; +import { VariableNode } from './variable-node'; +import VariablePickerMenuPlugin from './variable-picker-plugin'; + +// Catch any errors that occur during Lexical updates and log them +// or throw them as needed. If you don't throw them, Lexical will +// try to recover gracefully without losing user data. +function onError(error: Error) { + console.error(error); +} + +type MyOnChangePluginProps = { onChange: (editorState: EditorState) => void }; + +const Nodes: Array> = [ + HeadingNode, + QuoteNode, + CodeHighlightNode, + CodeNode, + VariableNode, +]; + +function MyOnChangePlugin({ onChange }: MyOnChangePluginProps) { + const [editor] = useLexicalComposerContext(); + useEffect(() => { + return editor.registerUpdateListener(({ editorState }) => { + onChange(editorState); + }); + }, [editor, onChange]); + return null; +} + +export function PromptEditor() { + const initialConfig: InitialConfigType = { + namespace: 'MyEditor', + theme, + onError, + nodes: Nodes, + // html: { import: buildImportMap() }, + + // editorState() { + // const root = $getRoot(); + // if (root.getFirstChild() === null) { + // const quote = $createQuoteNode(); + // quote.append( + // $createTextNode( + // `In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. ` + + // `You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.`, + // ), + // ); + // root.append(quote); + // } + // }, + }; + + const [editorState, setEditorState] = useState(); + function onChange(editorState: EditorState) { + const editorStateJSON = editorState.toJSON(); + console.log('🚀 ~ onChange ~ editorStateJSON:', editorStateJSON); + setEditorState(editorState); + } + + return ( + + + } + placeholder={ +
Enter some text...
+ } + ErrorBoundary={LexicalErrorBoundary} + /> + + + + +
+ ); +} diff --git a/web/src/components/prompt-editor/theme.ts b/web/src/components/prompt-editor/theme.ts new file mode 100644 index 00000000000..1cc2bc15528 --- /dev/null +++ b/web/src/components/prompt-editor/theme.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +export default { + code: 'editor-code', + heading: { + h1: 'editor-heading-h1', + h2: 'editor-heading-h2', + h3: 'editor-heading-h3', + h4: 'editor-heading-h4', + h5: 'editor-heading-h5', + }, + image: 'editor-image', + link: 'editor-link', + list: { + listitem: 'editor-listitem', + nested: { + listitem: 'editor-nested-listitem', + }, + ol: 'editor-list-ol', + ul: 'editor-list-ul', + }, + ltr: 'ltr', + paragraph: 'editor-paragraph', + placeholder: 'editor-placeholder', + quote: 'editor-quote', + rtl: 'rtl', + text: { + bold: 'editor-text-bold', + code: 'editor-text-code', + hashtag: 'editor-text-hashtag', + italic: 'editor-text-italic', + overflowed: 'editor-text-overflowed', + strikethrough: 'editor-text-strikethrough', + underline: 'editor-text-underline', + underlineStrikethrough: 'editor-text-underlineStrikethrough', + }, +}; diff --git a/web/src/components/prompt-editor/variable-node.tsx b/web/src/components/prompt-editor/variable-node.tsx new file mode 100644 index 00000000000..94ef5d88347 --- /dev/null +++ b/web/src/components/prompt-editor/variable-node.tsx @@ -0,0 +1,45 @@ +import { DecoratorNode, LexicalNode, NodeKey } from 'lexical'; +import { ReactNode } from 'react'; +import { Badge } from '../ui/badge'; + +export class VariableNode extends DecoratorNode { + __id: string; + + static getType(): string { + return 'video'; + } + + static clone(node: VariableNode): VariableNode { + return new VariableNode(node.__id, node.__key); + } + + constructor(id: string, key?: NodeKey) { + super(key); + this.__id = id; + } + + createDOM(): HTMLElement { + const dom = document.createElement('span'); + dom.className = 'mr-1'; + + return dom; + } + + updateDOM(): false { + return false; + } + + decorate(): ReactNode { + return {this.__id}; + } +} + +export function $createVariableNode(id: string): VariableNode { + return new VariableNode(id); +} + +export function $isVariableNode( + node: LexicalNode | null | undefined, +): node is VariableNode { + return node instanceof VariableNode; +} diff --git a/web/src/components/prompt-editor/variable-picker-plugin.tsx b/web/src/components/prompt-editor/variable-picker-plugin.tsx new file mode 100644 index 00000000000..8418b49aa85 --- /dev/null +++ b/web/src/components/prompt-editor/variable-picker-plugin.tsx @@ -0,0 +1,161 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { + LexicalTypeaheadMenuPlugin, + MenuOption, + useBasicTypeaheadTriggerMatch, +} from '@lexical/react/LexicalTypeaheadMenuPlugin'; +import { $getSelection, $isRangeSelection, TextNode } from 'lexical'; +import { useCallback, useState } from 'react'; +import * as ReactDOM from 'react-dom'; + +import './index.css'; +import { $createVariableNode } from './variable-node'; + +type VariableOptionType = { + key: string; + label: string; + value: string; +} & MenuOption; + +class VariableOption extends MenuOption { + label: string; + value: string; + + constructor(label: string, value: string) { + super(label); + this.label = label; + this.value = value; + } +} + +function VariablePickerMenuItem({ + index, + isSelected, + onClick, + onMouseEnter, + option, +}: { + index: number; + isSelected: boolean; + onClick: () => void; + onMouseEnter: () => void; + option: VariableOptionType; +}) { + let className = 'item'; + if (isSelected) { + className += ' selected'; + } + return ( +
  • + {/* {option.icon} */} + {option.label} +
  • + ); +} + +export default function VariablePickerMenuPlugin(): JSX.Element { + const [editor] = useLexicalComposerContext(); + const [queryString, setQueryString] = useState(null); + + const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', { + minLength: 0, + }); + + const options: VariableOptionType[] = [ + { + value: 'afc163', + label: 'afc163', + }, + { + value: 'zombieJ', + label: 'zombieJ', + }, + { + value: 'yesmeck', + label: 'yesmeck', + }, + ].map((x) => new VariableOption(x.label, x.value)); + + const onSelectOption = useCallback( + ( + selectedOption: VariableOptionType, + nodeToRemove: TextNode | null, + closeMenu: () => void, + ) => { + editor.update(() => { + const selection = $getSelection(); + + if (!$isRangeSelection(selection) || selectedOption === null) { + return; + } + + if (nodeToRemove) { + nodeToRemove.remove(); + } + + selection.insertNodes([$createVariableNode(selectedOption.value)]); + + closeMenu(); + }); + }, + [editor], + ); + + return ( + <> + + onQueryChange={setQueryString} + onSelectOption={onSelectOption} + triggerFn={checkForTriggerMatch} + options={options} + menuRenderFn={( + anchorElementRef, + { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, + ) => + anchorElementRef.current && options.length + ? ReactDOM.createPortal( +
    +
      + {options.map((option, i: number) => ( + { + setHighlightedIndex(i); + selectOptionAndCleanUp(option); + }} + onMouseEnter={() => { + setHighlightedIndex(i); + }} + key={option.key} + option={option} + /> + ))} +
    +
    , + anchorElementRef.current, + ) + : null + } + /> + + ); +} diff --git a/web/src/pages/flow/form/generate-form/index.tsx b/web/src/pages/flow/form/generate-form/index.tsx index ad6396d9e20..4e70e80d913 100644 --- a/web/src/pages/flow/form/generate-form/index.tsx +++ b/web/src/pages/flow/form/generate-form/index.tsx @@ -1,13 +1,45 @@ import LLMSelect from '@/components/llm-select'; import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item'; +import { PromptEditor } from '@/components/prompt-editor'; import { useTranslate } from '@/hooks/common-hooks'; -import { Form, Input, Switch } from 'antd'; +import { Form, Switch } from 'antd'; +import { useMemo } from 'react'; +import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; import { IOperatorForm } from '../../interface'; import DynamicParameters from './dynamic-parameters'; +const list = [ + { + value: 'afc163', + label: 'afc163', + }, + { + value: 'zombieJ', + label: 'zombieJ', + }, + { + value: 'yesmeck', + label: 'yesmeck', + }, +].map((x) => ({ + ...x, + value: `{${x.value}}`, +})); + const GenerateForm = ({ onValuesChange, form, node }: IOperatorForm) => { const { t } = useTranslate('flow'); + const options = useBuildComponentIdSelectOptions(node?.id, node?.parentId); + + const nextOptions = useMemo(() => { + return options.reduce((pre, cur) => { + cur.options.forEach((x) => { + pre.push({ ...x, value: `{${x.value}}` }); + }); + return pre; + }, []); + }, [options]); + return (
    { }, ]} > - + { From d966fa34045e2a783f1d2a934551b3011bf9bef4 Mon Sep 17 00:00:00 2001 From: bill Date: Tue, 4 Feb 2025 15:13:18 +0800 Subject: [PATCH 02/10] Feat: Convert a string into components --- web/src/components/prompt-editor/index.css | 20 +-- web/src/components/prompt-editor/index.tsx | 63 +++---- .../prompt-editor/variable-node.tsx | 25 ++- .../prompt-editor/variable-picker-plugin.tsx | 162 +++++++++++------- web/src/pages/flow/context.ts | 6 + web/src/pages/flow/flow-drawer/index.tsx | 13 +- web/src/pages/flow/utils.ts | 10 ++ 7 files changed, 178 insertions(+), 121 deletions(-) create mode 100644 web/src/pages/flow/context.ts diff --git a/web/src/components/prompt-editor/index.css b/web/src/components/prompt-editor/index.css index cec1af5105f..2bdb5656ca7 100644 --- a/web/src/components/prompt-editor/index.css +++ b/web/src/components/prompt-editor/index.css @@ -7,10 +7,10 @@ } .typeahead-popover ul { - padding: 0; + /* padding: 0; */ list-style: none; margin: 0; - border-radius: 8px; + /* border-radius: 8px; */ max-height: 200px; overflow-y: scroll; } @@ -39,7 +39,7 @@ .typeahead-popover li { margin: 0 8px 0 8px; - padding: 8px; + /* padding: 8px; */ color: #050505; cursor: pointer; line-height: 16px; @@ -49,7 +49,7 @@ flex-direction: row; flex-shrink: 0; background-color: #fff; - border-radius: 8px; + /* border-radius: 8px; */ border: 0; } @@ -60,17 +60,17 @@ background-size: contain; } -.typeahead-popover li:first-child { +/* .typeahead-popover li:first-child { border-radius: 8px 8px 0px 0px; -} +} */ -.typeahead-popover li:last-child { +/* .typeahead-popover li:last-child { border-radius: 0px 0px 8px 8px; -} +} */ -.typeahead-popover li:hover { +/* .typeahead-popover li:hover { background-color: #eee; -} +} */ .typeahead-popover li .text { display: flex; diff --git a/web/src/components/prompt-editor/index.tsx b/web/src/components/prompt-editor/index.tsx index 48c861d1fb1..13938128754 100644 --- a/web/src/components/prompt-editor/index.tsx +++ b/web/src/components/prompt-editor/index.tsx @@ -4,14 +4,19 @@ import { InitialConfigType, LexicalComposer, } from '@lexical/react/LexicalComposer'; -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; +import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { HeadingNode, QuoteNode } from '@lexical/rich-text'; -import { EditorState, Klass, LexicalNode } from 'lexical'; -import { useEffect, useState } from 'react'; +import { + $getRoot, + $nodesOfType, + EditorState, + Klass, + LexicalNode, +} from 'lexical'; import theme from './theme'; import { VariableNode } from './variable-node'; @@ -24,8 +29,6 @@ function onError(error: Error) { console.error(error); } -type MyOnChangePluginProps = { onChange: (editorState: EditorState) => void }; - const Nodes: Array> = [ HeadingNode, QuoteNode, @@ -34,44 +37,29 @@ const Nodes: Array> = [ VariableNode, ]; -function MyOnChangePlugin({ onChange }: MyOnChangePluginProps) { - const [editor] = useLexicalComposerContext(); - useEffect(() => { - return editor.registerUpdateListener(({ editorState }) => { - onChange(editorState); - }); - }, [editor, onChange]); - return null; -} +type IProps = { + value?: string; + onChange: (value?: string) => void; +}; -export function PromptEditor() { +export function PromptEditor({ value, onChange }: IProps) { const initialConfig: InitialConfigType = { namespace: 'MyEditor', theme, onError, nodes: Nodes, - // html: { import: buildImportMap() }, - - // editorState() { - // const root = $getRoot(); - // if (root.getFirstChild() === null) { - // const quote = $createQuoteNode(); - // quote.append( - // $createTextNode( - // `In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. ` + - // `You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.`, - // ), - // ); - // root.append(quote); - // } - // }, }; - const [editorState, setEditorState] = useState(); - function onChange(editorState: EditorState) { - const editorStateJSON = editorState.toJSON(); - console.log('🚀 ~ onChange ~ editorStateJSON:', editorStateJSON); - setEditorState(editorState); + function onValueChange(editorState: EditorState) { + editorState?.read(() => { + const listNodes = $nodesOfType(VariableNode); + // const allNodes = $dfs(); + console.log('🚀 ~ onChange ~ allNodes:', listNodes); + + const text = $getRoot().getTextContent(); + console.log('🚀 ~ editorState?.read ~ x:', text); + onChange(text); + }); } return ( @@ -87,8 +75,9 @@ export function PromptEditor() { /> - - + {/* */} + + ); } diff --git a/web/src/components/prompt-editor/variable-node.tsx b/web/src/components/prompt-editor/variable-node.tsx index 94ef5d88347..959e58b1b8b 100644 --- a/web/src/components/prompt-editor/variable-node.tsx +++ b/web/src/components/prompt-editor/variable-node.tsx @@ -3,19 +3,21 @@ import { ReactNode } from 'react'; import { Badge } from '../ui/badge'; export class VariableNode extends DecoratorNode { - __id: string; + __value: string; + __label: string; static getType(): string { - return 'video'; + return 'variable'; } static clone(node: VariableNode): VariableNode { - return new VariableNode(node.__id, node.__key); + return new VariableNode(node.__value, node.__label, node.__key); } - constructor(id: string, key?: NodeKey) { + constructor(value: string, label: string, key?: NodeKey) { super(key); - this.__id = id; + this.__value = value; + this.__label = label; } createDOM(): HTMLElement { @@ -30,12 +32,19 @@ export class VariableNode extends DecoratorNode { } decorate(): ReactNode { - return {this.__id}; + return {this.__label}; + } + + getTextContent(): string { + return `{${this.__value}}`; } } -export function $createVariableNode(id: string): VariableNode { - return new VariableNode(id); +export function $createVariableNode( + value: string, + label: string, +): VariableNode { + return new VariableNode(value, label); } export function $isVariableNode( diff --git a/web/src/components/prompt-editor/variable-picker-plugin.tsx b/web/src/components/prompt-editor/variable-picker-plugin.tsx index 8418b49aa85..9eff0646c63 100644 --- a/web/src/components/prompt-editor/variable-picker-plugin.tsx +++ b/web/src/components/prompt-editor/variable-picker-plugin.tsx @@ -12,94 +12,128 @@ import { MenuOption, useBasicTypeaheadTriggerMatch, } from '@lexical/react/LexicalTypeaheadMenuPlugin'; -import { $getSelection, $isRangeSelection, TextNode } from 'lexical'; -import { useCallback, useState } from 'react'; +import { + $createParagraphNode, + $createTextNode, + $getRoot, + $getSelection, + $isRangeSelection, + TextNode, +} from 'lexical'; +import { + ReactElement, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from 'react'; import * as ReactDOM from 'react-dom'; -import './index.css'; +import { FlowFormContext } from '@/pages/flow/context'; +import { useBuildComponentIdSelectOptions } from '@/pages/flow/hooks/use-get-begin-query'; import { $createVariableNode } from './variable-node'; -type VariableOptionType = { - key: string; - label: string; - value: string; -} & MenuOption; - -class VariableOption extends MenuOption { +import './index.css'; +class VariableInnerOption extends MenuOption { label: string; value: string; constructor(label: string, value: string) { - super(label); + super(value); this.label = label; this.value = value; } } +class VariableOption extends MenuOption { + label: ReactElement | string; + title: string; + options: VariableInnerOption[]; + + constructor( + label: ReactElement | string, + title: string, + options: VariableInnerOption[], + ) { + super(title); + this.label = label; + this.title = title; + this.options = options; + } +} + function VariablePickerMenuItem({ index, - isSelected, - onClick, - onMouseEnter, option, + selectOptionAndCleanUp, }: { index: number; - isSelected: boolean; - onClick: () => void; - onMouseEnter: () => void; - option: VariableOptionType; + option: VariableOption; + selectOptionAndCleanUp: (option: VariableOption) => void; }) { - let className = 'item'; - if (isSelected) { - className += ' selected'; - } return (
  • - {/* {option.icon} */} - {option.label} +
    + {option.title} +
      + {option.options.map((x) => ( +
    • selectOptionAndCleanUp(x)} + className="hover:bg-slate-300 p-1" + > + {x.label} +
    • + ))} +
    +
  • ); } -export default function VariablePickerMenuPlugin(): JSX.Element { +export default function VariablePickerMenuPlugin({ + value, +}: { + value?: string; +}): JSX.Element { const [editor] = useLexicalComposerContext(); const [queryString, setQueryString] = useState(null); + const isFirstRender = useRef(true); + + const node = useContext(FlowFormContext); const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', { minLength: 0, }); - const options: VariableOptionType[] = [ - { - value: 'afc163', - label: 'afc163', - }, - { - value: 'zombieJ', - label: 'zombieJ', - }, - { - value: 'yesmeck', - label: 'yesmeck', - }, - ].map((x) => new VariableOption(x.label, x.value)); + const options = useBuildComponentIdSelectOptions(node?.id, node?.parentId); + + const nextOptions: VariableOption[] = options.map( + (x) => + new VariableOption( + x.label, + x.title, + x.options.map((y) => new VariableInnerOption(y.label, y.value)), + ), + ); const onSelectOption = useCallback( ( - selectedOption: VariableOptionType, + selectedOption: VariableOption, nodeToRemove: TextNode | null, closeMenu: () => void, ) => { + console.log( + '🚀 ~ VariablePickerMenuPlugin ~ selectedOption:', + selectedOption, + ); editor.update(() => { const selection = $getSelection(); @@ -111,7 +145,9 @@ export default function VariablePickerMenuPlugin(): JSX.Element { nodeToRemove.remove(); } - selection.insertNodes([$createVariableNode(selectedOption.value)]); + selection.insertNodes([ + $createVariableNode(selectedOption.value, selectedOption.label), + ]); closeMenu(); }); @@ -119,34 +155,38 @@ export default function VariablePickerMenuPlugin(): JSX.Element { [editor], ); + useEffect(() => { + if (editor && value && isFirstRender.current) { + isFirstRender.current = false; + editor.update(() => { + const paragraph = $createParagraphNode(); + const textNode = $createTextNode(value); + + paragraph.append(textNode); + + $getRoot().clear().append(paragraph); + }); + } + }, [editor, value]); + return ( <> - + onQueryChange={setQueryString} onSelectOption={onSelectOption} triggerFn={checkForTriggerMatch} - options={options} - menuRenderFn={( - anchorElementRef, - { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, - ) => + options={nextOptions} + menuRenderFn={(anchorElementRef, { selectOptionAndCleanUp }) => anchorElementRef.current && options.length ? ReactDOM.createPortal( -
    +
      - {options.map((option, i: number) => ( + {nextOptions.map((option, i: number) => ( { - setHighlightedIndex(i); - selectOptionAndCleanUp(option); - }} - onMouseEnter={() => { - setHighlightedIndex(i); - }} key={option.key} option={option} + selectOptionAndCleanUp={selectOptionAndCleanUp} /> ))}
    diff --git a/web/src/pages/flow/context.ts b/web/src/pages/flow/context.ts new file mode 100644 index 00000000000..fe51d8d6dac --- /dev/null +++ b/web/src/pages/flow/context.ts @@ -0,0 +1,6 @@ +import { RAGFlowNodeType } from '@/interfaces/database/flow'; +import { createContext } from 'react'; + +export const FlowFormContext = createContext( + undefined, +); diff --git a/web/src/pages/flow/flow-drawer/index.tsx b/web/src/pages/flow/flow-drawer/index.tsx index 487e746be29..a4adc02bf5f 100644 --- a/web/src/pages/flow/flow-drawer/index.tsx +++ b/web/src/pages/flow/flow-drawer/index.tsx @@ -44,6 +44,7 @@ import { getDrawerWidth, needsSingleStepDebugging } from '../utils'; import SingleDebugDrawer from './single-debug-drawer'; import { RAGFlowNodeType } from '@/interfaces/database/flow'; +import { FlowFormContext } from '../context'; import { RunTooltip } from '../flow-tooltip'; import IterationForm from '../form/iteration-from'; import styles from './index.less'; @@ -176,11 +177,13 @@ const FormDrawer = ({ >
    {visible && ( - + + + )}
    {singleDebugDrawerVisible && ( diff --git a/web/src/pages/flow/utils.ts b/web/src/pages/flow/utils.ts index 5ce73247b9c..12f18e7775a 100644 --- a/web/src/pages/flow/utils.ts +++ b/web/src/pages/flow/utils.ts @@ -389,3 +389,13 @@ export const generateDuplicateNode = ( dragHandle: getNodeDragHandle(label), }; }; + +export function splitTextToArray(text?: string) { + if (text) { + const separators = text.match(/\{.+\}/g); + + separators?.forEach((separator) => {}); + } + + return []; +} From 0f6a9e8d2ceb1a58832800550a7cfaa9f5a933d2 Mon Sep 17 00:00:00 2001 From: bill Date: Wed, 5 Feb 2025 15:11:17 +0800 Subject: [PATCH 03/10] Feat: parse text to variable nodes --- web/src/components/prompt-editor/index.tsx | 4 +- .../prompt-editor/variable-picker-plugin.tsx | 70 ++++++++++++++++--- web/src/pages/flow/utils.ts | 10 --- 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/web/src/components/prompt-editor/index.tsx b/web/src/components/prompt-editor/index.tsx index 13938128754..333db1c672c 100644 --- a/web/src/components/prompt-editor/index.tsx +++ b/web/src/components/prompt-editor/index.tsx @@ -39,7 +39,7 @@ const Nodes: Array> = [ type IProps = { value?: string; - onChange: (value?: string) => void; + onChange?: (value?: string) => void; }; export function PromptEditor({ value, onChange }: IProps) { @@ -58,7 +58,7 @@ export function PromptEditor({ value, onChange }: IProps) { const text = $getRoot().getTextContent(); console.log('🚀 ~ editorState?.read ~ x:', text); - onChange(text); + onChange?.(text); }); } diff --git a/web/src/components/prompt-editor/variable-picker-plugin.tsx b/web/src/components/prompt-editor/variable-picker-plugin.tsx index 9eff0646c63..a9a549b6980 100644 --- a/web/src/components/prompt-editor/variable-picker-plugin.tsx +++ b/web/src/components/prompt-editor/variable-picker-plugin.tsx @@ -124,6 +124,20 @@ export default function VariablePickerMenuPlugin({ ), ); + const findLabelByValue = useCallback( + (value: string) => { + const children = options.reduce>( + (pre, cur) => { + return pre.concat(cur.options); + }, + [], + ); + + return children.find((x) => x.value === value)?.label; + }, + [options], + ); + const onSelectOption = useCallback( ( selectedOption: VariableOption, @@ -146,7 +160,10 @@ export default function VariablePickerMenuPlugin({ } selection.insertNodes([ - $createVariableNode(selectedOption.value, selectedOption.label), + $createVariableNode( + selectedOption.value, + selectedOption.label as string, + ), ]); closeMenu(); @@ -155,19 +172,56 @@ export default function VariablePickerMenuPlugin({ [editor], ); + const parseTextToVariableNodes = useCallback( + (text: string) => { + const paragraph = $createParagraphNode(); + + // Regular expression to match content within {} + const regex = /{([^}]*)}/g; + let match; + let lastIndex = 0; + + while ((match = regex.exec(text)) !== null) { + const { 1: content, index, 0: template } = match; + + // Add the previous text part (if any) + if (index > lastIndex) { + const textNode = $createTextNode(text.slice(lastIndex, index)); + + paragraph.append(textNode); + } + + // Add variable node or text node + const label = findLabelByValue(content); + if (label) { + paragraph.append($createVariableNode(content, label)); + } else { + paragraph.append($createTextNode(template)); + } + + // Update index + lastIndex = regex.lastIndex; + } + + // Add the last part of text (if any) + if (lastIndex < text.length) { + const textNode = $createTextNode(text.slice(lastIndex)); + paragraph.append(textNode); + } + + $getRoot().clear().append(paragraph); + }, + [findLabelByValue], + ); + useEffect(() => { if (editor && value && isFirstRender.current) { isFirstRender.current = false; editor.update(() => { - const paragraph = $createParagraphNode(); - const textNode = $createTextNode(value); - - paragraph.append(textNode); - - $getRoot().clear().append(paragraph); + parseTextToVariableNodes(value); }); } - }, [editor, value]); + }, [parseTextToVariableNodes, editor, value]); return ( <> diff --git a/web/src/pages/flow/utils.ts b/web/src/pages/flow/utils.ts index 12f18e7775a..5ce73247b9c 100644 --- a/web/src/pages/flow/utils.ts +++ b/web/src/pages/flow/utils.ts @@ -389,13 +389,3 @@ export const generateDuplicateNode = ( dragHandle: getNodeDragHandle(label), }; }; - -export function splitTextToArray(text?: string) { - if (text) { - const separators = text.match(/\{.+\}/g); - - separators?.forEach((separator) => {}); - } - - return []; -} From 2f05d50d3ddf05d1983f664e9944f320b40def2b Mon Sep 17 00:00:00 2001 From: bill Date: Wed, 5 Feb 2025 17:05:15 +0800 Subject: [PATCH 04/10] Feat: Remove useless code --- web/src/components/prompt-editor/index.css | 16 ----- web/src/components/prompt-editor/index.tsx | 10 +-- .../prompt-editor/variable-node.tsx | 20 +++++- .../prompt-editor/variable-picker-plugin.tsx | 68 +++++++++---------- 4 files changed, 55 insertions(+), 59 deletions(-) diff --git a/web/src/components/prompt-editor/index.css b/web/src/components/prompt-editor/index.css index 2bdb5656ca7..8f305064721 100644 --- a/web/src/components/prompt-editor/index.css +++ b/web/src/components/prompt-editor/index.css @@ -7,10 +7,8 @@ } .typeahead-popover ul { - /* padding: 0; */ list-style: none; margin: 0; - /* border-radius: 8px; */ max-height: 200px; overflow-y: scroll; } @@ -39,7 +37,6 @@ .typeahead-popover li { margin: 0 8px 0 8px; - /* padding: 8px; */ color: #050505; cursor: pointer; line-height: 16px; @@ -49,7 +46,6 @@ flex-direction: row; flex-shrink: 0; background-color: #fff; - /* border-radius: 8px; */ border: 0; } @@ -60,18 +56,6 @@ background-size: contain; } -/* .typeahead-popover li:first-child { - border-radius: 8px 8px 0px 0px; -} */ - -/* .typeahead-popover li:last-child { - border-radius: 0px 0px 8px 8px; -} */ - -/* .typeahead-popover li:hover { - background-color: #eee; -} */ - .typeahead-popover li .text { display: flex; line-height: 20px; diff --git a/web/src/components/prompt-editor/index.tsx b/web/src/components/prompt-editor/index.tsx index 333db1c672c..695d1004740 100644 --- a/web/src/components/prompt-editor/index.tsx +++ b/web/src/components/prompt-editor/index.tsx @@ -18,6 +18,7 @@ import { LexicalNode, } from 'lexical'; +import { useTranslation } from 'react-i18next'; import theme from './theme'; import { VariableNode } from './variable-node'; import VariablePickerMenuPlugin from './variable-picker-plugin'; @@ -43,8 +44,9 @@ type IProps = { }; export function PromptEditor({ value, onChange }: IProps) { + const { t } = useTranslation(); const initialConfig: InitialConfigType = { - namespace: 'MyEditor', + namespace: 'PromptEditor', theme, onError, nodes: Nodes, @@ -52,12 +54,11 @@ export function PromptEditor({ value, onChange }: IProps) { function onValueChange(editorState: EditorState) { editorState?.read(() => { - const listNodes = $nodesOfType(VariableNode); + const listNodes = $nodesOfType(VariableNode); // to be removed // const allNodes = $dfs(); console.log('🚀 ~ onChange ~ allNodes:', listNodes); const text = $getRoot().getTextContent(); - console.log('🚀 ~ editorState?.read ~ x:', text); onChange?.(text); }); } @@ -69,13 +70,12 @@ export function PromptEditor({ value, onChange }: IProps) { } placeholder={ -
    Enter some text...
    +
    {t('common.pleaseInput')}
    } ErrorBoundary={LexicalErrorBoundary} /> - {/* */} diff --git a/web/src/components/prompt-editor/variable-node.tsx b/web/src/components/prompt-editor/variable-node.tsx index 959e58b1b8b..e2a8cc29f93 100644 --- a/web/src/components/prompt-editor/variable-node.tsx +++ b/web/src/components/prompt-editor/variable-node.tsx @@ -1,6 +1,8 @@ +import i18n from '@/locales/config'; +import { BeginId } from '@/pages/flow/constant'; import { DecoratorNode, LexicalNode, NodeKey } from 'lexical'; import { ReactNode } from 'react'; -import { Badge } from '../ui/badge'; +const prefix = BeginId + '@'; export class VariableNode extends DecoratorNode { __value: string; @@ -32,7 +34,21 @@ export class VariableNode extends DecoratorNode { } decorate(): ReactNode { - return {this.__label}; + let content: ReactNode = ( + {this.__label} + ); + if (this.__value.startsWith(prefix)) { + content = ( +
    + {i18n.t(`flow.begin`)} / {content} +
    + ); + } + return ( +
    + {content} +
    + ); } getTextContent(): string { diff --git a/web/src/components/prompt-editor/variable-picker-plugin.tsx b/web/src/components/prompt-editor/variable-picker-plugin.tsx index a9a549b6980..4a30ad3bcdf 100644 --- a/web/src/components/prompt-editor/variable-picker-plugin.tsx +++ b/web/src/components/prompt-editor/variable-picker-plugin.tsx @@ -26,7 +26,6 @@ import { useContext, useEffect, useRef, - useState, } from 'react'; import * as ReactDOM from 'react-dom'; @@ -70,7 +69,9 @@ function VariablePickerMenuItem({ }: { index: number; option: VariableOption; - selectOptionAndCleanUp: (option: VariableOption) => void; + selectOptionAndCleanUp: ( + option: VariableOption | VariableInnerOption, + ) => void; }) { return (
  • (null); const isFirstRender = useRef(true); const node = useContext(FlowFormContext); @@ -113,6 +113,8 @@ export default function VariablePickerMenuPlugin({ minLength: 0, }); + const setQueryString = useCallback(() => {}, []); + const options = useBuildComponentIdSelectOptions(node?.id, node?.parentId); const nextOptions: VariableOption[] = options.map( @@ -140,14 +142,10 @@ export default function VariablePickerMenuPlugin({ const onSelectOption = useCallback( ( - selectedOption: VariableOption, + selectedOption: VariableOption | VariableInnerOption, nodeToRemove: TextNode | null, closeMenu: () => void, ) => { - console.log( - '🚀 ~ VariablePickerMenuPlugin ~ selectedOption:', - selectedOption, - ); editor.update(() => { const selection = $getSelection(); @@ -161,7 +159,7 @@ export default function VariablePickerMenuPlugin({ selection.insertNodes([ $createVariableNode( - selectedOption.value, + (selectedOption as VariableInnerOption).value, selectedOption.label as string, ), ]); @@ -224,32 +222,30 @@ export default function VariablePickerMenuPlugin({ }, [parseTextToVariableNodes, editor, value]); return ( - <> - - onQueryChange={setQueryString} - onSelectOption={onSelectOption} - triggerFn={checkForTriggerMatch} - options={nextOptions} - menuRenderFn={(anchorElementRef, { selectOptionAndCleanUp }) => - anchorElementRef.current && options.length - ? ReactDOM.createPortal( -
    -
      - {nextOptions.map((option, i: number) => ( - - ))} -
    -
    , - anchorElementRef.current, - ) - : null - } - /> - + + onQueryChange={setQueryString} + onSelectOption={onSelectOption} + triggerFn={checkForTriggerMatch} + options={nextOptions} + menuRenderFn={(anchorElementRef, { selectOptionAndCleanUp }) => + anchorElementRef.current && options.length + ? ReactDOM.createPortal( +
    +
      + {nextOptions.map((option, i: number) => ( + + ))} +
    +
    , + anchorElementRef.current, + ) + : null + } + /> ); } From 7412cd3f00ed9a10bd5b4901071d0a2e2740c2d8 Mon Sep 17 00:00:00 2001 From: bill Date: Fri, 7 Feb 2025 15:56:40 +0800 Subject: [PATCH 05/10] Feat: Remove DynamicParameters from GenerateForm #4764 --- .../pages/flow/canvas/node/generate-node.tsx | 20 ----------- .../pages/flow/form/generate-form/index.tsx | 35 +------------------ .../pages/flow/form/template-form/index.tsx | 10 +++--- 3 files changed, 5 insertions(+), 60 deletions(-) diff --git a/web/src/pages/flow/canvas/node/generate-node.tsx b/web/src/pages/flow/canvas/node/generate-node.tsx index 01b4829fdba..255eccd993a 100644 --- a/web/src/pages/flow/canvas/node/generate-node.tsx +++ b/web/src/pages/flow/canvas/node/generate-node.tsx @@ -2,11 +2,8 @@ import LLMLabel from '@/components/llm-select/llm-label'; import { useTheme } from '@/components/theme-provider'; import { IGenerateNode } from '@/interfaces/database/flow'; import { Handle, NodeProps, Position } from '@xyflow/react'; -import { Flex } from 'antd'; import classNames from 'classnames'; import { get } from 'lodash'; -import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query'; -import { IGenerateParameter } from '../../interface'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; @@ -17,8 +14,6 @@ export function GenerateNode({ isConnectable = true, selected, }: NodeProps) { - const parameters: IGenerateParameter[] = get(data, 'form.parameters', []); - const getLabel = useGetComponentLabelByValue(id); const { theme } = useTheme(); return (
  • - - {parameters.map((x) => ( - - - - {getLabel(x.component_id)} - - - ))} - ); } diff --git a/web/src/pages/flow/form/generate-form/index.tsx b/web/src/pages/flow/form/generate-form/index.tsx index 4e70e80d913..076ed25180e 100644 --- a/web/src/pages/flow/form/generate-form/index.tsx +++ b/web/src/pages/flow/form/generate-form/index.tsx @@ -3,43 +3,11 @@ import MessageHistoryWindowSizeItem from '@/components/message-history-window-si import { PromptEditor } from '@/components/prompt-editor'; import { useTranslate } from '@/hooks/common-hooks'; import { Form, Switch } from 'antd'; -import { useMemo } from 'react'; -import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query'; import { IOperatorForm } from '../../interface'; -import DynamicParameters from './dynamic-parameters'; -const list = [ - { - value: 'afc163', - label: 'afc163', - }, - { - value: 'zombieJ', - label: 'zombieJ', - }, - { - value: 'yesmeck', - label: 'yesmeck', - }, -].map((x) => ({ - ...x, - value: `{${x.value}}`, -})); - -const GenerateForm = ({ onValuesChange, form, node }: IOperatorForm) => { +const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => { const { t } = useTranslate('flow'); - const options = useBuildComponentIdSelectOptions(node?.id, node?.parentId); - - const nextOptions = useMemo(() => { - return options.reduce((pre, cur) => { - cur.options.forEach((x) => { - pre.push({ ...x, value: `{${x.value}}` }); - }); - return pre; - }, []); - }, [options]); - return ( { - ); }; diff --git a/web/src/pages/flow/form/template-form/index.tsx b/web/src/pages/flow/form/template-form/index.tsx index afe7555832e..ddf5c7883a4 100644 --- a/web/src/pages/flow/form/template-form/index.tsx +++ b/web/src/pages/flow/form/template-form/index.tsx @@ -1,9 +1,9 @@ -import { Form, Input } from 'antd'; +import { PromptEditor } from '@/components/prompt-editor'; +import { Form } from 'antd'; import { useTranslation } from 'react-i18next'; import { IOperatorForm } from '../../interface'; -import DynamicParameters from '../generate-form/dynamic-parameters'; -const TemplateForm = ({ onValuesChange, form, node }: IOperatorForm) => { +const TemplateForm = ({ onValuesChange, form }: IOperatorForm) => { const { t } = useTranslation(); return ( @@ -15,10 +15,8 @@ const TemplateForm = ({ onValuesChange, form, node }: IOperatorForm) => { layout={'vertical'} > - + - - ); }; From c80daf2920306fc0bc9f0e5d62a569e1854f93ce Mon Sep 17 00:00:00 2001 From: bill Date: Fri, 7 Feb 2025 15:57:35 +0800 Subject: [PATCH 06/10] Feat: Set jsMinifier to none --- web/.umirc.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/.umirc.ts b/web/.umirc.ts index 75a8bb505fe..62df036ecc6 100644 --- a/web/.umirc.ts +++ b/web/.umirc.ts @@ -11,7 +11,7 @@ export default defineConfig({ base: '/', routes, publicPath: '/', - esbuildMinifyIIFE: true, + // esbuildMinifyIIFE: true, icons: {}, hash: true, favicons: ['/logo.svg'], @@ -23,7 +23,8 @@ export default defineConfig({ '@react-dev-inspector/umi4-plugin', '@umijs/plugins/dist/tailwindcss', ], - jsMinifier: 'terser', + jsMinifier: 'none', + // cssMinifier: 'cssnano', lessLoader: { modifyVars: { hack: `true; @import "~@/less/index.less";`, From 15a4b83db4794a5932df7c324177ebefd0b5b296 Mon Sep 17 00:00:00 2001 From: bill Date: Fri, 7 Feb 2025 18:51:50 +0800 Subject: [PATCH 07/10] Feat: Fixed the issue that the page displayed an error after packaging lexical with terser --- web/.umirc.ts | 8 ++- web/package-lock.json | 148 ++++++++++++++++++++++++++++++++++++------ web/package.json | 1 + 3 files changed, 135 insertions(+), 22 deletions(-) diff --git a/web/.umirc.ts b/web/.umirc.ts index 62df036ecc6..cae37519ddd 100644 --- a/web/.umirc.ts +++ b/web/.umirc.ts @@ -1,4 +1,5 @@ import path from 'path'; +import TerserPlugin from 'terser-webpack-plugin'; import { defineConfig } from 'umi'; import { appName } from './src/conf.json'; import routes from './src/routes'; @@ -11,7 +12,7 @@ export default defineConfig({ base: '/', routes, publicPath: '/', - // esbuildMinifyIIFE: true, + esbuildMinifyIIFE: true, icons: {}, hash: true, favicons: ['/logo.svg'], @@ -23,8 +24,7 @@ export default defineConfig({ '@react-dev-inspector/umi4-plugin', '@umijs/plugins/dist/tailwindcss', ], - jsMinifier: 'none', - // cssMinifier: 'cssnano', + jsMinifier: 'none', // Fixed the issue that the page displayed an error after packaging lexical with terser lessLoader: { modifyVars: { hack: `true; @import "~@/less/index.less";`, @@ -49,6 +49,8 @@ export default defineConfig({ chainWebpack(memo, args) { memo.module.rule('markdown').test(/\.md$/).type('asset/source'); + memo.optimization.minimizer('terser').use(TerserPlugin); // Fixed the issue that the page displayed an error after packaging lexical with terser + return memo; }, tailwindcss: {}, diff --git a/web/package-lock.json b/web/package-lock.json index d1b1247d924..d4b3af06e04 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -119,6 +119,7 @@ "react-dev-inspector": "^2.0.1", "remark-loader": "^6.0.0", "tailwindcss": "^3", + "terser-webpack-plugin": "^5.3.11", "ts-node": "^10.9.2", "typescript": "^5.0.3", "umi-plugin-icons": "^0.1.1" @@ -9615,6 +9616,45 @@ "uri-js": "^4.2.2" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -14823,6 +14863,22 @@ "node": ">=6" } }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmmirror.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -27276,7 +27332,6 @@ "version": "2.0.2", "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -27667,10 +27722,10 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "peer": true, + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -29257,9 +29312,10 @@ } }, "node_modules/terser": { - "version": "5.26.0", - "resolved": "https://registry.npmmirror.com/terser/-/terser-5.26.0.tgz", - "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "version": "5.38.1", + "resolved": "https://registry.npmmirror.com/terser/-/terser-5.38.1.tgz", + "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==", + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -29274,20 +29330,24 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", - "peer": true, + "version": "5.3.11", + "resolved": "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, "peerDependencies": { "webpack": "^5.1.0" }, @@ -29303,11 +29363,38 @@ } } }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, "node_modules/terser-webpack-plugin/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, "engines": { "node": ">=8" } @@ -29316,7 +29403,6 @@ "version": "27.5.1", "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -29326,11 +29412,35 @@ "node": ">= 10.13.0" } }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, diff --git a/web/package.json b/web/package.json index 1ab894f5ee2..cd4984e3412 100644 --- a/web/package.json +++ b/web/package.json @@ -130,6 +130,7 @@ "react-dev-inspector": "^2.0.1", "remark-loader": "^6.0.0", "tailwindcss": "^3", + "terser-webpack-plugin": "^5.3.11", "ts-node": "^10.9.2", "typescript": "^5.0.3", "umi-plugin-icons": "^0.1.1" From 003c6a4912b73803656f6b5441aaa9b6004c1855 Mon Sep 17 00:00:00 2001 From: bill Date: Sat, 8 Feb 2025 14:07:41 +0800 Subject: [PATCH 08/10] Feat: Remove the restriction that the rewrite operator cannot connect to downstream classification operators --- web/src/pages/flow/constant.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/pages/flow/constant.tsx b/web/src/pages/flow/constant.tsx index a44511ca590..05580f84733 100644 --- a/web/src/pages/flow/constant.tsx +++ b/web/src/pages/flow/constant.tsx @@ -671,7 +671,6 @@ export const RestrictedUpstreamMap = { Operator.Message, Operator.Generate, Operator.RewriteQuestion, - Operator.Categorize, Operator.Relevant, ], [Operator.KeywordExtract]: [ From 15205804c5c4c951a4b5eae5e51509dab9797532 Mon Sep 17 00:00:00 2001 From: bill Date: Sat, 8 Feb 2025 14:41:15 +0800 Subject: [PATCH 09/10] Feat: Set style of Similarity badge #3221 --- web/src/pages/dataset/testing/index.tsx | 38 ++++++++++++------------- web/tailwind.config.js | 1 + web/tailwind.css | 2 ++ 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/web/src/pages/dataset/testing/index.tsx b/web/src/pages/dataset/testing/index.tsx index 1aab3572bac..992ff02437a 100644 --- a/web/src/pages/dataset/testing/index.tsx +++ b/web/src/pages/dataset/testing/index.tsx @@ -6,6 +6,12 @@ const list = new Array(15).fill({ content: `Lorem ipsum odor amet, consectetuer adipiscing elit. Ullamcorper vulputate id laoreet malesuada commodo molestie. Lectus convallis class euismod; consequat in curabitur. Ablandit praesent inceptos nibh placerat lectus fringilla finibus. Hac vivamus id scelerisque et gravida nec ligula et non. Consectetur eu himenaeos eget felis quis habitant tellus. Tellus commodo inceptos litora habitant per himenaeos faucibus pretium. Gravida velit pretium amet purus rhoncus taciti. `, }); +const SimilarityList = [ + { label: '混合相似度', value: 45.88 }, + { label: '关键词似度', value: 45.88 }, + { label: '向量相似度', value: 45.88 }, +]; + export default function RetrievalTesting() { return (
    @@ -13,7 +19,7 @@ export default function RetrievalTesting() {
    -

    +

    15 Results from 3 files

    @@ -25,24 +31,18 @@ export default function RetrievalTesting() {
    - - 混合相似度 45.88 - - - 关键词似度 45.88 - - - 向量相似度 45.88 - + {SimilarityList.map((x, idx) => ( + + {x.label} + + {x.value} + + + ))}
    diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 94df6b51ce8..0b0ff1d2cd3 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -38,6 +38,7 @@ module.exports = { 'colors-text-functional-danger': 'var(--colors-text-functional-danger)', 'colors-text-inverse-strong': 'var(--colors-text-inverse-strong)', 'colors-text-persist-light': 'var(--colors-text-persist-light)', + 'colors-text-inverse-weak': 'var(--colors-text-inverse-weak)', primary: { DEFAULT: 'hsl(var(--primary))', diff --git a/web/tailwind.css b/web/tailwind.css index de3c6638727..54af4ba532b 100644 --- a/web/tailwind.css +++ b/web/tailwind.css @@ -57,6 +57,7 @@ --colors-text-functional-danger: rgba(255, 81, 81, 1); --colors-text-inverse-strong: rgba(255, 255, 255, 1); --colors-text-persist-light: rgba(255, 255, 255, 1); + --colors-text-inverse-weak: rgba(184, 181, 203, 1); } .dark { @@ -138,6 +139,7 @@ --colors-text-functional-danger: rgba(255, 81, 81, 1); --colors-text-inverse-strong: rgba(17, 16, 23, 1); --colors-text-persist-light: rgba(255, 255, 255, 1); + --colors-text-inverse-weak: rgba(84, 80, 106, 1); } } From 334cc754583b487a5c03c6cf60c1ed30e623c1f2 Mon Sep 17 00:00:00 2001 From: bill Date: Sat, 8 Feb 2025 16:08:57 +0800 Subject: [PATCH 10/10] Fix: Fixed the issue where switching between two operator models would cause the classification node connection to be lost --- web/src/pages/flow/flow-drawer/index.tsx | 23 +++++++-- .../pages/flow/form/categorize-form/hooks.ts | 50 ++----------------- web/src/pages/flow/utils.ts | 27 ++++++++++ 3 files changed, 50 insertions(+), 50 deletions(-) diff --git a/web/src/pages/flow/flow-drawer/index.tsx b/web/src/pages/flow/flow-drawer/index.tsx index a4adc02bf5f..3dc822be0af 100644 --- a/web/src/pages/flow/flow-drawer/index.tsx +++ b/web/src/pages/flow/flow-drawer/index.tsx @@ -2,7 +2,7 @@ import { useTranslate } from '@/hooks/common-hooks'; import { IModalProps } from '@/interfaces/common'; import { CloseOutlined } from '@ant-design/icons'; import { Drawer, Flex, Form, Input } from 'antd'; -import { lowerFirst } from 'lodash'; +import { get, isPlainObject, lowerFirst } from 'lodash'; import { Play } from 'lucide-react'; import { useEffect, useRef } from 'react'; import { BeginId, Operator, operatorMap } from '../constant'; @@ -40,7 +40,11 @@ import WikipediaForm from '../form/wikipedia-form'; import YahooFinanceForm from '../form/yahoo-finance-form'; import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks'; import OperatorIcon from '../operator-icon'; -import { getDrawerWidth, needsSingleStepDebugging } from '../utils'; +import { + buildCategorizeListFromObject, + getDrawerWidth, + needsSingleStepDebugging, +} from '../utils'; import SingleDebugDrawer from './single-debug-drawer'; import { RAGFlowNodeType } from '@/interfaces/database/flow'; @@ -123,10 +127,21 @@ const FormDrawer = ({ if (node?.id !== previousId.current) { form.resetFields(); } - form.setFieldsValue(node?.data?.form); + + if (operatorName === Operator.Categorize) { + const items = buildCategorizeListFromObject( + get(node, 'data.form.category_description', {}), + ); + const formData = node?.data?.form; + if (isPlainObject(formData)) { + form.setFieldsValue({ ...formData, items }); + } + } else { + form.setFieldsValue(node?.data?.form); + } previousId.current = node?.id; } - }, [visible, form, node?.data?.form, node?.id]); + }, [visible, form, node?.data?.form, node?.id, node, operatorName]); return ( { - // Categorize's to field has two data sources, with edges as the data source. - // Changes in the edge or to field need to be synchronized to the form field. - return Object.keys(categorizeItem) - .reduce>((pre, cur) => { - // synchronize edge data to the to field - - pre.push({ name: cur, ...categorizeItem[cur] }); - return pre; - }, []) - .sort((a, b) => a.index - b.index); -}; +} from '@/interfaces/database/flow'; +import omit from 'lodash/omit'; +import { useCallback } from 'react'; +import { IOperatorForm } from '../../interface'; /** * Convert the list in the following form into an object @@ -58,12 +30,7 @@ const buildCategorizeObjectFromList = (list: Array) => { export const useHandleFormValuesChange = ({ onValuesChange, - form, - nodeId, }: IOperatorForm) => { - const getNode = useGraphStore((state) => state.getNode); - const node = getNode(nodeId); - const handleValuesChange = useCallback( (changedValues: any, values: any) => { onValuesChange?.(changedValues, { @@ -74,14 +41,5 @@ export const useHandleFormValuesChange = ({ [onValuesChange], ); - useEffect(() => { - const items = buildCategorizeListFromObject( - get(node, 'data.form.category_description', {}), - ); - form?.setFieldsValue({ - items, - }); - }, [form, node]); - return { handleValuesChange }; }; diff --git a/web/src/pages/flow/utils.ts b/web/src/pages/flow/utils.ts index 5ce73247b9c..dcd0867dd11 100644 --- a/web/src/pages/flow/utils.ts +++ b/web/src/pages/flow/utils.ts @@ -1,5 +1,6 @@ import { DSLComponents, + ICategorizeItem, ICategorizeItemResult, RAGFlowNodeType, } from '@/interfaces/database/flow'; @@ -389,3 +390,29 @@ export const generateDuplicateNode = ( dragHandle: getNodeDragHandle(label), }; }; + +/** + * convert the following object into a list + * + * { + "product_related": { + "description": "The question is about product usage, appearance and how it works.", + "examples": "Why it always beaming?\nHow to install it onto the wall?\nIt leaks, what to do?", + "to": "generate:0" + } + } +*/ +export const buildCategorizeListFromObject = ( + categorizeItem: ICategorizeItemResult, +) => { + // Categorize's to field has two data sources, with edges as the data source. + // Changes in the edge or to field need to be synchronized to the form field. + return Object.keys(categorizeItem) + .reduce>((pre, cur) => { + // synchronize edge data to the to field + + pre.push({ name: cur, ...categorizeItem[cur] }); + return pre; + }, []) + .sort((a, b) => a.index - b.index); +};