diff --git a/web/package-lock.json b/web/package-lock.json index 3286ade26..04902e1e3 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -18,16 +18,19 @@ "lodash": "^4.17.21", "moment": "^2.30.1", "rc-tween-one": "^3.0.6", + "react-chat-elements": "^12.0.13", "react-i18next": "^14.0.0", "react-infinite-scroll-component": "^6.1.0", "umi": "^4.0.90", - "umi-request": "^1.4.0" + "umi-request": "^1.4.0", + "uuid": "^9.0.1" }, "devDependencies": { "@react-dev-inspector/umi4-plugin": "^2.0.1", "@types/lodash": "^4.14.202", "@types/react": "^18.0.33", "@types/react-dom": "^18.0.11", + "@types/uuid": "^9.0.8", "@umijs/lint": "^4.1.1", "@umijs/plugins": "^4.1.0", "cross-env": "^7.0.3", @@ -2929,6 +2932,12 @@ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", "dev": true }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmmirror.com/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, "node_modules/@types/yargs": { "version": "16.0.9", "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-16.0.9.tgz", @@ -10669,6 +10678,11 @@ "node": ">=8.9.0" } }, + "node_modules/loaders.css": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/loaders.css/-/loaders.css-0.1.2.tgz", + "integrity": "sha512-Rhowlq24ey1VOeor+3wYOt9+MjaxBOJm1u4KlQgNC3+0xJ0LS4wq4iG57D/BPzvuD/7HHDGQOWJ+81oR2EI9bQ==" + }, "node_modules/local-pkg": { "version": "0.4.3", "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz", @@ -12666,6 +12680,15 @@ "resolved": "https://registry.npmmirror.com/process-warning/-/process-warning-1.0.0.tgz", "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==" }, + "node_modules/progressbar.js": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/progressbar.js/-/progressbar.js-1.1.1.tgz", + "integrity": "sha512-FBsw3BKsUbb+hNeYfiP3xzvAAQrPi4DnGDw66bCmfuRCDLcslxyxv2GyYUdBSKFGSIBa73CUP5WMcl6F8AAXlw==", + "dependencies": { + "lodash.merge": "^4.6.2", + "shifty": "^2.8.3" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz", @@ -13446,6 +13469,22 @@ "node": ">=0.10.0" } }, + "node_modules/react-chat-elements": { + "version": "12.0.13", + "resolved": "https://registry.npmmirror.com/react-chat-elements/-/react-chat-elements-12.0.13.tgz", + "integrity": "sha512-Vu5x8kW4LPu8onKfz5vsuDwZsDhoQmTBHJdqKAhVsi42PCQ8KOfzHiDp0fPUJlinJZ/MTJTm69UAchpys4iSTQ==", + "dependencies": { + "classnames": "^2.2.5", + "progressbar.js": "^1.1.0", + "react-icons": "^4.3.1", + "react-spinkit": "^3.0.0", + "timeago.js": "^4.0.2" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "18.2.0" + } + }, "node_modules/react-dev-inspector": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/react-dev-inspector/-/react-dev-inspector-2.0.1.tgz", @@ -13784,6 +13823,14 @@ } } }, + "node_modules/react-icons": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-infinite-scroll-component": { "version": "6.1.0", "resolved": "https://registry.npmmirror.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz", @@ -13924,6 +13971,17 @@ "react": ">=15" } }, + "node_modules/react-spinkit": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/react-spinkit/-/react-spinkit-3.0.0.tgz", + "integrity": "sha512-RrfGRPjqxHQiy7quPqhjPynTu0zobgQaZu1QYBMpJJ6pCSRRRK16EZMaxdE6fLVYFRJWpX/eGATWLMoVFFT5uQ==", + "dependencies": { + "classnames": "^2.2.3", + "loaders.css": "^0.1.2", + "object-assign": "^4.1.0", + "prop-types": "^15.5.8" + } + }, "node_modules/reactcss": { "version": "1.2.3", "resolved": "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz", @@ -14633,6 +14691,14 @@ "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "dev": true }, + "node_modules/shifty": { + "version": "2.20.4", + "resolved": "https://registry.npmmirror.com/shifty/-/shifty-2.20.4.tgz", + "integrity": "sha512-4Y0qRkg8ME5XN8yGNAwmFOmsIURGFKT9UQfNL6DDJQErYtN5HsjyoBuJn41ZQfTkuu2rIbRMn9qazjKsDpO2TA==", + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.4.tgz", @@ -15783,6 +15849,11 @@ "node": ">=12.22" } }, + "node_modules/timeago.js": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/timeago.js/-/timeago.js-4.0.2.tgz", + "integrity": "sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==" + }, "node_modules/timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmmirror.com/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -16859,6 +16930,14 @@ "resolved": "https://registry.npmmirror.com/utila/-/utila-0.4.0.tgz", "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache": { "version": "2.4.0", "resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", diff --git a/web/package.json b/web/package.json index 8301f3fb7..9fcffb194 100644 --- a/web/package.json +++ b/web/package.json @@ -22,16 +22,19 @@ "lodash": "^4.17.21", "moment": "^2.30.1", "rc-tween-one": "^3.0.6", + "react-chat-elements": "^12.0.13", "react-i18next": "^14.0.0", "react-infinite-scroll-component": "^6.1.0", "umi": "^4.0.90", - "umi-request": "^1.4.0" + "umi-request": "^1.4.0", + "uuid": "^9.0.1" }, "devDependencies": { "@react-dev-inspector/umi4-plugin": "^2.0.1", "@types/lodash": "^4.14.202", "@types/react": "^18.0.33", "@types/react-dom": "^18.0.11", + "@types/uuid": "^9.0.8", "@umijs/lint": "^4.1.1", "@umijs/plugins": "^4.1.0", "cross-env": "^7.0.3", diff --git a/web/src/assets/svg/chat-configuration-atom.svg b/web/src/assets/svg/chat-configuration-atom.svg new file mode 100644 index 000000000..47e57a246 --- /dev/null +++ b/web/src/assets/svg/chat-configuration-atom.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/layouts/components/header/index.less b/web/src/layouts/components/header/index.less index f27b0720b..9ef620f69 100644 --- a/web/src/layouts/components/header/index.less +++ b/web/src/layouts/components/header/index.less @@ -19,9 +19,9 @@ .appName { vertical-align: middle; font-family: Inter; - font-size: 14px; + font-size: 16px; font-style: normal; - font-weight: 400; + font-weight: 600; line-height: 20px; } diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx index 6e71f8cab..24cfac4cb 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx @@ -68,7 +68,7 @@ const Configuration = () => { const fileList = values.avatar; let avatar; - if (Array.isArray(fileList)) { + if (Array.isArray(fileList) && fileList.length > 0) { avatar = fileList[0].thumbUrl; } diff --git a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx new file mode 100644 index 000000000..4623c6ad7 --- /dev/null +++ b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx @@ -0,0 +1,35 @@ +import { Form, Input } from 'antd'; + +import classNames from 'classnames'; +import { ISegmentedContentProps } from './interface'; + +import styles from './index.less'; + +const AssistantSetting = ({ show }: ISegmentedContentProps) => { + return ( +
+ + + + + + + + + + + + +
+ ); +}; + +export default AssistantSetting; diff --git a/web/src/pages/chat/chat-configuration-modal/editable-cell.tsx b/web/src/pages/chat/chat-configuration-modal/editable-cell.tsx new file mode 100644 index 000000000..f8b65cf9f --- /dev/null +++ b/web/src/pages/chat/chat-configuration-modal/editable-cell.tsx @@ -0,0 +1,103 @@ +import { Form, FormInstance, Input, InputRef } from 'antd'; +import React, { useContext, useEffect, useRef, useState } from 'react'; + +const EditableContext = React.createContext | null>(null); + +interface EditableRowProps { + index: number; +} + +interface Item { + key: string; + name: string; + age: string; + address: string; +} + +export const EditableRow: React.FC = ({ + index, + ...props +}) => { + const [form] = Form.useForm(); + return ( +
+ + + +
+ ); +}; + +interface EditableCellProps { + title: React.ReactNode; + editable: boolean; + children: React.ReactNode; + dataIndex: keyof Item; + record: Item; + handleSave: (record: Item) => void; +} + +export const EditableCell: React.FC = ({ + title, + editable, + children, + dataIndex, + record, + handleSave, + ...restProps +}) => { + const [editing, setEditing] = useState(false); + const inputRef = useRef(null); + const form = useContext(EditableContext)!; + + useEffect(() => { + if (editing) { + inputRef.current!.focus(); + } + }, [editing]); + + const toggleEdit = () => { + setEditing(!editing); + form.setFieldsValue({ [dataIndex]: record[dataIndex] }); + }; + + const save = async () => { + try { + const values = await form.validateFields(); + + toggleEdit(); + handleSave({ ...record, ...values }); + } catch (errInfo) { + console.log('Save failed:', errInfo); + } + }; + + let childNode = children; + + if (editable) { + childNode = editing ? ( + + + + ) : ( +
+ {children} +
+ ); + } + + return {childNode}; +}; diff --git a/web/src/pages/chat/chat-configuration-modal/index.less b/web/src/pages/chat/chat-configuration-modal/index.less new file mode 100644 index 000000000..ab624287b --- /dev/null +++ b/web/src/pages/chat/chat-configuration-modal/index.less @@ -0,0 +1,43 @@ +.chatConfigurationDescription { + font-size: 14px; +} + +.variableContainer { + padding-bottom: 20px; + .variableAlign { + text-align: right; + } + + .variableLabel { + margin-right: 16px; + } + + .variableTable { + margin-top: 14px; + } + .editableRow { + :global(.editable-cell) { + position: relative; + } + + :global(.editable-cell-value-wrap) { + padding: 5px 12px; + cursor: pointer; + height: 22px !important; + } + &:hover { + :global(.editable-cell-value-wrap) { + padding: 4px 11px; + border: 1px solid #d9d9d9; + border-radius: 2px; + } + } + } +} + +.segmentedHidden { + opacity: 0; + height: 0; + width: 0; + margin: 0; +} diff --git a/web/src/pages/chat/chat-configuration-modal/index.tsx b/web/src/pages/chat/chat-configuration-modal/index.tsx new file mode 100644 index 000000000..9c253febb --- /dev/null +++ b/web/src/pages/chat/chat-configuration-modal/index.tsx @@ -0,0 +1,107 @@ +import { ReactComponent as ChatConfigurationAtom } from '@/assets/svg/chat-configuration-atom.svg'; +import { IModalManagerChildrenProps } from '@/components/modal-manager'; +import { Divider, Flex, Form, Modal, Segmented } from 'antd'; +import { SegmentedValue } from 'antd/es/segmented'; +import { useState } from 'react'; +import AssistantSetting from './assistant-setting'; +import ModelSetting from './model-setting'; +import PromptEngine from './prompt-engine'; + +import styles from './index.less'; + +enum ConfigurationSegmented { + AssistantSetting = 'Assistant Setting', + ModelSetting = 'Model Setting', + PromptEngine = 'Prompt Engine', +} + +const segmentedMap = { + [ConfigurationSegmented.AssistantSetting]: AssistantSetting, + [ConfigurationSegmented.ModelSetting]: ModelSetting, + [ConfigurationSegmented.PromptEngine]: PromptEngine, +}; + +const layout = { + labelCol: { span: 6 }, + wrapperCol: { span: 18 }, +}; + +const validateMessages = { + required: '${label} is required!', + types: { + email: '${label} is not a valid email!', + number: '${label} is not a valid number!', + }, + number: { + range: '${label} must be between ${min} and ${max}', + }, +}; + +const ChatConfigurationModal = ({ + visible, + hideModal, +}: IModalManagerChildrenProps) => { + const [form] = Form.useForm(); + const [value, setValue] = useState( + ConfigurationSegmented.AssistantSetting, + ); + + const handleOk = async () => { + const x = await form.validateFields(); + console.info(x); + }; + + const handleCancel = () => { + hideModal(); + }; + + const handleSegmentedChange = (val: SegmentedValue) => { + setValue(val as ConfigurationSegmented); + }; + + const title = ( + + +
+ Chat Configuration +
+ Here, dress up a dedicated assistant for your special knowledge bases! + 💕 +
+
+
+ ); + + return ( + + + +
+ {Object.entries(segmentedMap).map(([key, Element]) => ( + + ))} +
+
+ ); +}; + +export default ChatConfigurationModal; diff --git a/web/src/pages/chat/chat-configuration-modal/interface.ts b/web/src/pages/chat/chat-configuration-modal/interface.ts new file mode 100644 index 000000000..af3ab9625 --- /dev/null +++ b/web/src/pages/chat/chat-configuration-modal/interface.ts @@ -0,0 +1,3 @@ +export interface ISegmentedContentProps { + show: boolean; +} diff --git a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx new file mode 100644 index 000000000..5f76e4ad8 --- /dev/null +++ b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx @@ -0,0 +1,155 @@ +import { Divider, Flex, Form, InputNumber, Select, Slider } from 'antd'; +import classNames from 'classnames'; +import { ISegmentedContentProps } from './interface'; + +import styles from './index.less'; + +const { Option } = Select; + +const ModelSetting = ({ show }: ISegmentedContentProps) => { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +}; + +export default ModelSetting; diff --git a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx new file mode 100644 index 000000000..9df92e4de --- /dev/null +++ b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx @@ -0,0 +1,163 @@ +import { DeleteOutlined } from '@ant-design/icons'; +import { + Button, + Col, + Divider, + Form, + Input, + Row, + Select, + Switch, + Table, + TableProps, +} from 'antd'; +import classNames from 'classnames'; +import { useState } from 'react'; +import { v4 as uuid } from 'uuid'; +import { EditableCell, EditableRow } from './editable-cell'; +import { ISegmentedContentProps } from './interface'; + +import styles from './index.less'; + +interface DataType { + key: string; + optional: boolean; +} + +const { Option } = Select; + +const PromptEngine = ({ show }: ISegmentedContentProps) => { + const [dataSource, setDataSource] = useState([]); + + const components = { + body: { + row: EditableRow, + cell: EditableCell, + }, + }; + + const handleRemove = (key: string) => () => { + const newData = dataSource.filter((item) => item.key !== key); + setDataSource(newData); + }; + + const handleSave = (row: DataType) => { + const newData = [...dataSource]; + const index = newData.findIndex((item) => row.key === item.key); + const item = newData[index]; + newData.splice(index, 1, { + ...item, + ...row, + }); + setDataSource(newData); + }; + + const columns: TableProps['columns'] = [ + { + title: 'key', + dataIndex: 'variable', + key: 'variable', + onCell: (record: DataType) => ({ + record, + editable: true, + dataIndex: 'variable', + title: 'key', + handleSave, + }), + }, + { + title: 'optional', + dataIndex: 'optional', + key: 'optional', + width: 40, + align: 'center', + render() { + return ; + }, + }, + { + title: 'operation', + dataIndex: 'operation', + width: 30, + key: 'operation', + align: 'center', + render(_, record) { + return ; + }, + }, + ]; + + const handleAdd = () => { + setDataSource((state) => [ + ...state, + { + key: uuid(), + variable: '', + optional: true, + }, + ]); + }; + + return ( +
+ + + + +
+ + + + + + + + + {dataSource.length > 0 && ( + + + + styles.editableRow} + /> + + + )} + + + + + + ); +}; + +export default PromptEngine; diff --git a/web/src/pages/chat/chat-container/index.less b/web/src/pages/chat/chat-container/index.less new file mode 100644 index 000000000..147213cff --- /dev/null +++ b/web/src/pages/chat/chat-container/index.less @@ -0,0 +1,3 @@ +.chatContainer { + padding: 0 24px 24px; +} diff --git a/web/src/pages/chat/chat-container/index.tsx b/web/src/pages/chat/chat-container/index.tsx new file mode 100644 index 000000000..bd8d837ba --- /dev/null +++ b/web/src/pages/chat/chat-container/index.tsx @@ -0,0 +1,36 @@ +import { Button, Flex, Input } from 'antd'; +import { ChangeEventHandler, useState } from 'react'; + +import styles from './index.less'; + +const ChatContainer = () => { + const [value, setValue] = useState(''); + + const handlePressEnter = () => { + console.info(value); + }; + + const handleInputChange: ChangeEventHandler = (e) => { + setValue(e.target.value); + }; + + return ( + + xx + + Send + + } + onPressEnter={handlePressEnter} + onChange={handleInputChange} + /> + + ); +}; + +export default ChatContainer; diff --git a/web/src/pages/chat/index.less b/web/src/pages/chat/index.less new file mode 100644 index 000000000..e0cb8555b --- /dev/null +++ b/web/src/pages/chat/index.less @@ -0,0 +1,25 @@ +.chatWrapper { + height: 100%; + + .chatAppWrapper { + width: 288px; + padding: 26px; + } + .chatTitleWrapper { + width: 220px; + padding: 26px 0; + } + + .chatTitle { + padding: 5px 15px; + } + + .chatTitleContent { + padding: 5px 10px; + } + + .divider { + margin: 0; + height: 100%; + } +} diff --git a/web/src/pages/chat/index.tsx b/web/src/pages/chat/index.tsx index f79132287..898f8c322 100644 --- a/web/src/pages/chat/index.tsx +++ b/web/src/pages/chat/index.tsx @@ -1,8 +1,64 @@ +import { FormOutlined } from '@ant-design/icons'; +import { Button, Card, Divider, Flex, Space, Tag } from 'antd'; import { useSelector } from 'umi'; +import ChatContainer from './chat-container'; + +import ModalManager from '@/components/modal-manager'; +import ChatConfigurationModal from './chat-configuration-modal'; +import styles from './index.less'; const Chat = () => { const { name } = useSelector((state: any) => state.chatModel); - return
chat:{name}
; + + return ( + + + + + {({ visible, showModal, hideModal }) => { + return ( + <> + + + + ); + }} + + + + +

Card content

+
+
+
+ + + + + + Chat + 25 + + + + +
today
+
+
+ + +
+ ); }; export default Chat; diff --git a/web/src/pages/chat/message-box.tsx b/web/src/pages/chat/message-box.tsx new file mode 100644 index 000000000..d20722d6b --- /dev/null +++ b/web/src/pages/chat/message-box.tsx @@ -0,0 +1,45 @@ +// RCE CSS +import { MessageList } from 'react-chat-elements'; +import 'react-chat-elements/dist/main.css'; + +const ChatBox = () => { + return ( +
+ {/* */} + + +
+ ); +}; + +export default ChatBox; diff --git a/web/src/pages/knowledge/knowledge-card/index.less b/web/src/pages/knowledge/knowledge-card/index.less index cf2aa02b0..43157d8ed 100644 --- a/web/src/pages/knowledge/knowledge-card/index.less +++ b/web/src/pages/knowledge/knowledge-card/index.less @@ -26,7 +26,7 @@ border: 1px solid rgba(234, 236, 240, 1); box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); padding: 24px; - max-width: 300px; + width: 300px; cursor: pointer; .titleWrapper { diff --git a/web/src/pages/knowledge/knowledge-card/index.tsx b/web/src/pages/knowledge/knowledge-card/index.tsx index 199b76386..8f7b05094 100644 --- a/web/src/pages/knowledge/knowledge-card/index.tsx +++ b/web/src/pages/knowledge/knowledge-card/index.tsx @@ -63,7 +63,7 @@ const KnowledgeCard = ({ item }: IProps) => {
- } /> + } src={item.avatar} /> {
{item.name} -

A comprehensive knowledge base for crafting effective resumes.

+

{item.description}

- {item.doc_num}文档 + {item.doc_num}Docs