566 lines
16 KiB
TypeScript
566 lines
16 KiB
TypeScript
"use client";
|
||
|
||
import React, { useEffect, useState } from 'react';
|
||
import { Button, Form, GetProp, Input, InputNumber, Modal, Select, Upload, UploadFile, UploadProps, message } from 'antd';
|
||
import { Loading } from "@/components/dashboard/loading";
|
||
import service, { UserDataStorageName } from '@/lib/http/service';
|
||
import toast from 'react-hot-toast';
|
||
import { StaffsInfo, addStaff } from '@/lib/http/staff';
|
||
import { useRouter } from 'next/navigation';
|
||
import { PlusOutlined, UploadOutlined } from '@ant-design/icons';
|
||
import { useLocalStorage } from '@/lib/hooks/use-local-storage';
|
||
import { UserData } from '@/components/user-menu';
|
||
|
||
|
||
|
||
|
||
|
||
|
||
export interface CreateFormProps {
|
||
onCancel?: () => void;
|
||
onSubmit?: (values: StaffsInfo) => void;
|
||
createModalVisible?: boolean;
|
||
}
|
||
|
||
const FormItem = Form.Item;
|
||
|
||
const formLayout = {
|
||
labelCol: { span: 7 },
|
||
wrapperCol: { span: 13 },
|
||
};
|
||
|
||
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||
|
||
const getBase64 = (file: FileType): Promise<string> =>
|
||
new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.readAsDataURL(file);
|
||
reader.onload = () => resolve(reader.result as string);
|
||
reader.onerror = (error) => reject(error);
|
||
});
|
||
|
||
/**
|
||
* 添加节点
|
||
* @param fields
|
||
*/
|
||
const handleAdd = async (fields: StaffsInfo) => {
|
||
const hide = message.loading('正在添加');
|
||
try {
|
||
// fields.password = md5(fields.password)
|
||
// fields.user_alias = fields.user_name
|
||
console.log("----------:", fields)
|
||
|
||
let result = await addStaff({ ...fields });
|
||
|
||
console.log("---handleAdd-result------:", result)
|
||
hide();
|
||
message.success('添加成功');
|
||
return true;
|
||
} catch (error) {
|
||
hide();
|
||
message.error('添加失败请重试!');
|
||
return false;
|
||
}
|
||
};
|
||
|
||
const CreateForm: React.FC<CreateFormProps> = (props) => {
|
||
const [form] = Form.useForm();
|
||
|
||
const [initLoading, setInitLoading] = useState(false);
|
||
|
||
const {
|
||
onSubmit,
|
||
onCancel,
|
||
createModalVisible,
|
||
} = props;
|
||
|
||
useEffect(() => {
|
||
if (form && !createModalVisible) {
|
||
form.resetFields();
|
||
}
|
||
}, [props.createModalVisible]);
|
||
|
||
|
||
const handleSubmit = () => {
|
||
if (!form) return;
|
||
form.submit();
|
||
};
|
||
|
||
// const handleFinish = (values: JobListItem) => {
|
||
// if (onSubmit) {
|
||
// onSubmit(values);
|
||
// }
|
||
// };
|
||
|
||
const [previewOpen, setPreviewOpen] = useState(false);
|
||
const [previewImage, setPreviewImage] = useState('');
|
||
const [urlImage, setURLImage] = useState('');
|
||
const [uploading, setUploading] = useState(false);
|
||
const [fileList, setFileList] = useState<UploadFile[]>([
|
||
// {
|
||
// uid: '-1',
|
||
// name: 'image.png',
|
||
// status: 'done',
|
||
// url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
|
||
// },
|
||
// {
|
||
// uid: '-xxx',
|
||
// percent: 50,
|
||
// name: 'image.png',
|
||
// status: 'uploading',
|
||
// url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
|
||
// },
|
||
// {
|
||
// uid: '-5',
|
||
// name: 'image.png',
|
||
// status: 'error',
|
||
// },
|
||
]);
|
||
|
||
|
||
const handleFinish = async (value: StaffsInfo) => {
|
||
console.log("onFinish ---", value);
|
||
|
||
|
||
await service.post('/api/v1/customer/create/staff', {
|
||
|
||
// "name":"我的AI员工1号", //员工名称(必填)
|
||
// "org_name":"主板销售部", //组织名称(必填)
|
||
// "tone":"亲切且专业", //语气
|
||
// "output_length":"简短", //回复长度
|
||
// "description":"销冠的潜质", //员工描述
|
||
// "avatar_url":"https://www.pic.com/avatar001.jpg" //头像URL
|
||
|
||
name: value.name,
|
||
org_name: value.org_name,
|
||
tone: value.tone,
|
||
output_length: value.output_length,
|
||
description: value.description,
|
||
avatar_url: value.avatar_url,
|
||
}, {
|
||
headers: {
|
||
// 'Authorization': token
|
||
}
|
||
}).then(function (result: any) {
|
||
|
||
|
||
console.log("result:", result)
|
||
|
||
if (result && result.header.code != 0) {
|
||
|
||
toast.error(result.header.message)
|
||
return
|
||
}
|
||
|
||
// setInitLoading(false);
|
||
|
||
// categoriesRef.current.values
|
||
|
||
|
||
const tags = result.data.list
|
||
|
||
// getNewListFunc(1)
|
||
|
||
|
||
}).catch((err) => {
|
||
// setInitLoading(false);
|
||
|
||
});
|
||
};
|
||
|
||
const handlePreview = async (file: UploadFile) => {
|
||
if (!file.url && !file.preview) {
|
||
file.preview = await getBase64(file.originFileObj as FileType);
|
||
}
|
||
|
||
setPreviewImage(file.url || (file.preview as string));
|
||
setPreviewOpen(true);
|
||
|
||
|
||
};
|
||
|
||
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
|
||
setFileList(newFileList);
|
||
|
||
console.log("=---handleChange----", newFileList)
|
||
|
||
// const formData = new FormData();
|
||
// fileList.forEach((file) => {
|
||
// formData.append('files[]', file as FileType);
|
||
// });
|
||
// setUploading(true);
|
||
// // You can use any AJAX library you like
|
||
// fetch(process.env.NEXT_PUBLIC_FILE_BASE + "/api/v1/customer/upload/file", {
|
||
// method: 'POST',
|
||
// body: formData,
|
||
// })
|
||
// .then((res) => res.json())
|
||
// .then(() => {
|
||
// setFileList([]);
|
||
// message.success('upload successfully.');
|
||
// })
|
||
// .catch(() => {
|
||
// message.error('upload failed.');
|
||
// })
|
||
// .finally(() => {
|
||
// setUploading(false);
|
||
// });
|
||
}
|
||
|
||
|
||
const uploadButton = (
|
||
<button style={{ border: 0, background: 'none' }} type="button">
|
||
<PlusOutlined />
|
||
<div style={{ marginTop: 8 }}>Upload</div>
|
||
</button>
|
||
);
|
||
|
||
const [userData, setUserData] = useLocalStorage(
|
||
UserDataStorageName,
|
||
{
|
||
auth_token: "",
|
||
id: 1,
|
||
login_ip: "",
|
||
login_time: 0,
|
||
role: "",
|
||
user_name: "",
|
||
first_name: "",
|
||
version: ""
|
||
} as UserData
|
||
)
|
||
|
||
const renderContent = () => {
|
||
return (
|
||
<>
|
||
<Form.Item label="版本">
|
||
<Select defaultValue="demo" disabled>
|
||
<Select.Option value="demo">通用版本</Select.Option>
|
||
<Select.Option value="demo2">高级版本</Select.Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="设置组织名称"
|
||
name="org_name"
|
||
rules={[{ required: true }]}
|
||
>
|
||
<Input
|
||
// variant="borderless"
|
||
// className="placeholder:text-[#808080] "
|
||
// style={{
|
||
// background: "#F5F5F5",
|
||
// borderRadius: "0",
|
||
// // width: 360
|
||
|
||
// }}
|
||
placeholder="设置名称"
|
||
/>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="员工名称(必填)"
|
||
name="name"
|
||
rules={[{ required: true }]}
|
||
>
|
||
<Input
|
||
// variant="borderless"
|
||
// className="placeholder:text-[#808080] "
|
||
// style={{
|
||
// background: "#F5F5F5",
|
||
// borderRadius: "0",
|
||
// // width: 360
|
||
|
||
// }}
|
||
placeholder="设置名称"
|
||
/>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="语言模式"
|
||
name="language_mode"
|
||
// rules={[{ required: true }]}
|
||
>
|
||
<Select id="language_mode" placeholder={'请选择语言模式'}>
|
||
<Select.Option value="spoken language">口语</Select.Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="回复语言类型"
|
||
name="language_response"
|
||
// rules={[{ required: true }]}
|
||
>
|
||
<Select id="language_response" placeholder={'请选择回复语言类型'}>
|
||
<Select.Option value="chinese">中文</Select.Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="客服语气"
|
||
name="tone"
|
||
// rules={[{ required: true }]}
|
||
>
|
||
|
||
{/* communication tones(交流语气):友好且真诚friendly and sincere, 专业且稳重professional and cautious, 清晰且直接clear and direct */}
|
||
{/* defaultValue="friendly and sincere" */}
|
||
<Select id="tone" placeholder={'请选择语气'}>
|
||
<Select.Option value="friendly and sincere">友好且真诚</Select.Option>
|
||
<Select.Option value="professional and cautious">专业且稳重</Select.Option>
|
||
<Select.Option value="clear and direct">清晰且直接</Select.Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
|
||
|
||
<Form.Item
|
||
label="回答长度"
|
||
name="output_length"
|
||
// rules={[{ required: true }]}
|
||
>
|
||
<Select id="output_length" placeholder={'请选择回答长度'}>
|
||
<Select.Option value="short">默认(简短)</Select.Option>
|
||
<Select.Option value="medium">中等</Select.Option>
|
||
<Select.Option value="longer">较长</Select.Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="上传LOGO"
|
||
name="avatar_url"
|
||
// valuePropName="fileList"
|
||
// getValueFromEvent={(e) => {
|
||
// if (Array.isArray(e)) {
|
||
// return e;
|
||
// }
|
||
// return e && e.fileList;
|
||
// }}
|
||
// rules={[{ required: true }]}
|
||
>
|
||
{/* <Input
|
||
// variant="borderless"
|
||
// className="placeholder:text-[#808080] "
|
||
// style={{
|
||
// background: "#F5F5F5",
|
||
// borderRadius: "0",
|
||
// // width: 360
|
||
|
||
// }}
|
||
placeholder="设置Logo"
|
||
/> */}
|
||
|
||
<Upload
|
||
action={process.env.NEXT_PUBLIC_FILE_BASE + "/api/v1/customer/upload/file"}
|
||
// action="//jsonplaceholder.typicode.com/posts/"
|
||
accept='*'
|
||
listType="picture"
|
||
// headers={{"Authorization": 'Bearer ' + localStorage.getItem(UserDataStorageName)}}
|
||
// headers={{ "Authorization": localStorage.getItem(UserDataStorageName) }}
|
||
maxCount={1}
|
||
headers={{
|
||
"Authorization": userData.auth_token,
|
||
}}
|
||
data={(file) => {
|
||
// const formData = new FormData()
|
||
// formData.append('files[]', file as FileType)
|
||
return {
|
||
file_data: file,
|
||
file_name: file.name,
|
||
}
|
||
|
||
}}
|
||
defaultFileList={[...fileList]}
|
||
// name='file_data'
|
||
onPreview={handlePreview}
|
||
// onChange={handleChange}
|
||
onChange={(info) => {
|
||
|
||
console.log("-------info----", info)
|
||
if (info.file.status !== 'uploading') {
|
||
console.log(info.file, info.fileList);
|
||
}
|
||
if (info.file.status === 'done') {
|
||
message.success(`${info.file.name} file uploaded successfully`);
|
||
|
||
if (info.file.response.header.code === 0) {
|
||
setURLImage(info.file.response.data.file_url)
|
||
} else {
|
||
message.error(`${info.file.response.header.message}.`);
|
||
}
|
||
|
||
} else if (info.file.status === 'error') {
|
||
message.error(`${info.file.name} file upload failed.`);
|
||
}
|
||
|
||
|
||
|
||
setFileList(info.fileList);
|
||
// setFileList([
|
||
// ...fileList,
|
||
// ...info.fileList,
|
||
// {
|
||
// uid: info.file.originFileObj?.uid || "",
|
||
// name: info.file.name,
|
||
// status: 'done',
|
||
// url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
|
||
// }
|
||
// ]);
|
||
}}
|
||
// previewFile={(file) => {
|
||
// console.log('Your upload file:', file);
|
||
// // Your process logic. Here we just mock to the same file
|
||
|
||
// const formData = new FormData();
|
||
// // fileList.forEach((file) => {
|
||
// // formData.append('files[]', file as FileType);
|
||
// // });
|
||
|
||
// formData.append('files[]', file as FileType);
|
||
|
||
// return fetch(process.env.NEXT_PUBLIC_FILE_BASE + "/api/v1/customer/upload/file", {
|
||
// method: 'POST',
|
||
// body: formData,
|
||
// })
|
||
// .then((res) => res.json())
|
||
// .then(({ thumbnail }) => thumbnail);
|
||
// }}
|
||
beforeUpload={(file) => {
|
||
// setFileList([...fileList, file]);
|
||
|
||
return true;
|
||
}}
|
||
|
||
>
|
||
<Button icon={<UploadOutlined />}>Upload</Button>
|
||
</Upload>
|
||
<br />
|
||
|
||
</Form.Item >
|
||
|
||
<Form.Item label="问候语"
|
||
name="greetings"
|
||
>
|
||
<Input.TextArea
|
||
maxLength={512}
|
||
className=""
|
||
/>
|
||
</Form.Item>
|
||
<Form.Item label="员工描述"
|
||
name="description"
|
||
// wrapperCol={{
|
||
// offset: 2,
|
||
// span: 16
|
||
// }}
|
||
// validateStatus="error"
|
||
// hasFeedback
|
||
// help="Should have something"
|
||
>
|
||
<Input.TextArea
|
||
maxLength={1024}
|
||
// allowClear
|
||
// showCount
|
||
// style={{
|
||
// background: "#fff",
|
||
// borderRadius: "0",
|
||
// border: "none",
|
||
// height: "100px",
|
||
// }}
|
||
className=""
|
||
/>
|
||
</Form.Item>
|
||
|
||
|
||
|
||
{/* <Button type="primary" htmlType="submit" className="bg-[#000] w-full rounded">
|
||
创建AI客服
|
||
|
||
|
||
</Button> */}
|
||
|
||
<Form.Item wrapperCol={{ span: 12, offset: 12 }}>
|
||
<Button
|
||
// disabled={initLoading || !form.formState.isValid}
|
||
|
||
disabled={
|
||
initLoading
|
||
// ||
|
||
// !form.isFieldsTouched(true) ||
|
||
// !!form.getFieldsError().filter(({ errors }) => errors.length).length
|
||
}
|
||
className="mt-4 bg-[#000] text-[#fff]"
|
||
htmlType="submit"
|
||
>
|
||
{initLoading ? <Loading /> : "创建AI客服"}
|
||
</Button>
|
||
</Form.Item>
|
||
|
||
|
||
|
||
|
||
</>
|
||
);
|
||
};
|
||
|
||
const modalFooter = { okText: '保存', onOk: handleSubmit, onCancel };
|
||
|
||
const router = useRouter();
|
||
|
||
return (
|
||
// <Modal
|
||
// forceRender
|
||
// destroyOnClose
|
||
// title="新建职位信息"
|
||
// open={createModalVisible}
|
||
// {...modalFooter}
|
||
// >
|
||
// <Form
|
||
// {...formLayout}
|
||
// form={form}
|
||
// onFinish={handleFinish}
|
||
// >
|
||
// {renderContent()}
|
||
// </Form>
|
||
// </Modal>
|
||
|
||
<div>
|
||
|
||
<Form
|
||
{...formLayout}
|
||
form={form}
|
||
|
||
initialValues={{
|
||
tone: 'friendly and sincere',
|
||
output_length: 'short',
|
||
language_mode: 'spoken language',
|
||
language_response: 'chinese',
|
||
}}
|
||
onFinish={async (e: StaffsInfo) => {
|
||
|
||
// "name":"我的AI员工1号", //员工名称(必填)
|
||
// "org_name":"主板销售部", //组织名称(必填)
|
||
// "tone":"亲切且专业", //语气
|
||
// "language_mode":"口语", //语言模式
|
||
// "language_response":"中文", //回复语言类型
|
||
// "output_length":"简短", //回复长度
|
||
// "description":"销冠的潜质", //员工描述
|
||
// "avatar_url":"https://www.pic.com/avatar001.jpg" //头像URL
|
||
|
||
e.avatar_url = urlImage
|
||
console.log("-----onFinish--------", e)
|
||
if (await handleAdd(e)) {
|
||
// router.push("/manage/staffs")
|
||
|
||
window.location.href = `/manage/staffs`
|
||
}
|
||
|
||
}}
|
||
>
|
||
{renderContent()}
|
||
</Form>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default CreateForm;
|
||
|
||
|