### What problem does this PR solve? Feat: Display agent operator call log #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.1
| @@ -38,6 +38,8 @@ | |||
| "@radix-ui/react-switch": "^1.1.1", | |||
| "@radix-ui/react-tabs": "^1.1.1", | |||
| "@radix-ui/react-toast": "^1.2.6", | |||
| "@radix-ui/react-toggle": "^1.1.9", | |||
| "@radix-ui/react-toggle-group": "^1.1.10", | |||
| "@radix-ui/react-tooltip": "^1.1.4", | |||
| "@tailwindcss/line-clamp": "^0.4.4", | |||
| "@tanstack/react-query": "^5.40.0", | |||
| @@ -7591,6 +7593,372 @@ | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle": { | |||
| "version": "1.1.9", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-toggle/-/react-toggle-1.1.9.tgz", | |||
| "integrity": "sha512-ZoFkBBz9zv9GWer7wIjvdRxmh2wyc2oKWw6C6CseWd6/yq1DK/l5lJ+wnsmFwJZbBYqr02mrf8A2q/CVCuM3ZA==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/primitive": "1.1.2", | |||
| "@radix-ui/react-primitive": "2.1.3", | |||
| "@radix-ui/react-use-controllable-state": "1.2.2" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "@types/react-dom": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
| "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| }, | |||
| "@types/react-dom": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group": { | |||
| "version": "1.1.10", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.10.tgz", | |||
| "integrity": "sha512-kiU694Km3WFLTC75DdqgM/3Jauf3rD9wxeS9XtyWFKsBUeZA337lC+6uUazT7I1DhanZ5gyD5Stf8uf2dbQxOQ==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/primitive": "1.1.2", | |||
| "@radix-ui/react-context": "1.1.2", | |||
| "@radix-ui/react-direction": "1.1.1", | |||
| "@radix-ui/react-primitive": "2.1.3", | |||
| "@radix-ui/react-roving-focus": "1.1.10", | |||
| "@radix-ui/react-toggle": "1.1.9", | |||
| "@radix-ui/react-use-controllable-state": "1.2.2" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "@types/react-dom": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
| "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| }, | |||
| "@types/react-dom": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/primitive": { | |||
| "version": "1.1.2", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.2.tgz", | |||
| "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", | |||
| "license": "MIT" | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-collection": { | |||
| "version": "1.1.7", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", | |||
| "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/react-compose-refs": "1.1.2", | |||
| "@radix-ui/react-context": "1.1.2", | |||
| "@radix-ui/react-primitive": "2.1.3", | |||
| "@radix-ui/react-slot": "1.2.3" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "@types/react-dom": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
| "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| }, | |||
| "@types/react-dom": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-compose-refs": { | |||
| "version": "1.1.2", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", | |||
| "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", | |||
| "license": "MIT", | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-context": { | |||
| "version": "1.1.2", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.2.tgz", | |||
| "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", | |||
| "license": "MIT", | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-direction": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", | |||
| "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", | |||
| "license": "MIT", | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-id": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.1.1.tgz", | |||
| "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/react-use-layout-effect": "1.1.1" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-primitive": { | |||
| "version": "2.1.3", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", | |||
| "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/react-slot": "1.2.3" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "@types/react-dom": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
| "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| }, | |||
| "@types/react-dom": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-roving-focus": { | |||
| "version": "1.1.10", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", | |||
| "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/primitive": "1.1.2", | |||
| "@radix-ui/react-collection": "1.1.7", | |||
| "@radix-ui/react-compose-refs": "1.1.2", | |||
| "@radix-ui/react-context": "1.1.2", | |||
| "@radix-ui/react-direction": "1.1.1", | |||
| "@radix-ui/react-id": "1.1.1", | |||
| "@radix-ui/react-primitive": "2.1.3", | |||
| "@radix-ui/react-use-callback-ref": "1.1.1", | |||
| "@radix-ui/react-use-controllable-state": "1.2.2" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "@types/react-dom": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
| "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| }, | |||
| "@types/react-dom": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-slot": { | |||
| "version": "1.2.3", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", | |||
| "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/react-compose-refs": "1.1.2" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-use-callback-ref": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", | |||
| "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", | |||
| "license": "MIT", | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-use-controllable-state": { | |||
| "version": "1.2.2", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", | |||
| "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/react-use-effect-event": "0.0.2", | |||
| "@radix-ui/react-use-layout-effect": "1.1.1" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-use-layout-effect": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", | |||
| "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", | |||
| "license": "MIT", | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/primitive": { | |||
| "version": "1.1.2", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.2.tgz", | |||
| "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", | |||
| "license": "MIT" | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-compose-refs": { | |||
| "version": "1.1.2", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", | |||
| "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", | |||
| "license": "MIT", | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": { | |||
| "version": "2.1.3", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", | |||
| "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/react-slot": "1.2.3" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "@types/react-dom": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
| "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| }, | |||
| "@types/react-dom": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-slot": { | |||
| "version": "1.2.3", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", | |||
| "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/react-compose-refs": "1.1.2" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-controllable-state": { | |||
| "version": "1.2.2", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", | |||
| "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@radix-ui/react-use-effect-event": "0.0.2", | |||
| "@radix-ui/react-use-layout-effect": "1.1.1" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-layout-effect": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", | |||
| "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", | |||
| "license": "MIT", | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-tooltip": { | |||
| "version": "1.1.4", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.4.tgz", | |||
| @@ -49,6 +49,8 @@ | |||
| "@radix-ui/react-switch": "^1.1.1", | |||
| "@radix-ui/react-tabs": "^1.1.1", | |||
| "@radix-ui/react-toast": "^1.2.6", | |||
| "@radix-ui/react-toggle": "^1.1.9", | |||
| "@radix-ui/react-toggle-group": "^1.1.10", | |||
| "@radix-ui/react-tooltip": "^1.1.4", | |||
| "@tailwindcss/line-clamp": "^0.4.4", | |||
| "@tanstack/react-query": "^5.40.0", | |||
| @@ -0,0 +1,51 @@ | |||
| import { Form, Input, Modal } from 'antd'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { IFeedbackRequestBody } from '@/interfaces/request/chat'; | |||
| import { useCallback } from 'react'; | |||
| type FieldType = { | |||
| feedback?: string; | |||
| }; | |||
| const FeedbackModal = ({ | |||
| visible, | |||
| hideModal, | |||
| onOk, | |||
| loading, | |||
| }: IModalProps<IFeedbackRequestBody>) => { | |||
| const [form] = Form.useForm(); | |||
| const handleOk = useCallback(async () => { | |||
| const ret = await form.validateFields(); | |||
| return onOk?.({ thumbup: false, feedback: ret.feedback }); | |||
| }, [onOk, form]); | |||
| return ( | |||
| <Modal | |||
| title="Feedback" | |||
| open={visible} | |||
| onOk={handleOk} | |||
| onCancel={hideModal} | |||
| confirmLoading={loading} | |||
| > | |||
| <Form | |||
| name="basic" | |||
| labelCol={{ span: 0 }} | |||
| wrapperCol={{ span: 24 }} | |||
| style={{ maxWidth: 600 }} | |||
| autoComplete="off" | |||
| form={form} | |||
| > | |||
| <Form.Item<FieldType> | |||
| name="feedback" | |||
| rules={[{ required: true, message: 'Please input your feedback!' }]} | |||
| > | |||
| <Input.TextArea rows={8} placeholder="Please input your feedback!" /> | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| ); | |||
| }; | |||
| export default FeedbackModal; | |||
| @@ -0,0 +1,220 @@ | |||
| import { PromptIcon } from '@/assets/icon/Icon'; | |||
| import CopyToClipboard from '@/components/copy-to-clipboard'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { IRemoveMessageById } from '@/hooks/logic-hooks'; | |||
| import { AgentChatContext } from '@/pages/agent/context'; | |||
| import { | |||
| DeleteOutlined, | |||
| DislikeOutlined, | |||
| LikeOutlined, | |||
| PauseCircleOutlined, | |||
| SoundOutlined, | |||
| SyncOutlined, | |||
| } from '@ant-design/icons'; | |||
| import { Radio, Tooltip } from 'antd'; | |||
| import { NotebookText } from 'lucide-react'; | |||
| import { useCallback, useContext } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group'; | |||
| import FeedbackModal from './feedback-modal'; | |||
| import { useRemoveMessage, useSendFeedback, useSpeech } from './hooks'; | |||
| import PromptModal from './prompt-modal'; | |||
| interface IProps { | |||
| messageId: string; | |||
| content: string; | |||
| prompt?: string; | |||
| showLikeButton: boolean; | |||
| audioBinary?: string; | |||
| showLoudspeaker?: boolean; | |||
| } | |||
| export const AssistantGroupButton = ({ | |||
| messageId, | |||
| content, | |||
| prompt, | |||
| audioBinary, | |||
| showLikeButton, | |||
| showLoudspeaker = true, | |||
| }: IProps) => { | |||
| const { visible, hideModal, showModal, onFeedbackOk, loading } = | |||
| useSendFeedback(messageId); | |||
| const { | |||
| visible: promptVisible, | |||
| hideModal: hidePromptModal, | |||
| showModal: showPromptModal, | |||
| } = useSetModalState(); | |||
| const { t } = useTranslation(); | |||
| const { handleRead, ref, isPlaying } = useSpeech(content, audioBinary); | |||
| const handleLike = useCallback(() => { | |||
| onFeedbackOk({ thumbup: true }); | |||
| }, [onFeedbackOk]); | |||
| const { showLogSheet } = useContext(AgentChatContext); | |||
| const handleShowLogSheet = useCallback(() => { | |||
| showLogSheet(messageId); | |||
| }, [messageId, showLogSheet]); | |||
| return ( | |||
| <> | |||
| <ToggleGroup | |||
| type={'single'} | |||
| size="sm" | |||
| variant="outline" | |||
| className="space-x-1" | |||
| > | |||
| <ToggleGroupItem value="a"> | |||
| <CopyToClipboard text={content}></CopyToClipboard> | |||
| </ToggleGroupItem> | |||
| {showLoudspeaker && ( | |||
| <ToggleGroupItem value="b" onClick={handleRead}> | |||
| <Tooltip title={t('chat.read')}> | |||
| {isPlaying ? <PauseCircleOutlined /> : <SoundOutlined />} | |||
| </Tooltip> | |||
| <audio src="" ref={ref}></audio> | |||
| </ToggleGroupItem> | |||
| )} | |||
| {showLikeButton && ( | |||
| <> | |||
| <ToggleGroupItem value="c" onClick={handleLike}> | |||
| <LikeOutlined /> | |||
| </ToggleGroupItem> | |||
| <ToggleGroupItem value="d" onClick={showModal}> | |||
| <DislikeOutlined /> | |||
| </ToggleGroupItem> | |||
| </> | |||
| )} | |||
| {prompt && ( | |||
| <Radio.Button value="e" onClick={showPromptModal}> | |||
| <PromptIcon style={{ fontSize: '16px' }} /> | |||
| </Radio.Button> | |||
| )} | |||
| <ToggleGroupItem value="f" onClick={handleShowLogSheet}> | |||
| <NotebookText className="size-4" /> | |||
| </ToggleGroupItem> | |||
| </ToggleGroup> | |||
| {visible && ( | |||
| <FeedbackModal | |||
| visible={visible} | |||
| hideModal={hideModal} | |||
| onOk={onFeedbackOk} | |||
| loading={loading} | |||
| ></FeedbackModal> | |||
| )} | |||
| {promptVisible && ( | |||
| <PromptModal | |||
| visible={promptVisible} | |||
| hideModal={hidePromptModal} | |||
| prompt={prompt} | |||
| ></PromptModal> | |||
| )} | |||
| </> | |||
| ); | |||
| return ( | |||
| <> | |||
| <Radio.Group size="small"> | |||
| <Radio.Button value="a"> | |||
| <CopyToClipboard text={content}></CopyToClipboard> | |||
| </Radio.Button> | |||
| {showLoudspeaker && ( | |||
| <Radio.Button value="b" onClick={handleRead}> | |||
| <Tooltip title={t('chat.read')}> | |||
| {isPlaying ? <PauseCircleOutlined /> : <SoundOutlined />} | |||
| </Tooltip> | |||
| <audio src="" ref={ref}></audio> | |||
| </Radio.Button> | |||
| )} | |||
| {showLikeButton && ( | |||
| <> | |||
| <Radio.Button value="c" onClick={handleLike}> | |||
| <LikeOutlined /> | |||
| </Radio.Button> | |||
| <Radio.Button value="d" onClick={showModal}> | |||
| <DislikeOutlined /> | |||
| </Radio.Button> | |||
| </> | |||
| )} | |||
| {prompt && ( | |||
| <Radio.Button value="e" onClick={showPromptModal}> | |||
| <PromptIcon style={{ fontSize: '16px' }} /> | |||
| </Radio.Button> | |||
| )} | |||
| <Radio.Button | |||
| value="f" | |||
| onClick={(e) => { | |||
| e.preventDefault(); | |||
| e.stopPropagation(); | |||
| handleShowLogSheet(); | |||
| }} | |||
| > | |||
| <NotebookText className="size-4" /> | |||
| </Radio.Button> | |||
| </Radio.Group> | |||
| {visible && ( | |||
| <FeedbackModal | |||
| visible={visible} | |||
| hideModal={hideModal} | |||
| onOk={onFeedbackOk} | |||
| loading={loading} | |||
| ></FeedbackModal> | |||
| )} | |||
| {promptVisible && ( | |||
| <PromptModal | |||
| visible={promptVisible} | |||
| hideModal={hidePromptModal} | |||
| prompt={prompt} | |||
| ></PromptModal> | |||
| )} | |||
| </> | |||
| ); | |||
| }; | |||
| interface UserGroupButtonProps extends Partial<IRemoveMessageById> { | |||
| messageId: string; | |||
| content: string; | |||
| regenerateMessage?: () => void; | |||
| sendLoading: boolean; | |||
| } | |||
| export const UserGroupButton = ({ | |||
| content, | |||
| messageId, | |||
| sendLoading, | |||
| removeMessageById, | |||
| regenerateMessage, | |||
| }: UserGroupButtonProps) => { | |||
| const { onRemoveMessage, loading } = useRemoveMessage( | |||
| messageId, | |||
| removeMessageById, | |||
| ); | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Radio.Group size="small"> | |||
| <Radio.Button value="a"> | |||
| <CopyToClipboard text={content}></CopyToClipboard> | |||
| </Radio.Button> | |||
| {regenerateMessage && ( | |||
| <Radio.Button | |||
| value="b" | |||
| onClick={regenerateMessage} | |||
| disabled={sendLoading} | |||
| > | |||
| <Tooltip title={t('chat.regenerate')}> | |||
| <SyncOutlined spin={sendLoading} /> | |||
| </Tooltip> | |||
| </Radio.Button> | |||
| )} | |||
| {removeMessageById && ( | |||
| <Radio.Button value="c" onClick={onRemoveMessage} disabled={loading}> | |||
| <Tooltip title={t('common.delete')}> | |||
| <DeleteOutlined spin={loading} /> | |||
| </Tooltip> | |||
| </Radio.Button> | |||
| )} | |||
| </Radio.Group> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,116 @@ | |||
| import { useDeleteMessage, useFeedback } from '@/hooks/chat-hooks'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { IRemoveMessageById, useSpeechWithSse } from '@/hooks/logic-hooks'; | |||
| import { IFeedbackRequestBody } from '@/interfaces/request/chat'; | |||
| import { hexStringToUint8Array } from '@/utils/common-util'; | |||
| import { SpeechPlayer } from 'openai-speech-stream-player'; | |||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||
| export const useSendFeedback = (messageId: string) => { | |||
| const { visible, hideModal, showModal } = useSetModalState(); | |||
| const { feedback, loading } = useFeedback(); | |||
| const onFeedbackOk = useCallback( | |||
| async (params: IFeedbackRequestBody) => { | |||
| const ret = await feedback({ | |||
| ...params, | |||
| messageId: messageId, | |||
| }); | |||
| if (ret === 0) { | |||
| hideModal(); | |||
| } | |||
| }, | |||
| [feedback, hideModal, messageId], | |||
| ); | |||
| return { | |||
| loading, | |||
| onFeedbackOk, | |||
| visible, | |||
| hideModal, | |||
| showModal, | |||
| }; | |||
| }; | |||
| export const useRemoveMessage = ( | |||
| messageId: string, | |||
| removeMessageById?: IRemoveMessageById['removeMessageById'], | |||
| ) => { | |||
| const { deleteMessage, loading } = useDeleteMessage(); | |||
| const onRemoveMessage = useCallback(async () => { | |||
| if (messageId) { | |||
| const code = await deleteMessage(messageId); | |||
| if (code === 0) { | |||
| removeMessageById?.(messageId); | |||
| } | |||
| } | |||
| }, [deleteMessage, messageId, removeMessageById]); | |||
| return { onRemoveMessage, loading }; | |||
| }; | |||
| export const useSpeech = (content: string, audioBinary?: string) => { | |||
| const ref = useRef<HTMLAudioElement>(null); | |||
| const { read } = useSpeechWithSse(); | |||
| const player = useRef<SpeechPlayer>(); | |||
| const [isPlaying, setIsPlaying] = useState<boolean>(false); | |||
| const initialize = useCallback(async () => { | |||
| player.current = new SpeechPlayer({ | |||
| audio: ref.current!, | |||
| onPlaying: () => { | |||
| setIsPlaying(true); | |||
| }, | |||
| onPause: () => { | |||
| setIsPlaying(false); | |||
| }, | |||
| onChunkEnd: () => {}, | |||
| mimeType: MediaSource.isTypeSupported('audio/mpeg') | |||
| ? 'audio/mpeg' | |||
| : 'audio/mp4; codecs="mp4a.40.2"', // https://stackoverflow.com/questions/64079424/cannot-replay-mp3-in-firefox-using-mediasource-even-though-it-works-in-chrome | |||
| }); | |||
| await player.current.init(); | |||
| }, []); | |||
| const pause = useCallback(() => { | |||
| player.current?.pause(); | |||
| }, []); | |||
| const speech = useCallback(async () => { | |||
| const response = await read({ text: content }); | |||
| if (response) { | |||
| player?.current?.feedWithResponse(response); | |||
| } | |||
| }, [read, content]); | |||
| const handleRead = useCallback(async () => { | |||
| if (isPlaying) { | |||
| setIsPlaying(false); | |||
| pause(); | |||
| } else { | |||
| setIsPlaying(true); | |||
| speech(); | |||
| } | |||
| }, [setIsPlaying, speech, isPlaying, pause]); | |||
| useEffect(() => { | |||
| if (audioBinary) { | |||
| const units = hexStringToUint8Array(audioBinary); | |||
| if (units) { | |||
| try { | |||
| player.current?.feed(units); | |||
| } catch (error) { | |||
| console.warn(error); | |||
| } | |||
| } | |||
| } | |||
| }, [audioBinary]); | |||
| useEffect(() => { | |||
| initialize(); | |||
| }, [initialize]); | |||
| return { ref, handleRead, isPlaying }; | |||
| }; | |||
| @@ -0,0 +1,63 @@ | |||
| .messageItem { | |||
| padding: 24px 0; | |||
| .messageItemSection { | |||
| display: inline-block; | |||
| } | |||
| .messageItemSectionLeft { | |||
| width: 80%; | |||
| } | |||
| .messageItemContent { | |||
| display: inline-flex; | |||
| gap: 20px; | |||
| } | |||
| .messageItemContentReverse { | |||
| flex-direction: row-reverse; | |||
| } | |||
| .messageTextBase() { | |||
| padding: 6px 10px; | |||
| border-radius: 8px; | |||
| & > p { | |||
| margin: 0; | |||
| } | |||
| } | |||
| .messageText { | |||
| .chunkText(); | |||
| .messageTextBase(); | |||
| background-color: #e6f4ff; | |||
| word-break: break-word; | |||
| } | |||
| .messageTextDark { | |||
| .chunkText(); | |||
| .messageTextBase(); | |||
| background-color: #1668dc; | |||
| word-break: break-word; | |||
| :global(section.think) { | |||
| color: rgb(166, 166, 166); | |||
| border-left-color: rgb(78, 78, 86); | |||
| } | |||
| } | |||
| .messageUserText { | |||
| .chunkText(); | |||
| .messageTextBase(); | |||
| background-color: rgba(255, 255, 255, 0.3); | |||
| word-break: break-word; | |||
| text-align: justify; | |||
| } | |||
| .messageEmpty { | |||
| width: 300px; | |||
| } | |||
| .thumbnailImg { | |||
| max-width: 20px; | |||
| } | |||
| } | |||
| .messageItemLeft { | |||
| text-align: left; | |||
| } | |||
| .messageItemRight { | |||
| text-align: right; | |||
| } | |||
| @@ -0,0 +1,244 @@ | |||
| import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg'; | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { IReference, IReferenceChunk } from '@/interfaces/database/chat'; | |||
| import classNames from 'classnames'; | |||
| import { memo, useCallback, useEffect, useMemo, useState } from 'react'; | |||
| import { | |||
| useFetchDocumentInfosByIds, | |||
| useFetchDocumentThumbnailsByIds, | |||
| } from '@/hooks/document-hooks'; | |||
| import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks'; | |||
| import { IMessage } from '@/pages/chat/interface'; | |||
| import MarkdownContent from '@/pages/chat/markdown-content'; | |||
| import { getExtension, isImage } from '@/utils/document-util'; | |||
| import { Avatar, Button, Flex, List, Space, Typography } from 'antd'; | |||
| import FileIcon from '../file-icon'; | |||
| import IndentedTreeModal from '../indented-tree/modal'; | |||
| import NewDocumentLink from '../new-document-link'; | |||
| import { useTheme } from '../theme-provider'; | |||
| import { AssistantGroupButton, UserGroupButton } from './group-button'; | |||
| import styles from './index.less'; | |||
| const { Text } = Typography; | |||
| interface IProps extends Partial<IRemoveMessageById>, IRegenerateMessage { | |||
| item: IMessage; | |||
| reference: IReference; | |||
| loading?: boolean; | |||
| sendLoading?: boolean; | |||
| visibleAvatar?: boolean; | |||
| nickname?: string; | |||
| avatar?: string; | |||
| avatarDialog?: string | null; | |||
| clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void; | |||
| index: number; | |||
| showLikeButton?: boolean; | |||
| showLoudspeaker?: boolean; | |||
| } | |||
| const MessageItem = ({ | |||
| item, | |||
| reference, | |||
| loading = false, | |||
| avatar, | |||
| avatarDialog, | |||
| sendLoading = false, | |||
| clickDocumentButton, | |||
| index, | |||
| removeMessageById, | |||
| regenerateMessage, | |||
| showLikeButton = true, | |||
| showLoudspeaker = true, | |||
| visibleAvatar = true, | |||
| }: IProps) => { | |||
| const { theme } = useTheme(); | |||
| const isAssistant = item.role === MessageType.Assistant; | |||
| const isUser = item.role === MessageType.User; | |||
| const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds(); | |||
| const { data: documentThumbnails, setDocumentIds: setIds } = | |||
| useFetchDocumentThumbnailsByIds(); | |||
| const { visible, hideModal, showModal } = useSetModalState(); | |||
| const [clickedDocumentId, setClickedDocumentId] = useState(''); | |||
| const referenceDocumentList = useMemo(() => { | |||
| return reference?.doc_aggs ?? []; | |||
| }, [reference?.doc_aggs]); | |||
| const handleUserDocumentClick = useCallback( | |||
| (id: string) => () => { | |||
| setClickedDocumentId(id); | |||
| showModal(); | |||
| }, | |||
| [showModal], | |||
| ); | |||
| const handleRegenerateMessage = useCallback(() => { | |||
| regenerateMessage?.(item); | |||
| }, [regenerateMessage, item]); | |||
| useEffect(() => { | |||
| const ids = item?.doc_ids ?? []; | |||
| if (ids.length) { | |||
| setDocumentIds(ids); | |||
| const documentIds = ids.filter((x) => !(x in documentThumbnails)); | |||
| if (documentIds.length) { | |||
| setIds(documentIds); | |||
| } | |||
| } | |||
| }, [item.doc_ids, setDocumentIds, setIds, documentThumbnails]); | |||
| return ( | |||
| <div | |||
| className={classNames(styles.messageItem, { | |||
| [styles.messageItemLeft]: item.role === MessageType.Assistant, | |||
| [styles.messageItemRight]: item.role === MessageType.User, | |||
| })} | |||
| > | |||
| <section | |||
| className={classNames(styles.messageItemSection, { | |||
| [styles.messageItemSectionLeft]: item.role === MessageType.Assistant, | |||
| [styles.messageItemSectionRight]: item.role === MessageType.User, | |||
| })} | |||
| > | |||
| <div | |||
| className={classNames(styles.messageItemContent, { | |||
| [styles.messageItemContentReverse]: item.role === MessageType.User, | |||
| })} | |||
| > | |||
| {visibleAvatar && | |||
| (item.role === MessageType.User ? ( | |||
| <Avatar size={40} src={avatar ?? '/logo.svg'} /> | |||
| ) : avatarDialog ? ( | |||
| <Avatar size={40} src={avatarDialog} /> | |||
| ) : ( | |||
| <AssistantIcon /> | |||
| ))} | |||
| <Flex vertical gap={8} flex={1}> | |||
| <Space> | |||
| {isAssistant ? ( | |||
| index !== 0 && ( | |||
| <AssistantGroupButton | |||
| messageId={item.id} | |||
| content={item.content} | |||
| prompt={item.prompt} | |||
| showLikeButton={showLikeButton} | |||
| audioBinary={item.audio_binary} | |||
| showLoudspeaker={showLoudspeaker} | |||
| ></AssistantGroupButton> | |||
| ) | |||
| ) : ( | |||
| <UserGroupButton | |||
| content={item.content} | |||
| messageId={item.id} | |||
| removeMessageById={removeMessageById} | |||
| regenerateMessage={ | |||
| regenerateMessage && handleRegenerateMessage | |||
| } | |||
| sendLoading={sendLoading} | |||
| ></UserGroupButton> | |||
| )} | |||
| {/* <b>{isAssistant ? '' : nickname}</b> */} | |||
| </Space> | |||
| <div | |||
| className={ | |||
| isAssistant | |||
| ? theme === 'dark' | |||
| ? styles.messageTextDark | |||
| : styles.messageText | |||
| : styles.messageUserText | |||
| } | |||
| > | |||
| <MarkdownContent | |||
| loading={loading} | |||
| content={item.content} | |||
| reference={reference} | |||
| clickDocumentButton={clickDocumentButton} | |||
| ></MarkdownContent> | |||
| </div> | |||
| {isAssistant && referenceDocumentList.length > 0 && ( | |||
| <List | |||
| bordered | |||
| dataSource={referenceDocumentList} | |||
| renderItem={(item) => { | |||
| return ( | |||
| <List.Item> | |||
| <Flex gap={'small'} align="center"> | |||
| <FileIcon | |||
| id={item.doc_id} | |||
| name={item.doc_name} | |||
| ></FileIcon> | |||
| <NewDocumentLink | |||
| documentId={item.doc_id} | |||
| documentName={item.doc_name} | |||
| prefix="document" | |||
| link={item.url} | |||
| > | |||
| {item.doc_name} | |||
| </NewDocumentLink> | |||
| </Flex> | |||
| </List.Item> | |||
| ); | |||
| }} | |||
| /> | |||
| )} | |||
| {isUser && documentList.length > 0 && ( | |||
| <List | |||
| bordered | |||
| dataSource={documentList} | |||
| renderItem={(item) => { | |||
| // TODO: | |||
| // const fileThumbnail = | |||
| // documentThumbnails[item.id] || documentThumbnails[item.id]; | |||
| const fileExtension = getExtension(item.name); | |||
| return ( | |||
| <List.Item> | |||
| <Flex gap={'small'} align="center"> | |||
| <FileIcon id={item.id} name={item.name}></FileIcon> | |||
| {isImage(fileExtension) ? ( | |||
| <NewDocumentLink | |||
| documentId={item.id} | |||
| documentName={item.name} | |||
| prefix="document" | |||
| > | |||
| {item.name} | |||
| </NewDocumentLink> | |||
| ) : ( | |||
| <Button | |||
| type={'text'} | |||
| onClick={handleUserDocumentClick(item.id)} | |||
| > | |||
| <Text | |||
| style={{ maxWidth: '40vw' }} | |||
| ellipsis={{ tooltip: item.name }} | |||
| > | |||
| {item.name} | |||
| </Text> | |||
| </Button> | |||
| )} | |||
| </Flex> | |||
| </List.Item> | |||
| ); | |||
| }} | |||
| /> | |||
| )} | |||
| </Flex> | |||
| </div> | |||
| </section> | |||
| {visible && ( | |||
| <IndentedTreeModal | |||
| visible={visible} | |||
| hideModal={hideModal} | |||
| documentId={clickedDocumentId} | |||
| ></IndentedTreeModal> | |||
| )} | |||
| </div> | |||
| ); | |||
| }; | |||
| export default memo(MessageItem); | |||
| @@ -0,0 +1,30 @@ | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { IFeedbackRequestBody } from '@/interfaces/request/chat'; | |||
| import { Modal, Space } from 'antd'; | |||
| import HightLightMarkdown from '../highlight-markdown'; | |||
| import SvgIcon from '../svg-icon'; | |||
| const PromptModal = ({ | |||
| visible, | |||
| hideModal, | |||
| prompt, | |||
| }: IModalProps<IFeedbackRequestBody> & { prompt?: string }) => { | |||
| return ( | |||
| <Modal | |||
| title={ | |||
| <Space> | |||
| <SvgIcon name={`prompt`} width={18}></SvgIcon> | |||
| Prompt | |||
| </Space> | |||
| } | |||
| width={'80%'} | |||
| open={visible} | |||
| onCancel={hideModal} | |||
| footer={null} | |||
| > | |||
| <HightLightMarkdown>{prompt}</HightLightMarkdown> | |||
| </Modal> | |||
| ); | |||
| }; | |||
| export default PromptModal; | |||
| @@ -1,58 +1,66 @@ | |||
| 'use client'; | |||
| import * as AccordionPrimitive from '@radix-ui/react-accordion'; | |||
| import { ChevronDown } from 'lucide-react'; | |||
| import { ChevronDownIcon } from 'lucide-react'; | |||
| import * as React from 'react'; | |||
| import { cn } from '@/lib/utils'; | |||
| const Accordion = AccordionPrimitive.Root; | |||
| function Accordion({ | |||
| ...props | |||
| }: React.ComponentProps<typeof AccordionPrimitive.Root>) { | |||
| return <AccordionPrimitive.Root data-slot="accordion" {...props} />; | |||
| } | |||
| const AccordionItem = React.forwardRef< | |||
| React.ElementRef<typeof AccordionPrimitive.Item>, | |||
| React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> | |||
| >(({ className, ...props }, ref) => ( | |||
| <AccordionPrimitive.Item | |||
| ref={ref} | |||
| className={cn('border-b', className)} | |||
| {...props} | |||
| /> | |||
| )); | |||
| AccordionItem.displayName = 'AccordionItem'; | |||
| const AccordionTrigger = React.forwardRef< | |||
| React.ElementRef<typeof AccordionPrimitive.Trigger>, | |||
| React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> | |||
| >(({ className, children, ...props }, ref) => ( | |||
| <AccordionPrimitive.Header className="flex"> | |||
| <AccordionPrimitive.Trigger | |||
| ref={ref} | |||
| className={cn( | |||
| 'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180', | |||
| className, | |||
| )} | |||
| function AccordionItem({ | |||
| className, | |||
| ...props | |||
| }: React.ComponentProps<typeof AccordionPrimitive.Item>) { | |||
| return ( | |||
| <AccordionPrimitive.Item | |||
| data-slot="accordion-item" | |||
| className={cn('border-b last:border-b-0', className)} | |||
| {...props} | |||
| > | |||
| {children} | |||
| <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" /> | |||
| </AccordionPrimitive.Trigger> | |||
| </AccordionPrimitive.Header> | |||
| )); | |||
| AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; | |||
| /> | |||
| ); | |||
| } | |||
| const AccordionContent = React.forwardRef< | |||
| React.ElementRef<typeof AccordionPrimitive.Content>, | |||
| React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> | |||
| >(({ className, children, ...props }, ref) => ( | |||
| <AccordionPrimitive.Content | |||
| ref={ref} | |||
| className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" | |||
| {...props} | |||
| > | |||
| <div className={cn('pb-4 pt-0', className)}>{children}</div> | |||
| </AccordionPrimitive.Content> | |||
| )); | |||
| function AccordionTrigger({ | |||
| className, | |||
| children, | |||
| ...props | |||
| }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) { | |||
| return ( | |||
| <AccordionPrimitive.Header className="flex"> | |||
| <AccordionPrimitive.Trigger | |||
| data-slot="accordion-trigger" | |||
| className={cn( | |||
| 'focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180', | |||
| className, | |||
| )} | |||
| {...props} | |||
| > | |||
| {children} | |||
| <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" /> | |||
| </AccordionPrimitive.Trigger> | |||
| </AccordionPrimitive.Header> | |||
| ); | |||
| } | |||
| AccordionContent.displayName = AccordionPrimitive.Content.displayName; | |||
| function AccordionContent({ | |||
| className, | |||
| children, | |||
| ...props | |||
| }: React.ComponentProps<typeof AccordionPrimitive.Content>) { | |||
| return ( | |||
| <AccordionPrimitive.Content | |||
| data-slot="accordion-content" | |||
| className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm" | |||
| {...props} | |||
| > | |||
| <div className={cn('pt-0 pb-4', className)}>{children}</div> | |||
| </AccordionPrimitive.Content> | |||
| ); | |||
| } | |||
| export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }; | |||
| @@ -0,0 +1,73 @@ | |||
| 'use client'; | |||
| import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'; | |||
| import { type VariantProps } from 'class-variance-authority'; | |||
| import * as React from 'react'; | |||
| import { toggleVariants } from '@/components/ui/toggle'; | |||
| import { cn } from '@/lib/utils'; | |||
| const ToggleGroupContext = React.createContext< | |||
| VariantProps<typeof toggleVariants> | |||
| >({ | |||
| size: 'default', | |||
| variant: 'default', | |||
| }); | |||
| function ToggleGroup({ | |||
| className, | |||
| variant, | |||
| size, | |||
| children, | |||
| ...props | |||
| }: React.ComponentProps<typeof ToggleGroupPrimitive.Root> & | |||
| VariantProps<typeof toggleVariants>) { | |||
| return ( | |||
| <ToggleGroupPrimitive.Root | |||
| data-slot="toggle-group" | |||
| data-variant={variant} | |||
| data-size={size} | |||
| className={cn( | |||
| 'group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs', | |||
| className, | |||
| )} | |||
| {...props} | |||
| > | |||
| <ToggleGroupContext.Provider value={{ variant, size }}> | |||
| {children} | |||
| </ToggleGroupContext.Provider> | |||
| </ToggleGroupPrimitive.Root> | |||
| ); | |||
| } | |||
| function ToggleGroupItem({ | |||
| className, | |||
| children, | |||
| variant, | |||
| size, | |||
| ...props | |||
| }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> & | |||
| VariantProps<typeof toggleVariants>) { | |||
| const context = React.useContext(ToggleGroupContext); | |||
| return ( | |||
| <ToggleGroupPrimitive.Item | |||
| data-slot="toggle-group-item" | |||
| data-variant={context.variant || variant} | |||
| data-size={context.size || size} | |||
| className={cn( | |||
| toggleVariants({ | |||
| variant: context.variant || variant, | |||
| size: context.size || size, | |||
| }), | |||
| 'min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l', | |||
| className, | |||
| )} | |||
| {...props} | |||
| > | |||
| {children} | |||
| </ToggleGroupPrimitive.Item> | |||
| ); | |||
| } | |||
| export { ToggleGroup, ToggleGroupItem }; | |||
| @@ -0,0 +1,47 @@ | |||
| 'use client'; | |||
| import * as TogglePrimitive from '@radix-ui/react-toggle'; | |||
| import { cva, type VariantProps } from 'class-variance-authority'; | |||
| import * as React from 'react'; | |||
| import { cn } from '@/lib/utils'; | |||
| const toggleVariants = cva( | |||
| "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap", | |||
| { | |||
| variants: { | |||
| variant: { | |||
| default: 'bg-transparent', | |||
| outline: | |||
| 'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground', | |||
| }, | |||
| size: { | |||
| default: 'h-9 px-2 min-w-9', | |||
| sm: 'h-8 px-1.5 min-w-8', | |||
| lg: 'h-10 px-2.5 min-w-10', | |||
| }, | |||
| }, | |||
| defaultVariants: { | |||
| variant: 'default', | |||
| size: 'default', | |||
| }, | |||
| }, | |||
| ); | |||
| function Toggle({ | |||
| className, | |||
| variant, | |||
| size, | |||
| ...props | |||
| }: React.ComponentProps<typeof TogglePrimitive.Root> & | |||
| VariantProps<typeof toggleVariants>) { | |||
| return ( | |||
| <TogglePrimitive.Root | |||
| data-slot="toggle" | |||
| className={cn(toggleVariants({ variant, size, className }))} | |||
| {...props} | |||
| /> | |||
| ); | |||
| } | |||
| export { Toggle, toggleVariants }; | |||
| @@ -38,7 +38,9 @@ export type INodeEvent = IAnswerEvent<INodeData>; | |||
| export type IMessageEvent = IAnswerEvent<IMessageData>; | |||
| export type IEventList = Array<INodeEvent | IMessageEvent>; | |||
| export type IChatEvent = INodeEvent | IMessageEvent; | |||
| export type IEventList = Array<IChatEvent>; | |||
| export const useSendMessageBySSE = (url: string = api.completeConversation) => { | |||
| const [answerList, setAnswerList] = useState<IEventList>([]); | |||
| @@ -6,7 +6,11 @@ import { | |||
| } from '@xyflow/react'; | |||
| import '@xyflow/react/dist/style.css'; | |||
| import { ChatSheet } from '../chat/chat-sheet'; | |||
| import { AgentInstanceContext } from '../context'; | |||
| import { | |||
| AgentChatContext, | |||
| AgentChatLogContext, | |||
| AgentInstanceContext, | |||
| } from '../context'; | |||
| import FormSheet from '../form-sheet/next'; | |||
| import { | |||
| useHandleDrop, | |||
| @@ -16,6 +20,7 @@ import { | |||
| } from '../hooks'; | |||
| import { useAddNode } from '../hooks/use-add-node'; | |||
| import { useBeforeDelete } from '../hooks/use-before-delete'; | |||
| import { useCacheChatLog } from '../hooks/use-cache-chat-log'; | |||
| import { useShowDrawer, useShowLogSheet } from '../hooks/use-show-drawer'; | |||
| import { LogSheet } from '../log-sheet'; | |||
| import RunSheet from '../run-sheet'; | |||
| @@ -101,7 +106,12 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| hideDrawer, | |||
| }); | |||
| const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet(); | |||
| const { addEventList, setCurrentMessageId, currentEventListWithoutMessage } = | |||
| useCacheChatLog(); | |||
| const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({ | |||
| setCurrentMessageId, | |||
| }); | |||
| const { handleBeforeDelete } = useBeforeDelete(); | |||
| @@ -176,10 +186,13 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| </AgentInstanceContext.Provider> | |||
| )} | |||
| {chatVisible && ( | |||
| <ChatSheet | |||
| visible={chatVisible} | |||
| hideModal={hideRunOrChatDrawer} | |||
| ></ChatSheet> | |||
| <AgentChatContext.Provider value={{ showLogSheet }}> | |||
| <AgentChatLogContext.Provider | |||
| value={{ addEventList, setCurrentMessageId }} | |||
| > | |||
| <ChatSheet hideModal={hideRunOrChatDrawer}></ChatSheet> | |||
| </AgentChatLogContext.Provider> | |||
| </AgentChatContext.Provider> | |||
| )} | |||
| {runVisible && ( | |||
| <RunSheet | |||
| @@ -188,7 +201,10 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| ></RunSheet> | |||
| )} | |||
| {logSheetVisible && ( | |||
| <LogSheet hideModal={hideLogSheet} showModal={showLogSheet}></LogSheet> | |||
| <LogSheet | |||
| hideModal={hideLogSheet} | |||
| currentEventListWithoutMessage={currentEventListWithoutMessage} | |||
| ></LogSheet> | |||
| )} | |||
| </div> | |||
| ); | |||
| @@ -1,4 +1,3 @@ | |||
| import MessageItem from '@/components/message-item'; | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { useGetFileIcon } from '@/pages/chat/hooks'; | |||
| import { buildMessageItemReference } from '@/pages/chat/utils'; | |||
| @@ -7,6 +6,7 @@ import { Spin } from 'antd'; | |||
| import { useSendNextMessage } from './hooks'; | |||
| import MessageInput from '@/components/message-input'; | |||
| import MessageItem from '@/components/next-message-item'; | |||
| import PdfDrawer from '@/components/pdf-drawer'; | |||
| import { useClickDrawer } from '@/components/pdf-drawer/hooks'; | |||
| import { useFetchAgent } from '@/hooks/use-agent-request'; | |||
| @@ -8,9 +8,16 @@ import { IModalProps } from '@/interfaces/common'; | |||
| import { cn } from '@/lib/utils'; | |||
| import AgentChatBox from './box'; | |||
| export function ChatSheet({ visible, hideModal }: IModalProps<any>) { | |||
| export function ChatSheet({ hideModal }: IModalProps<any>) { | |||
| return ( | |||
| <Sheet open={visible} modal={false} onOpenChange={hideModal}> | |||
| <Sheet | |||
| open | |||
| modal={false} | |||
| onOpenChange={(open) => { | |||
| console.log('🚀 ~ ChatSheet ~ open:', open); | |||
| hideModal(); | |||
| }} | |||
| > | |||
| <SheetTitle className="hidden"></SheetTitle> | |||
| <SheetContent className={cn('top-20 p-0')}> | |||
| <SheetHeader> | |||
| @@ -16,10 +16,11 @@ import api from '@/utils/api'; | |||
| import { message } from 'antd'; | |||
| import { get } from 'lodash'; | |||
| import trim from 'lodash/trim'; | |||
| import { useCallback, useEffect, useMemo } from 'react'; | |||
| import { useCallback, useContext, useEffect, useMemo } from 'react'; | |||
| import { useParams } from 'umi'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| import { BeginId } from '../constant'; | |||
| import { AgentChatLogContext } from '../context'; | |||
| import useGraphStore from '../store'; | |||
| import { receiveMessageError } from '../utils'; | |||
| @@ -86,6 +87,7 @@ export const useSendNextMessage = () => { | |||
| const { id: agentId } = useParams(); | |||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | |||
| const { refetch } = useFetchAgent(); | |||
| const { addEventList } = useContext(AgentChatLogContext); | |||
| const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE( | |||
| api.runCanvas, | |||
| @@ -160,6 +162,10 @@ export const useSendNextMessage = () => { | |||
| } | |||
| }, [addNewestAnswer, prologue]); | |||
| useEffect(() => { | |||
| addEventList(answerList); | |||
| }, [addEventList, answerList]); | |||
| return { | |||
| handlePressEnter, | |||
| handleInputChange, | |||
| @@ -1,6 +1,8 @@ | |||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||
| import { createContext } from 'react'; | |||
| import { useAddNode } from './hooks/use-add-node'; | |||
| import { useCacheChatLog } from './hooks/use-cache-chat-log'; | |||
| import { useShowLogSheet } from './hooks/use-show-drawer'; | |||
| export const AgentFormContext = createContext<RAGFlowNodeType | undefined>( | |||
| undefined, | |||
| @@ -14,3 +16,21 @@ type AgentInstanceContextType = Pick< | |||
| export const AgentInstanceContext = createContext<AgentInstanceContextType>( | |||
| {} as AgentInstanceContextType, | |||
| ); | |||
| type AgentChatContextType = Pick< | |||
| ReturnType<typeof useShowLogSheet>, | |||
| 'showLogSheet' | |||
| >; | |||
| export const AgentChatContext = createContext<AgentChatContextType>( | |||
| {} as AgentChatContextType, | |||
| ); | |||
| type AgentChatLogContextType = Pick< | |||
| ReturnType<typeof useCacheChatLog>, | |||
| 'addEventList' | 'setCurrentMessageId' | |||
| >; | |||
| export const AgentChatLogContext = createContext<AgentChatLogContextType>( | |||
| {} as AgentChatLogContextType, | |||
| ); | |||
| @@ -0,0 +1,61 @@ | |||
| import { IEventList, MessageEventType } from '@/hooks/use-send-message'; | |||
| import { useCallback, useMemo, useState } from 'react'; | |||
| export const ExcludeTypes = [ | |||
| MessageEventType.Message, | |||
| MessageEventType.MessageEnd, | |||
| ]; | |||
| export function useCacheChatLog() { | |||
| const [eventList, setEventList] = useState<IEventList>([]); | |||
| const [currentMessageId, setCurrentMessageId] = useState(''); | |||
| const filterEventListByMessageId = useCallback( | |||
| (messageId: string) => { | |||
| return eventList.filter((x) => x.message_id === messageId); | |||
| }, | |||
| [eventList], | |||
| ); | |||
| const filterEventListByEventType = useCallback( | |||
| (eventType: string) => { | |||
| return eventList.filter((x) => x.event === eventType); | |||
| }, | |||
| [eventList], | |||
| ); | |||
| const clearEventList = useCallback(() => { | |||
| setEventList([]); | |||
| }, []); | |||
| const addEventList = useCallback((events: IEventList) => { | |||
| setEventList((list) => { | |||
| const nextList = [...list]; | |||
| events.forEach((x) => { | |||
| if (nextList.every((y) => y !== x)) { | |||
| nextList.push(x); | |||
| } | |||
| }); | |||
| return nextList; | |||
| }); | |||
| }, []); | |||
| const currentEventListWithoutMessage = useMemo(() => { | |||
| return eventList.filter( | |||
| (x) => | |||
| x.message_id === currentMessageId && | |||
| ExcludeTypes.every((y) => y !== x.event), | |||
| ); | |||
| }, [currentMessageId, eventList]); | |||
| return { | |||
| eventList, | |||
| currentEventListWithoutMessage, | |||
| setEventList, | |||
| clearEventList, | |||
| addEventList, | |||
| filterEventListByEventType, | |||
| filterEventListByMessageId, | |||
| setCurrentMessageId, | |||
| }; | |||
| } | |||
| @@ -5,6 +5,7 @@ import { useCallback, useEffect } from 'react'; | |||
| import { Operator } from '../constant'; | |||
| import { BeginQuery } from '../interface'; | |||
| import useGraphStore from '../store'; | |||
| import { useCacheChatLog } from './use-cache-chat-log'; | |||
| import { useGetBeginNodeDataQuery } from './use-get-begin-query'; | |||
| import { useSaveGraph } from './use-save-graph'; | |||
| @@ -152,12 +153,22 @@ export function useShowDrawer({ | |||
| }; | |||
| } | |||
| export function useShowLogSheet() { | |||
| export function useShowLogSheet({ | |||
| setCurrentMessageId, | |||
| }: Pick<ReturnType<typeof useCacheChatLog>, 'setCurrentMessageId'>) { | |||
| const { visible, showModal, hideModal } = useSetModalState(); | |||
| const handleShow = useCallback( | |||
| (messageId: string) => { | |||
| setCurrentMessageId(messageId); | |||
| showModal(); | |||
| }, | |||
| [setCurrentMessageId, showModal], | |||
| ); | |||
| return { | |||
| logSheetVisible: visible, | |||
| hideLogSheet: hideModal, | |||
| showLogSheet: showModal, | |||
| showLogSheet: handleShow, | |||
| }; | |||
| } | |||
| @@ -1,23 +1,116 @@ | |||
| import { | |||
| Accordion, | |||
| AccordionContent, | |||
| AccordionItem, | |||
| AccordionTrigger, | |||
| } from '@/components/ui/accordion'; | |||
| import { | |||
| Sheet, | |||
| SheetContent, | |||
| SheetDescription, | |||
| SheetHeader, | |||
| SheetTitle, | |||
| } from '@/components/ui/sheet'; | |||
| import { INodeEvent, MessageEventType } from '@/hooks/use-send-message'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { NotebookText } from 'lucide-react'; | |||
| import { useCallback, useMemo } from 'react'; | |||
| import JsonView from 'react18-json-view'; | |||
| import 'react18-json-view/src/style.css'; | |||
| import { useCacheChatLog } from '../hooks/use-cache-chat-log'; | |||
| import useGraphStore from '../store'; | |||
| type LogSheetProps = IModalProps<any> & | |||
| Pick<ReturnType<typeof useCacheChatLog>, 'currentEventListWithoutMessage'>; | |||
| export function LogSheet({ hideModal }: IModalProps<any>) { | |||
| function JsonViewer({ | |||
| data, | |||
| title, | |||
| }: { | |||
| data: Record<string, any>; | |||
| title: string; | |||
| }) { | |||
| return ( | |||
| <Sheet open onOpenChange={hideModal}> | |||
| <SheetContent> | |||
| <section className="space-y-2"> | |||
| <div>{title}</div> | |||
| <JsonView | |||
| src={data} | |||
| displaySize | |||
| collapseStringsAfterLength={100000000000} | |||
| className="w-full h-[200px] break-words overflow-auto p-2 bg-slate-800" | |||
| /> | |||
| </section> | |||
| ); | |||
| } | |||
| export function LogSheet({ | |||
| hideModal, | |||
| currentEventListWithoutMessage, | |||
| }: LogSheetProps) { | |||
| const getNode = useGraphStore((state) => state.getNode); | |||
| const getNodeName = useCallback( | |||
| (nodeId: string) => { | |||
| return getNode(nodeId)?.data.name; | |||
| }, | |||
| [getNode], | |||
| ); | |||
| const finishedNodeList = useMemo(() => { | |||
| return currentEventListWithoutMessage.filter( | |||
| (x) => x.event === MessageEventType.NodeFinished, | |||
| ) as INodeEvent[]; | |||
| }, [currentEventListWithoutMessage]); | |||
| return ( | |||
| <Sheet open onOpenChange={hideModal} modal={false}> | |||
| <SheetContent className="top-20 right-96"> | |||
| <SheetHeader> | |||
| <SheetTitle>Are you absolutely sure?</SheetTitle> | |||
| <SheetDescription> | |||
| This action cannot be undone. This will permanently delete your | |||
| account and remove your data from our servers. | |||
| </SheetDescription> | |||
| <SheetTitle className="flex items-center gap-1"> | |||
| <NotebookText className="size-4" /> | |||
| Log | |||
| </SheetTitle> | |||
| </SheetHeader> | |||
| <section className="max-h-[82vh] overflow-auto"> | |||
| {finishedNodeList.map((x, idx) => ( | |||
| <section key={idx}> | |||
| <Accordion type="single" collapsible> | |||
| <AccordionItem value={idx.toString()}> | |||
| <AccordionTrigger> | |||
| <div className="flex gap-2 items-center"> | |||
| <span>{getNodeName(x.data?.component_id)}</span> | |||
| <span className="text-text-sub-title text-xs"> | |||
| {x.data.elapsed_time?.toString().slice(0, 6)} | |||
| </span> | |||
| <span | |||
| className={cn( | |||
| 'border-background -end-1 -top-1 size-2 rounded-full border-2 bg-dot-green', | |||
| { 'text-dot-green': x.data.error === null }, | |||
| { 'text-dot-red': x.data.error !== null }, | |||
| )} | |||
| > | |||
| <span className="sr-only">Online</span> | |||
| </span> | |||
| </div> | |||
| </AccordionTrigger> | |||
| <AccordionContent> | |||
| <div className="space-y-2"> | |||
| <JsonViewer | |||
| data={x.data.inputs} | |||
| title="Input" | |||
| ></JsonViewer> | |||
| <JsonViewer | |||
| data={x.data.outputs} | |||
| title="Output" | |||
| ></JsonViewer> | |||
| </div> | |||
| </AccordionContent> | |||
| </AccordionItem> | |||
| </Accordion> | |||
| </section> | |||
| ))} | |||
| </section> | |||
| </SheetContent> | |||
| </Sheet> | |||
| ); | |||
| @@ -1,56 +0,0 @@ | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { Form, useForm } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { useCallback, useState } from 'react'; | |||
| import DynamicCategorize from './agent/form/categorize-form/dynamic-categorize'; | |||
| const formSchema = z.object({ | |||
| items: z | |||
| .array( | |||
| z | |||
| .object({ | |||
| name: z.string().min(1, 'xxx').trim(), | |||
| description: z.string().optional(), | |||
| // examples: z | |||
| // .array( | |||
| // z.object({ | |||
| // value: z.string(), | |||
| // }), | |||
| // ) | |||
| // .optional(), | |||
| }) | |||
| .optional(), | |||
| ) | |||
| .optional(), | |||
| }); | |||
| export function Demo() { | |||
| const [flag, setFlag] = useState(false); | |||
| const form = useForm<z.infer<typeof formSchema>>({ | |||
| resolver: zodResolver(formSchema), | |||
| defaultValues: { | |||
| items: [], | |||
| }, | |||
| }); | |||
| const handleReset = useCallback(() => { | |||
| form?.reset(); | |||
| }, [form]); | |||
| const handleSwitch = useCallback(() => { | |||
| setFlag(true); | |||
| }, []); | |||
| return ( | |||
| <div> | |||
| <Form {...form}> | |||
| <DynamicCategorize></DynamicCategorize> | |||
| </Form> | |||
| <Button onClick={handleReset}>reset</Button> | |||
| <Button onClick={handleSwitch}>switch</Button> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -54,6 +54,8 @@ module.exports = { | |||
| 'background-highlight': 'var(--background-highlight)', | |||
| 'input-border': 'var(--input-border)', | |||
| 'dot-green': 'var(--dot-green)', | |||
| 'dot-red': 'var(--dot-red)', | |||
| primary: { | |||
| DEFAULT: 'hsl(var(--primary))', | |||
| @@ -89,6 +89,8 @@ | |||
| --background-highlight: rgba(76, 164, 231, 0.1); | |||
| --input-border: rgba(22, 22, 24, 0.2); | |||
| --dot-green: rgba(59, 160, 92, 1); | |||
| --dot-red: rgba(216, 73, 75, 1); | |||
| } | |||
| .dark { | |||
| @@ -199,6 +201,9 @@ | |||
| --background-highlight: rgba(76, 164, 231, 0.1); | |||
| --input-border: rgba(255, 255, 255, 0.2); | |||
| --dot-green: rgba(59, 160, 92, 1); | |||
| --dot-red: rgba(216, 73, 75, 1); | |||
| } | |||
| } | |||