diff --git a/web/package-lock.json b/web/package-lock.json index e9de9209b..ad3faec8f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -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", diff --git a/web/package.json b/web/package.json index cc6d0132e..6184959ef 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/src/components/next-message-item/feedback-modal.tsx b/web/src/components/next-message-item/feedback-modal.tsx new file mode 100644 index 000000000..c2a471c7b --- /dev/null +++ b/web/src/components/next-message-item/feedback-modal.tsx @@ -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) => { + const [form] = Form.useForm(); + + const handleOk = useCallback(async () => { + const ret = await form.validateFields(); + return onOk?.({ thumbup: false, feedback: ret.feedback }); + }, [onOk, form]); + + return ( + +
+ + name="feedback" + rules={[{ required: true, message: 'Please input your feedback!' }]} + > + + + +
+ ); +}; + +export default FeedbackModal; diff --git a/web/src/components/next-message-item/group-button.tsx b/web/src/components/next-message-item/group-button.tsx new file mode 100644 index 000000000..3e1b21e88 --- /dev/null +++ b/web/src/components/next-message-item/group-button.tsx @@ -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 ( + <> + + + + + {showLoudspeaker && ( + + + {isPlaying ? : } + + + + )} + {showLikeButton && ( + <> + + + + + + + + )} + {prompt && ( + + + + )} + + + + + {visible && ( + + )} + {promptVisible && ( + + )} + + ); + + return ( + <> + + + + + {showLoudspeaker && ( + + + {isPlaying ? : } + + + + )} + {showLikeButton && ( + <> + + + + + + + + )} + {prompt && ( + + + + )} + { + e.preventDefault(); + e.stopPropagation(); + handleShowLogSheet(); + }} + > + + + + {visible && ( + + )} + {promptVisible && ( + + )} + + ); +}; + +interface UserGroupButtonProps extends Partial { + 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 ( + + + + + {regenerateMessage && ( + + + + + + )} + {removeMessageById && ( + + + + + + )} + + ); +}; diff --git a/web/src/components/next-message-item/hooks.ts b/web/src/components/next-message-item/hooks.ts new file mode 100644 index 000000000..f77da0501 --- /dev/null +++ b/web/src/components/next-message-item/hooks.ts @@ -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(null); + const { read } = useSpeechWithSse(); + const player = useRef(); + const [isPlaying, setIsPlaying] = useState(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 }; +}; diff --git a/web/src/components/next-message-item/index.less b/web/src/components/next-message-item/index.less new file mode 100644 index 000000000..a4812bd61 --- /dev/null +++ b/web/src/components/next-message-item/index.less @@ -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; +} diff --git a/web/src/components/next-message-item/index.tsx b/web/src/components/next-message-item/index.tsx new file mode 100644 index 000000000..664f46afb --- /dev/null +++ b/web/src/components/next-message-item/index.tsx @@ -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, 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 ( +
+
+
+ {visibleAvatar && + (item.role === MessageType.User ? ( + + ) : avatarDialog ? ( + + ) : ( + + ))} + + + + {isAssistant ? ( + index !== 0 && ( + + ) + ) : ( + + )} + + {/* {isAssistant ? '' : nickname} */} + +
+ +
+ {isAssistant && referenceDocumentList.length > 0 && ( + { + return ( + + + + + + {item.doc_name} + + + + ); + }} + /> + )} + {isUser && documentList.length > 0 && ( + { + // TODO: + // const fileThumbnail = + // documentThumbnails[item.id] || documentThumbnails[item.id]; + const fileExtension = getExtension(item.name); + return ( + + + + + {isImage(fileExtension) ? ( + + {item.name} + + ) : ( + + )} + + + ); + }} + /> + )} +
+
+
+ {visible && ( + + )} +
+ ); +}; + +export default memo(MessageItem); diff --git a/web/src/components/next-message-item/prompt-modal.tsx b/web/src/components/next-message-item/prompt-modal.tsx new file mode 100644 index 000000000..f5222e59b --- /dev/null +++ b/web/src/components/next-message-item/prompt-modal.tsx @@ -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 & { prompt?: string }) => { + return ( + + + Prompt + + } + width={'80%'} + open={visible} + onCancel={hideModal} + footer={null} + > + {prompt} + + ); +}; + +export default PromptModal; diff --git a/web/src/components/ui/accordion.tsx b/web/src/components/ui/accordion.tsx index 347afa7d5..67ab212d2 100644 --- a/web/src/components/ui/accordion.tsx +++ b/web/src/components/ui/accordion.tsx @@ -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) { + return ; +} -const AccordionItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AccordionItem.displayName = 'AccordionItem'; +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} -const AccordionTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - svg]:rotate-180', - className, - )} +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180', + className, + )} + {...props} + > + {children} + + + + ); +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + - {children} - - - -)); -AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; - -const AccordionContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - -
{children}
-
-)); - -AccordionContent.displayName = AccordionPrimitive.Content.displayName; +
{children}
+ + ); +} export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }; diff --git a/web/src/components/ui/toggle-group.tsx b/web/src/components/ui/toggle-group.tsx new file mode 100644 index 000000000..a99779385 --- /dev/null +++ b/web/src/components/ui/toggle-group.tsx @@ -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 +>({ + size: 'default', + variant: 'default', +}); + +function ToggleGroup({ + className, + variant, + size, + children, + ...props +}: React.ComponentProps & + VariantProps) { + return ( + + + {children} + + + ); +} + +function ToggleGroupItem({ + className, + children, + variant, + size, + ...props +}: React.ComponentProps & + VariantProps) { + const context = React.useContext(ToggleGroupContext); + + return ( + + {children} + + ); +} + +export { ToggleGroup, ToggleGroupItem }; diff --git a/web/src/components/ui/toggle.tsx b/web/src/components/ui/toggle.tsx new file mode 100644 index 000000000..72f15cf60 --- /dev/null +++ b/web/src/components/ui/toggle.tsx @@ -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 & + VariantProps) { + return ( + + ); +} + +export { Toggle, toggleVariants }; diff --git a/web/src/hooks/use-send-message.ts b/web/src/hooks/use-send-message.ts index f86e81e41..0253f29bf 100644 --- a/web/src/hooks/use-send-message.ts +++ b/web/src/hooks/use-send-message.ts @@ -38,7 +38,9 @@ export type INodeEvent = IAnswerEvent; export type IMessageEvent = IAnswerEvent; -export type IEventList = Array; +export type IChatEvent = INodeEvent | IMessageEvent; + +export type IEventList = Array; export const useSendMessageBySSE = (url: string = api.completeConversation) => { const [answerList, setAnswerList] = useState([]); diff --git a/web/src/pages/agent/canvas/index.tsx b/web/src/pages/agent/canvas/index.tsx index 06c351963..f498d8638 100644 --- a/web/src/pages/agent/canvas/index.tsx +++ b/web/src/pages/agent/canvas/index.tsx @@ -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) { )} {chatVisible && ( - + + + + + )} {runVisible && ( )} {logSheetVisible && ( - + )} ); diff --git a/web/src/pages/agent/chat/box.tsx b/web/src/pages/agent/chat/box.tsx index fa6aed660..6daabd17e 100644 --- a/web/src/pages/agent/chat/box.tsx +++ b/web/src/pages/agent/chat/box.tsx @@ -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'; diff --git a/web/src/pages/agent/chat/chat-sheet.tsx b/web/src/pages/agent/chat/chat-sheet.tsx index 703af40be..ece538f08 100644 --- a/web/src/pages/agent/chat/chat-sheet.tsx +++ b/web/src/pages/agent/chat/chat-sheet.tsx @@ -8,9 +8,16 @@ import { IModalProps } from '@/interfaces/common'; import { cn } from '@/lib/utils'; import AgentChatBox from './box'; -export function ChatSheet({ visible, hideModal }: IModalProps) { +export function ChatSheet({ hideModal }: IModalProps) { return ( - + { + console.log('🚀 ~ ChatSheet ~ open:', open); + hideModal(); + }} + > diff --git a/web/src/pages/agent/chat/hooks.ts b/web/src/pages/agent/chat/hooks.ts index 13bb3003c..e813c90c4 100644 --- a/web/src/pages/agent/chat/hooks.ts +++ b/web/src/pages/agent/chat/hooks.ts @@ -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, diff --git a/web/src/pages/agent/context.ts b/web/src/pages/agent/context.ts index ddb2d6ddb..cca63defb 100644 --- a/web/src/pages/agent/context.ts +++ b/web/src/pages/agent/context.ts @@ -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( undefined, @@ -14,3 +16,21 @@ type AgentInstanceContextType = Pick< export const AgentInstanceContext = createContext( {} as AgentInstanceContextType, ); + +type AgentChatContextType = Pick< + ReturnType, + 'showLogSheet' +>; + +export const AgentChatContext = createContext( + {} as AgentChatContextType, +); + +type AgentChatLogContextType = Pick< + ReturnType, + 'addEventList' | 'setCurrentMessageId' +>; + +export const AgentChatLogContext = createContext( + {} as AgentChatLogContextType, +); diff --git a/web/src/pages/agent/hooks/use-cache-chat-log.ts b/web/src/pages/agent/hooks/use-cache-chat-log.ts new file mode 100644 index 000000000..9b4f92bc5 --- /dev/null +++ b/web/src/pages/agent/hooks/use-cache-chat-log.ts @@ -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([]); + 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, + }; +} diff --git a/web/src/pages/agent/hooks/use-show-drawer.tsx b/web/src/pages/agent/hooks/use-show-drawer.tsx index f0693980f..4bdf25459 100644 --- a/web/src/pages/agent/hooks/use-show-drawer.tsx +++ b/web/src/pages/agent/hooks/use-show-drawer.tsx @@ -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, '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, }; } diff --git a/web/src/pages/agent/log-sheet/index.tsx b/web/src/pages/agent/log-sheet/index.tsx index 0d70fcd66..8a17d7699 100644 --- a/web/src/pages/agent/log-sheet/index.tsx +++ b/web/src/pages/agent/log-sheet/index.tsx @@ -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) { +type LogSheetProps = IModalProps & + Pick, 'currentEventListWithoutMessage'>; + +function JsonViewer({ + data, + title, +}: { + data: Record; + title: string; +}) { return ( - - +
+
{title}
+ +
+ ); +} + +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 ( + + - Are you absolutely sure? - - This action cannot be undone. This will permanently delete your - account and remove your data from our servers. - + + + Log + +
+ {finishedNodeList.map((x, idx) => ( +
+ + + +
+ {getNodeName(x.data?.component_id)} + + {x.data.elapsed_time?.toString().slice(0, 6)} + + + Online + +
+
+ +
+ + + +
+
+
+
+
+ ))} +
); diff --git a/web/src/pages/demo.tsx b/web/src/pages/demo.tsx deleted file mode 100644 index 358259893..000000000 --- a/web/src/pages/demo.tsx +++ /dev/null @@ -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>({ - resolver: zodResolver(formSchema), - defaultValues: { - items: [], - }, - }); - - const handleReset = useCallback(() => { - form?.reset(); - }, [form]); - - const handleSwitch = useCallback(() => { - setFlag(true); - }, []); - - return ( -
-
- -
- - -
- ); -} diff --git a/web/tailwind.config.js b/web/tailwind.config.js index f654ac2e2..81237478f 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -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))', diff --git a/web/tailwind.css b/web/tailwind.css index 45deb2d78..881cb20a5 100644 --- a/web/tailwind.css +++ b/web/tailwind.css @@ -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); } }