mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### 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)
This commit is contained in:
368
web/package-lock.json
generated
368
web/package-lock.json
generated
@ -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",
|
||||
|
||||
51
web/src/components/next-message-item/feedback-modal.tsx
Normal file
51
web/src/components/next-message-item/feedback-modal.tsx
Normal file
@ -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;
|
||||
220
web/src/components/next-message-item/group-button.tsx
Normal file
220
web/src/components/next-message-item/group-button.tsx
Normal file
@ -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>
|
||||
);
|
||||
};
|
||||
116
web/src/components/next-message-item/hooks.ts
Normal file
116
web/src/components/next-message-item/hooks.ts
Normal file
@ -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 };
|
||||
};
|
||||
63
web/src/components/next-message-item/index.less
Normal file
63
web/src/components/next-message-item/index.less
Normal file
@ -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;
|
||||
}
|
||||
244
web/src/components/next-message-item/index.tsx
Normal file
244
web/src/components/next-message-item/index.tsx
Normal file
@ -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);
|
||||
30
web/src/components/next-message-item/prompt-modal.tsx
Normal file
30
web/src/components/next-message-item/prompt-modal.tsx
Normal file
@ -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';
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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 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>
|
||||
);
|
||||
}
|
||||
|
||||
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}
|
||||
>
|
||||
{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>
|
||||
));
|
||||
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||
<div className={cn('pt-0 pb-4', className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
);
|
||||
}
|
||||
|
||||
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
||||
|
||||
73
web/src/components/ui/toggle-group.tsx
Normal file
73
web/src/components/ui/toggle-group.tsx
Normal file
@ -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 };
|
||||
47
web/src/components/ui/toggle.tsx
Normal file
47
web/src/components/ui/toggle.tsx
Normal file
@ -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,
|
||||
);
|
||||
|
||||
61
web/src/pages/agent/hooks/use-cache-chat-log.ts
Normal file
61
web/src/pages/agent/hooks/use-cache-chat-log.ts
Normal file
@ -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';
|
||||
|
||||
export function LogSheet({ hideModal }: IModalProps<any>) {
|
||||
type LogSheetProps = IModalProps<any> &
|
||||
Pick<ReturnType<typeof useCacheChatLog>, 'currentEventListWithoutMessage'>;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user