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-switch": "^1.1.1",
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-tabs": "^1.1.1",
|
||||||
"@radix-ui/react-toast": "^1.2.6",
|
"@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",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@tailwindcss/line-clamp": "^0.4.4",
|
"@tailwindcss/line-clamp": "^0.4.4",
|
||||||
"@tanstack/react-query": "^5.40.0",
|
"@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": {
|
"node_modules/@radix-ui/react-tooltip": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.4.tgz",
|
"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-switch": "^1.1.1",
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-tabs": "^1.1.1",
|
||||||
"@radix-ui/react-toast": "^1.2.6",
|
"@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",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@tailwindcss/line-clamp": "^0.4.4",
|
"@tailwindcss/line-clamp": "^0.4.4",
|
||||||
"@tanstack/react-query": "^5.40.0",
|
"@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';
|
'use client';
|
||||||
|
|
||||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDownIcon } from 'lucide-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
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<
|
function AccordionItem({
|
||||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
className,
|
||||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
...props
|
||||||
>(({ className, ...props }, ref) => (
|
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
||||||
<AccordionPrimitive.Item
|
return (
|
||||||
ref={ref}
|
<AccordionPrimitive.Item
|
||||||
className={cn('border-b', className)}
|
data-slot="accordion-item"
|
||||||
{...props}
|
className={cn('border-b last:border-b-0', className)}
|
||||||
/>
|
{...props}
|
||||||
));
|
/>
|
||||||
AccordionItem.displayName = 'AccordionItem';
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const AccordionTrigger = React.forwardRef<
|
function AccordionTrigger({
|
||||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
className,
|
||||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
children,
|
||||||
>(({ className, children, ...props }, ref) => (
|
...props
|
||||||
<AccordionPrimitive.Header className="flex">
|
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
||||||
<AccordionPrimitive.Trigger
|
return (
|
||||||
ref={ref}
|
<AccordionPrimitive.Header className="flex">
|
||||||
className={cn(
|
<AccordionPrimitive.Trigger
|
||||||
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
|
data-slot="accordion-trigger"
|
||||||
className,
|
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}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
<div className={cn('pt-0 pb-4', className)}>{children}</div>
|
||||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
</AccordionPrimitive.Content>
|
||||||
</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;
|
|
||||||
|
|
||||||
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
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 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) => {
|
export const useSendMessageBySSE = (url: string = api.completeConversation) => {
|
||||||
const [answerList, setAnswerList] = useState<IEventList>([]);
|
const [answerList, setAnswerList] = useState<IEventList>([]);
|
||||||
|
|||||||
@ -6,7 +6,11 @@ import {
|
|||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import '@xyflow/react/dist/style.css';
|
import '@xyflow/react/dist/style.css';
|
||||||
import { ChatSheet } from '../chat/chat-sheet';
|
import { ChatSheet } from '../chat/chat-sheet';
|
||||||
import { AgentInstanceContext } from '../context';
|
import {
|
||||||
|
AgentChatContext,
|
||||||
|
AgentChatLogContext,
|
||||||
|
AgentInstanceContext,
|
||||||
|
} from '../context';
|
||||||
import FormSheet from '../form-sheet/next';
|
import FormSheet from '../form-sheet/next';
|
||||||
import {
|
import {
|
||||||
useHandleDrop,
|
useHandleDrop,
|
||||||
@ -16,6 +20,7 @@ import {
|
|||||||
} from '../hooks';
|
} from '../hooks';
|
||||||
import { useAddNode } from '../hooks/use-add-node';
|
import { useAddNode } from '../hooks/use-add-node';
|
||||||
import { useBeforeDelete } from '../hooks/use-before-delete';
|
import { useBeforeDelete } from '../hooks/use-before-delete';
|
||||||
|
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
|
||||||
import { useShowDrawer, useShowLogSheet } from '../hooks/use-show-drawer';
|
import { useShowDrawer, useShowLogSheet } from '../hooks/use-show-drawer';
|
||||||
import { LogSheet } from '../log-sheet';
|
import { LogSheet } from '../log-sheet';
|
||||||
import RunSheet from '../run-sheet';
|
import RunSheet from '../run-sheet';
|
||||||
@ -101,7 +106,12 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
hideDrawer,
|
hideDrawer,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet();
|
const { addEventList, setCurrentMessageId, currentEventListWithoutMessage } =
|
||||||
|
useCacheChatLog();
|
||||||
|
|
||||||
|
const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({
|
||||||
|
setCurrentMessageId,
|
||||||
|
});
|
||||||
|
|
||||||
const { handleBeforeDelete } = useBeforeDelete();
|
const { handleBeforeDelete } = useBeforeDelete();
|
||||||
|
|
||||||
@ -176,10 +186,13 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
</AgentInstanceContext.Provider>
|
</AgentInstanceContext.Provider>
|
||||||
)}
|
)}
|
||||||
{chatVisible && (
|
{chatVisible && (
|
||||||
<ChatSheet
|
<AgentChatContext.Provider value={{ showLogSheet }}>
|
||||||
visible={chatVisible}
|
<AgentChatLogContext.Provider
|
||||||
hideModal={hideRunOrChatDrawer}
|
value={{ addEventList, setCurrentMessageId }}
|
||||||
></ChatSheet>
|
>
|
||||||
|
<ChatSheet hideModal={hideRunOrChatDrawer}></ChatSheet>
|
||||||
|
</AgentChatLogContext.Provider>
|
||||||
|
</AgentChatContext.Provider>
|
||||||
)}
|
)}
|
||||||
{runVisible && (
|
{runVisible && (
|
||||||
<RunSheet
|
<RunSheet
|
||||||
@ -188,7 +201,10 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
></RunSheet>
|
></RunSheet>
|
||||||
)}
|
)}
|
||||||
{logSheetVisible && (
|
{logSheetVisible && (
|
||||||
<LogSheet hideModal={hideLogSheet} showModal={showLogSheet}></LogSheet>
|
<LogSheet
|
||||||
|
hideModal={hideLogSheet}
|
||||||
|
currentEventListWithoutMessage={currentEventListWithoutMessage}
|
||||||
|
></LogSheet>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import MessageItem from '@/components/message-item';
|
|
||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { useGetFileIcon } from '@/pages/chat/hooks';
|
import { useGetFileIcon } from '@/pages/chat/hooks';
|
||||||
import { buildMessageItemReference } from '@/pages/chat/utils';
|
import { buildMessageItemReference } from '@/pages/chat/utils';
|
||||||
@ -7,6 +6,7 @@ import { Spin } from 'antd';
|
|||||||
import { useSendNextMessage } from './hooks';
|
import { useSendNextMessage } from './hooks';
|
||||||
|
|
||||||
import MessageInput from '@/components/message-input';
|
import MessageInput from '@/components/message-input';
|
||||||
|
import MessageItem from '@/components/next-message-item';
|
||||||
import PdfDrawer from '@/components/pdf-drawer';
|
import PdfDrawer from '@/components/pdf-drawer';
|
||||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
|
|||||||
@ -8,9 +8,16 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import AgentChatBox from './box';
|
import AgentChatBox from './box';
|
||||||
|
|
||||||
export function ChatSheet({ visible, hideModal }: IModalProps<any>) {
|
export function ChatSheet({ hideModal }: IModalProps<any>) {
|
||||||
return (
|
return (
|
||||||
<Sheet open={visible} modal={false} onOpenChange={hideModal}>
|
<Sheet
|
||||||
|
open
|
||||||
|
modal={false}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
console.log('🚀 ~ ChatSheet ~ open:', open);
|
||||||
|
hideModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SheetTitle className="hidden"></SheetTitle>
|
<SheetTitle className="hidden"></SheetTitle>
|
||||||
<SheetContent className={cn('top-20 p-0')}>
|
<SheetContent className={cn('top-20 p-0')}>
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
|
|||||||
@ -16,10 +16,11 @@ import api from '@/utils/api';
|
|||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import trim from 'lodash/trim';
|
import trim from 'lodash/trim';
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useContext, useEffect, useMemo } from 'react';
|
||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { BeginId } from '../constant';
|
import { BeginId } from '../constant';
|
||||||
|
import { AgentChatLogContext } from '../context';
|
||||||
import useGraphStore from '../store';
|
import useGraphStore from '../store';
|
||||||
import { receiveMessageError } from '../utils';
|
import { receiveMessageError } from '../utils';
|
||||||
|
|
||||||
@ -86,6 +87,7 @@ export const useSendNextMessage = () => {
|
|||||||
const { id: agentId } = useParams();
|
const { id: agentId } = useParams();
|
||||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||||
const { refetch } = useFetchAgent();
|
const { refetch } = useFetchAgent();
|
||||||
|
const { addEventList } = useContext(AgentChatLogContext);
|
||||||
|
|
||||||
const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE(
|
const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE(
|
||||||
api.runCanvas,
|
api.runCanvas,
|
||||||
@ -160,6 +162,10 @@ export const useSendNextMessage = () => {
|
|||||||
}
|
}
|
||||||
}, [addNewestAnswer, prologue]);
|
}, [addNewestAnswer, prologue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addEventList(answerList);
|
||||||
|
}, [addEventList, answerList]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handlePressEnter,
|
handlePressEnter,
|
||||||
handleInputChange,
|
handleInputChange,
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { useAddNode } from './hooks/use-add-node';
|
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>(
|
export const AgentFormContext = createContext<RAGFlowNodeType | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
@ -14,3 +16,21 @@ type AgentInstanceContextType = Pick<
|
|||||||
export const AgentInstanceContext = createContext<AgentInstanceContextType>(
|
export const AgentInstanceContext = createContext<AgentInstanceContextType>(
|
||||||
{} as 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 { Operator } from '../constant';
|
||||||
import { BeginQuery } from '../interface';
|
import { BeginQuery } from '../interface';
|
||||||
import useGraphStore from '../store';
|
import useGraphStore from '../store';
|
||||||
|
import { useCacheChatLog } from './use-cache-chat-log';
|
||||||
import { useGetBeginNodeDataQuery } from './use-get-begin-query';
|
import { useGetBeginNodeDataQuery } from './use-get-begin-query';
|
||||||
import { useSaveGraph } from './use-save-graph';
|
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 { visible, showModal, hideModal } = useSetModalState();
|
||||||
|
|
||||||
|
const handleShow = useCallback(
|
||||||
|
(messageId: string) => {
|
||||||
|
setCurrentMessageId(messageId);
|
||||||
|
showModal();
|
||||||
|
},
|
||||||
|
[setCurrentMessageId, showModal],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logSheetVisible: visible,
|
logSheetVisible: visible,
|
||||||
hideLogSheet: hideModal,
|
hideLogSheet: hideModal,
|
||||||
showLogSheet: showModal,
|
showLogSheet: handleShow,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,116 @@
|
|||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from '@/components/ui/accordion';
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
SheetDescription,
|
|
||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from '@/components/ui/sheet';
|
} from '@/components/ui/sheet';
|
||||||
|
import { INodeEvent, MessageEventType } from '@/hooks/use-send-message';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
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 (
|
return (
|
||||||
<Sheet open onOpenChange={hideModal}>
|
<section className="space-y-2">
|
||||||
<SheetContent>
|
<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>
|
<SheetHeader>
|
||||||
<SheetTitle>Are you absolutely sure?</SheetTitle>
|
<SheetTitle className="flex items-center gap-1">
|
||||||
<SheetDescription>
|
<NotebookText className="size-4" />
|
||||||
This action cannot be undone. This will permanently delete your
|
Log
|
||||||
account and remove your data from our servers.
|
</SheetTitle>
|
||||||
</SheetDescription>
|
|
||||||
</SheetHeader>
|
</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>
|
</SheetContent>
|
||||||
</Sheet>
|
</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)',
|
'background-highlight': 'var(--background-highlight)',
|
||||||
|
|
||||||
'input-border': 'var(--input-border)',
|
'input-border': 'var(--input-border)',
|
||||||
|
'dot-green': 'var(--dot-green)',
|
||||||
|
'dot-red': 'var(--dot-red)',
|
||||||
|
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
|
|||||||
@ -89,6 +89,8 @@
|
|||||||
--background-highlight: rgba(76, 164, 231, 0.1);
|
--background-highlight: rgba(76, 164, 231, 0.1);
|
||||||
|
|
||||||
--input-border: rgba(22, 22, 24, 0.2);
|
--input-border: rgba(22, 22, 24, 0.2);
|
||||||
|
--dot-green: rgba(59, 160, 92, 1);
|
||||||
|
--dot-red: rgba(216, 73, 75, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@ -199,6 +201,9 @@
|
|||||||
--background-highlight: rgba(76, 164, 231, 0.1);
|
--background-highlight: rgba(76, 164, 231, 0.1);
|
||||||
|
|
||||||
--input-border: rgba(255, 255, 255, 0.2);
|
--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