ok
This commit is contained in:
BIN
public/back.png
Normal file
BIN
public/back.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
BIN
public/camera.png
Normal file
BIN
public/camera.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
BIN
public/send.png
Normal file
BIN
public/send.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
BIN
public/upload.png
Normal file
BIN
public/upload.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
119
src/Camera.js
119
src/Camera.js
@@ -5,11 +5,11 @@ const CameraPage = ({ handleClose, handleUploadImage }) => {
|
|||||||
const [isCameraActive, setIsCameraActive] = useState(true);
|
const [isCameraActive, setIsCameraActive] = useState(true);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
|
const [isUploadedFile, setIsUploadedFile] = useState(false); // ✅ NEW STATE
|
||||||
const videoRef = useRef(null);
|
const videoRef = useRef(null);
|
||||||
const canvasRef = useRef(null);
|
const canvasRef = useRef(null);
|
||||||
const fileInputRef = useRef(null);
|
const fileInputRef = useRef(null);
|
||||||
|
|
||||||
// Check if device is mobile
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkMobile = () => {
|
const checkMobile = () => {
|
||||||
setIsMobile(window.innerWidth <= 768);
|
setIsMobile(window.innerWidth <= 768);
|
||||||
@@ -21,7 +21,6 @@ const CameraPage = ({ handleClose, handleUploadImage }) => {
|
|||||||
return () => window.removeEventListener('resize', checkMobile);
|
return () => window.removeEventListener('resize', checkMobile);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Start the camera when the component mounts
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const startCamera = async () => {
|
const startCamera = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -37,7 +36,6 @@ const CameraPage = ({ handleClose, handleUploadImage }) => {
|
|||||||
};
|
};
|
||||||
startCamera();
|
startCamera();
|
||||||
|
|
||||||
// Cleanup camera stream when component unmounts
|
|
||||||
return () => {
|
return () => {
|
||||||
if (videoRef.current && videoRef.current.srcObject) {
|
if (videoRef.current && videoRef.current.srcObject) {
|
||||||
const tracks = videoRef.current.srcObject.getTracks();
|
const tracks = videoRef.current.srcObject.getTracks();
|
||||||
@@ -46,7 +44,6 @@ const CameraPage = ({ handleClose, handleUploadImage }) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Capture the image from the video stream
|
|
||||||
const captureImage = () => {
|
const captureImage = () => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
const video = videoRef.current;
|
const video = videoRef.current;
|
||||||
@@ -61,10 +58,10 @@ const CameraPage = ({ handleClose, handleUploadImage }) => {
|
|||||||
const capturedImage = canvas.toDataURL('image/jpeg');
|
const capturedImage = canvas.toDataURL('image/jpeg');
|
||||||
setImage(capturedImage);
|
setImage(capturedImage);
|
||||||
setIsCameraActive(false);
|
setIsCameraActive(false);
|
||||||
|
setIsUploadedFile(false); // ✅ from camera
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle image upload from file input
|
|
||||||
const handleFileUpload = e => {
|
const handleFileUpload = e => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
@@ -72,15 +69,17 @@ const CameraPage = ({ handleClose, handleUploadImage }) => {
|
|||||||
reader.onloadend = () => {
|
reader.onloadend = () => {
|
||||||
setImage(reader.result);
|
setImage(reader.result);
|
||||||
setIsCameraActive(false);
|
setIsCameraActive(false);
|
||||||
|
setIsUploadedFile(true); // ✅ from file
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cancel the image capture or file upload and restart the camera
|
|
||||||
const cancelCapture = () => {
|
const cancelCapture = () => {
|
||||||
setImage(null);
|
setImage(null);
|
||||||
setIsCameraActive(true);
|
setIsCameraActive(true);
|
||||||
|
setIsUploadedFile(false); // ✅ reset
|
||||||
|
|
||||||
const startCamera = async () => {
|
const startCamera = async () => {
|
||||||
try {
|
try {
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
@@ -96,71 +95,96 @@ const CameraPage = ({ handleClose, handleUploadImage }) => {
|
|||||||
startCamera();
|
startCamera();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Trigger file input click
|
|
||||||
const triggerFileInput = () => {
|
const triggerFileInput = () => {
|
||||||
fileInputRef.current?.click();
|
fileInputRef.current?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const mainContent = (
|
const mainContent = (
|
||||||
<div style={containerStyle}>
|
<div style={containerStyle}>
|
||||||
{/* Camera/Image Display Area */}
|
|
||||||
<div style={cameraContainerStyle}>
|
<div style={cameraContainerStyle}>
|
||||||
{isCameraActive && (
|
{isCameraActive && (
|
||||||
<video ref={videoRef} autoPlay playsInline style={videoStyle} />
|
<video ref={videoRef} autoPlay playsInline style={videoStyle} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isCameraActive && image && (
|
{!isCameraActive && image && (
|
||||||
<img src={image} alt="Captured or Uploaded" style={imageStyle} />
|
<img
|
||||||
|
src={image}
|
||||||
|
alt="Captured or Uploaded"
|
||||||
|
style={{
|
||||||
|
...imageStyle,
|
||||||
|
objectFit: isUploadedFile ? 'contain' : 'cover',
|
||||||
|
backgroundColor: '#000',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Floating Controls */}
|
|
||||||
<div style={controlsStyle}>
|
<div style={controlsStyle}>
|
||||||
{isCameraActive ? (
|
{isCameraActive ? (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={() => {
|
||||||
style={cancelButtonStyle}
|
handleClose();
|
||||||
|
}}
|
||||||
|
style={baseButtonStyle}
|
||||||
aria-label="Cancel and retake"
|
aria-label="Cancel and retake"
|
||||||
>
|
>
|
||||||
❌
|
<img
|
||||||
|
src="/back.png"
|
||||||
|
alt="Kamera"
|
||||||
|
style={{ height: '26px' }}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={captureImage}
|
onClick={captureImage}
|
||||||
style={captureButtonStyle}
|
style={baseButtonStyle}
|
||||||
aria-label="Capture photo"
|
aria-label="Capture photo"
|
||||||
>
|
>
|
||||||
📸
|
<img
|
||||||
|
src="/camera.png"
|
||||||
|
alt="Kamera"
|
||||||
|
style={{ height: '24px' }}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={triggerFileInput}
|
onClick={triggerFileInput}
|
||||||
style={uploadButtonStyle}
|
style={baseButtonStyle}
|
||||||
aria-label="Upload from gallery"
|
aria-label="Upload from gallery"
|
||||||
>
|
>
|
||||||
📁
|
<img
|
||||||
|
src="/upload.png"
|
||||||
|
alt="Kamera"
|
||||||
|
style={{ height: '24px' }}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={cancelCapture}
|
onClick={cancelCapture}
|
||||||
style={cancelButtonStyle}
|
style={baseButtonStyle}
|
||||||
aria-label="Cancel and retake"
|
aria-label="Cancel and retake"
|
||||||
>
|
>
|
||||||
❌
|
<img
|
||||||
|
src="/back.png"
|
||||||
|
alt="Kamera"
|
||||||
|
style={{ height: '26px' }}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleUploadImage(image)} // Pass image data here
|
onClick={() => handleUploadImage(image)}
|
||||||
style={confirmButtonStyle}
|
style={baseButtonStyle}
|
||||||
disabled={uploading}
|
disabled={uploading}
|
||||||
aria-label={uploading ? 'Uploading...' : 'Confirm upload'}
|
aria-label={uploading ? 'Uploading...' : 'Confirm upload'}
|
||||||
>
|
>
|
||||||
{uploading ? '⏳' : '✅'}
|
<img
|
||||||
</button>
|
src="/send.png"
|
||||||
|
alt="Kamera"
|
||||||
|
style={{ height: '24px' }}
|
||||||
|
/> </button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Hidden file input */}
|
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
@@ -170,28 +194,20 @@ const CameraPage = ({ handleClose, handleUploadImage }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Hidden canvas element for capturing the image */}
|
|
||||||
<canvas ref={canvasRef} style={{ display: 'none' }}></canvas>
|
<canvas ref={canvasRef} style={{ display: 'none' }}></canvas>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Desktop layout with left and right sidebars
|
|
||||||
if (!isMobile) {
|
if (!isMobile) {
|
||||||
return (
|
return (
|
||||||
<div style={desktopLayoutStyle}>
|
<div style={desktopLayoutStyle}>
|
||||||
{/* Left Sidebar */}
|
|
||||||
<div style={sidebarStyle}></div>
|
<div style={sidebarStyle}></div>
|
||||||
|
|
||||||
{/* Main content */}
|
|
||||||
<div style={mainContentStyle}>{mainContent}</div>
|
<div style={mainContentStyle}>{mainContent}</div>
|
||||||
|
|
||||||
{/* Right Sidebar */}
|
|
||||||
<div style={sidebarStyle}></div>
|
<div style={sidebarStyle}></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mobile layout (full screen)
|
|
||||||
return mainContent;
|
return mainContent;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,7 +238,6 @@ const videoStyle = {
|
|||||||
const imageStyle = {
|
const imageStyle = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
objectFit: 'cover',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const controlsStyle = {
|
const controlsStyle = {
|
||||||
@@ -249,23 +264,6 @@ const baseButtonStyle = {
|
|||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
};
|
};
|
||||||
|
|
||||||
const captureButtonStyle = {
|
|
||||||
...baseButtonStyle,
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
color: '#000',
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadButtonStyle = {
|
|
||||||
...baseButtonStyle,
|
|
||||||
backgroundColor: '#4CAF50',
|
|
||||||
color: '#fff',
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelButtonStyle = {
|
|
||||||
...baseButtonStyle,
|
|
||||||
backgroundColor: '#f44336',
|
|
||||||
color: '#fff',
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmButtonStyle = {
|
const confirmButtonStyle = {
|
||||||
...baseButtonStyle,
|
...baseButtonStyle,
|
||||||
@@ -273,7 +271,6 @@ const confirmButtonStyle = {
|
|||||||
color: '#fff',
|
color: '#fff',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Desktop styles
|
|
||||||
const desktopLayoutStyle = {
|
const desktopLayoutStyle = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
@@ -289,26 +286,6 @@ const sidebarStyle = {
|
|||||||
borderRight: '1px solid #e0e0e0',
|
borderRight: '1px solid #e0e0e0',
|
||||||
};
|
};
|
||||||
|
|
||||||
const sidebarTitleStyle = {
|
|
||||||
margin: '0 0 30px 0',
|
|
||||||
fontSize: '24px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#333',
|
|
||||||
};
|
|
||||||
|
|
||||||
const sidebarContentStyle = {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '15px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const sidebarTextStyle = {
|
|
||||||
margin: '0',
|
|
||||||
fontSize: '16px',
|
|
||||||
lineHeight: '1.4',
|
|
||||||
color: '#666',
|
|
||||||
};
|
|
||||||
|
|
||||||
const mainContentStyle = {
|
const mainContentStyle = {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
|||||||
266
src/ChatBot.js
266
src/ChatBot.js
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import styles from './ChatBot.module.css';
|
import styles from './ChatBot.module.css';
|
||||||
import Camera from './Camera'
|
import Camera from './Camera';
|
||||||
|
|
||||||
const ChatBot = ({ existingConversation }) => {
|
const ChatBot = ({ existingConversation }) => {
|
||||||
const [messages, setMessages] = useState([
|
const [messages, setMessages] = useState([
|
||||||
@@ -15,22 +15,19 @@ const ChatBot = ({ existingConversation }) => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState('');
|
||||||
|
|
||||||
|
|
||||||
const [isPoppedUp, setIsPoppedUp] = useState('');
|
const [isPoppedUp, setIsPoppedUp] = useState('');
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
const [phoneNumber, setPhoneNumber] = useState('');
|
const [phoneNumber, setPhoneNumber] = useState('');
|
||||||
|
|
||||||
const [isOpenCamera, setIsOpenCamera] = useState(false);
|
const [isOpenCamera, setIsOpenCamera] = useState(false);
|
||||||
useEffect(() => {
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (existingConversation && existingConversation.length > 0) {
|
if (existingConversation && existingConversation.length > 0) {
|
||||||
setMessages(existingConversation);
|
setMessages(existingConversation);
|
||||||
}
|
}
|
||||||
}, [existingConversation])
|
}, [existingConversation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!localStorage.getItem('session')) {
|
if (!localStorage.getItem('session')) {
|
||||||
function generateUUID() {
|
function generateUUID() {
|
||||||
@@ -48,19 +45,107 @@ const ChatBot = ({ existingConversation }) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
function base64ToFile(base64Data, filename) {
|
||||||
|
const arr = base64Data.split(',');
|
||||||
|
const mime = arr[0].match(/:(.*?);/)[1];
|
||||||
|
const bstr = atob(arr[1]);
|
||||||
|
let n = bstr.length;
|
||||||
|
const u8arr = new Uint8Array(n);
|
||||||
|
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = bstr.charCodeAt(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new File([u8arr], filename, { type: mime });
|
||||||
|
}
|
||||||
|
|
||||||
|
const askToBot = async ({ type = 'text', content, tryCount = 0 }) => {
|
||||||
|
const session = JSON.parse(localStorage.getItem('session'));
|
||||||
|
if (!session || !session.sessionId) return;
|
||||||
|
|
||||||
|
let body;
|
||||||
|
let headers;
|
||||||
|
|
||||||
|
const isBase64Image = type === 'image' && typeof content === 'string' && content.startsWith('data:image/');
|
||||||
|
|
||||||
|
if (isBase64Image) {
|
||||||
|
const file = base64ToFile(content, 'photo.jpg');
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('sessionId', session.sessionId);
|
||||||
|
formData.append('lastSeen', new Date().toISOString());
|
||||||
|
formData.append('name', session.name || '');
|
||||||
|
formData.append('phoneNumber', session.phoneNumber || '');
|
||||||
|
formData.append('type', type);
|
||||||
|
formData.append('image', file);
|
||||||
|
|
||||||
|
body = formData;
|
||||||
|
headers = {};
|
||||||
|
} else {
|
||||||
|
body = JSON.stringify({
|
||||||
|
sessionId: session.sessionId,
|
||||||
|
lastSeen: new Date().toISOString(),
|
||||||
|
name: session.name,
|
||||||
|
phoneNumber: session.phoneNumber,
|
||||||
|
pertanyaan: content,
|
||||||
|
type: type,
|
||||||
|
});
|
||||||
|
headers = { 'Content-Type': 'application/json' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://bot.kediritechnopark.com/webhook/master-agent/ask', {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
if (tryCount < 3) {
|
||||||
|
return new Promise((resolve) =>
|
||||||
|
setTimeout(() => resolve(askToBot({ type, content, tryCount: tryCount + 1 })), 3000)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error('Bot unavailable:', error);
|
||||||
|
return { jawaban: 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUploadImage = async (img) => {
|
||||||
|
setIsOpenCamera(false);
|
||||||
|
|
||||||
|
const newMessages = [
|
||||||
|
...messages,
|
||||||
|
{ sender: 'user', img: img, time: getTime() },
|
||||||
|
];
|
||||||
|
setMessages(newMessages);
|
||||||
|
setIsLoading('Menganalisa gambar anda...');
|
||||||
|
|
||||||
|
const data = await askToBot({ type: 'image', content: img });
|
||||||
|
|
||||||
|
const botAnswer = data.jawaban || 'Maaf, saya tidak bisa menganalisis gambar tersebut.';
|
||||||
|
|
||||||
|
setMessages((prev) => [
|
||||||
|
...prev,
|
||||||
|
{ sender: 'bot', text: botAnswer, time: getTime() },
|
||||||
|
]);
|
||||||
|
|
||||||
|
setIsLoading('');
|
||||||
|
};
|
||||||
|
|
||||||
const sendMessage = async (textOverride = null, name, phoneNumber, tryCount = 0) => {
|
const sendMessage = async (textOverride = null, name, phoneNumber, tryCount = 0) => {
|
||||||
const message = textOverride || input.trim();
|
const message = textOverride || input.trim();
|
||||||
if (message === '') return;
|
if (message === '') return;
|
||||||
|
|
||||||
const session = JSON.parse(localStorage.getItem('session'));
|
const session = JSON.parse(localStorage.getItem('session'));
|
||||||
|
if ((!session || !session.name || !session.phoneNumber) && messages.length > 2) {
|
||||||
if ((!session || !session.name || !session.phoneNumber) && messages.length > 2) {
|
setIsPoppedUp(message);
|
||||||
setIsPoppedUp(message); // munculkan form input
|
|
||||||
setInput('');
|
setInput('');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show user's message immediately
|
|
||||||
const newMessages = [
|
const newMessages = [
|
||||||
...messages,
|
...messages,
|
||||||
{ sender: 'user', text: message, time: getTime() },
|
{ sender: 'user', text: message, time: getTime() },
|
||||||
@@ -68,87 +153,68 @@ const ChatBot = ({ existingConversation }) => {
|
|||||||
|
|
||||||
setMessages(newMessages);
|
setMessages(newMessages);
|
||||||
setInput('');
|
setInput('');
|
||||||
|
setIsLoading('Mengetik...');
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
try {
|
||||||
// Send to backend
|
const data = await askToBot({ type: 'text', content: message, tryCount });
|
||||||
const response = await fetch('https://bot.kediritechnopark.com/webhook/master-agent/ask', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ pertanyaan: message, sessionId: JSON.parse(localStorage.getItem('session')).sessionId, lastSeen: new Date().toISOString(), name: JSON.parse(localStorage.getItem('session')).name, phoneNumber: JSON.parse(localStorage.getItem('session')).phoneNumber }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
console.log(data)
|
|
||||||
// Assuming your backend sends back something like: { answer: "text" }
|
|
||||||
// Adjust this according to your actual response shape
|
|
||||||
const botAnswer = data.jawaban || 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti';
|
const botAnswer = data.jawaban || 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti';
|
||||||
|
|
||||||
// Add bot's reply
|
|
||||||
setMessages(prev => [
|
setMessages(prev => [
|
||||||
...prev,
|
...prev,
|
||||||
{ sender: 'bot', text: botAnswer, time: getTime() },
|
{ sender: 'bot', text: botAnswer, time: getTime() },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(tryCount)
|
console.error('Error sending message:', error);
|
||||||
if (tryCount > 3) {
|
if (tryCount >= 3) {
|
||||||
// Add bot's error reply
|
|
||||||
setMessages(prev => [
|
setMessages(prev => [
|
||||||
...prev,
|
...prev,
|
||||||
{ sender: 'bot', text: 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti', time: getTime() },
|
{ sender: 'bot', text: 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti', time: getTime() },
|
||||||
]);
|
]);
|
||||||
setIsLoading(false);
|
setIsLoading('');
|
||||||
return;
|
} else {
|
||||||
|
setTimeout(() => sendMessage(message, name, phoneNumber, tryCount + 1), 3000);
|
||||||
}
|
}
|
||||||
setTimeout(() => sendMessage(message, name, phoneNumber, tryCount + 1), 3000);
|
|
||||||
|
|
||||||
console.error('Fetch error:', error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function formatBoldText(text) {
|
|
||||||
const parts = text.split(/(\*\*[^\*]+\*\*)/g);
|
|
||||||
|
|
||||||
return parts.flatMap((part, index) => {
|
function formatBoldText(text) {
|
||||||
const elements = [];
|
const parts = text.split(/(\*\*[^\*]+\*\*)/g);
|
||||||
|
|
||||||
if (part.startsWith('**') && part.endsWith('**')) {
|
return parts.flatMap((part, index) => {
|
||||||
// Bold text
|
const elements = [];
|
||||||
part = part.slice(2, -2);
|
|
||||||
part.split('\n').forEach((line, i) => {
|
|
||||||
if (i > 0) elements.push(<br key={`br-bold-${index}-${i}`} />);
|
|
||||||
elements.push(<strong key={`bold-${index}-${i}`}>{line}</strong>);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Normal text
|
|
||||||
part.split('\n').forEach((line, i) => {
|
|
||||||
if (i > 0) elements.push(<br key={`br-${index}-${i}`} />);
|
|
||||||
elements.push(<span key={`text-${index}-${i}`}>{line}</span>);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return elements;
|
if (part.startsWith('**') && part.endsWith('**')) {
|
||||||
});
|
part = part.slice(2, -2);
|
||||||
}
|
part.split('\n').forEach((line, i) => {
|
||||||
|
if (i > 0) elements.push(<br key={`br-bold-${index}-${i}`} />);
|
||||||
|
elements.push(<strong key={`bold-${index}-${i}`}>{line}</strong>);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
part.split('\n').forEach((line, i) => {
|
||||||
|
if (i > 0) elements.push(<br key={`br-${index}-${i}`} />);
|
||||||
|
elements.push(<span key={`text-${index}-${i}`}>{line}</span>);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const handleUploadImage = (e) => {
|
return elements;
|
||||||
console.log(e)
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.chatContainer} >
|
<div className={styles.chatContainer}>
|
||||||
<div className={styles.chatHeader}>
|
<div className={styles.chatHeader}>
|
||||||
<img src="/dermalounge.jpg" alt="Bot Avatar" />
|
<img src="/dermalounge.jpg" alt="Bot Avatar" />
|
||||||
<strong>DERMALOUNGE</strong>
|
<strong>DERMALOUNGE</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.chatBody}>
|
<div className={styles.chatBody}>
|
||||||
|
{isLoading != '' && (
|
||||||
{isLoading && (
|
|
||||||
<div className={`${styles.messageRow} ${styles.bot}`}>
|
<div className={`${styles.messageRow} ${styles.bot}`}>
|
||||||
<div className={`${styles.message} ${styles.bot}`}>
|
<div className={`${styles.message} ${styles.bot}`}>
|
||||||
<em>Mengetik...</em>
|
<em>{isLoading}</em>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -159,15 +225,18 @@ const handleUploadImage = (e) => {
|
|||||||
>
|
>
|
||||||
<div className={`${styles.message} ${styles[msg.sender]}`}>
|
<div className={`${styles.message} ${styles[msg.sender]}`}>
|
||||||
{msg.sender !== 'bot'
|
{msg.sender !== 'bot'
|
||||||
? msg.text
|
? (msg.text ?
|
||||||
: (() => {
|
msg.text
|
||||||
try {
|
:
|
||||||
return formatBoldText(msg.text); // Apply formatting here
|
<img style={{ height: '160px', borderRadius: '12px' }} src={msg.img} />
|
||||||
} catch (e) {
|
)
|
||||||
return msg.text;
|
: (() => {
|
||||||
}
|
try {
|
||||||
})()}
|
return formatBoldText(msg.text);
|
||||||
|
} catch (e) {
|
||||||
|
return msg.text;
|
||||||
|
}
|
||||||
|
})()}
|
||||||
{msg.quickReplies && (
|
{msg.quickReplies && (
|
||||||
<div className={styles.quickReplies}>
|
<div className={styles.quickReplies}>
|
||||||
{msg.quickReplies.map((reply, i) => (
|
{msg.quickReplies.map((reply, i) => (
|
||||||
@@ -179,14 +248,14 @@ const handleUploadImage = (e) => {
|
|||||||
{reply}
|
{reply}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div
|
<div
|
||||||
className={styles.quickReply}
|
className={styles.quickReply}
|
||||||
onClick={() => setIsOpenCamera(true)}
|
onClick={() => setIsOpenCamera(true)}
|
||||||
style={{color: 'white', backgroundColor: '#075e54', display: 'flex', flexDirection: 'row', alignItems:'center'}}
|
style={{ color: 'white', backgroundColor: '#075e54', display: 'flex', flexDirection: 'row', alignItems: 'center' }}
|
||||||
>
|
>
|
||||||
<img style={{marginRight: '5px', height: '14px', filter: 'invert(1)'}}src={'/face.png'}/>
|
<img style={{ marginRight: '5px', height: '14px', filter: 'invert(1)' }} src={'/camera.png'} />
|
||||||
Analisa Kulit
|
Analisa Gambar
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={styles.timestamp}>{msg.time}</div>
|
<div className={styles.timestamp}>{msg.time}</div>
|
||||||
@@ -195,21 +264,34 @@ const handleUploadImage = (e) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.chatInput} /*/style={{ visibility: readOnly ? 'hidden' : 'visible'}}/*/>
|
<div className={styles.chatInput}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Ketik pesan..."
|
placeholder="Ketik pesan..."
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
|
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
|
||||||
disabled={isLoading}
|
disabled={isLoading != ''}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button onClick={() => sendMessage()} disabled={isLoading}>
|
<button onClick={() => sendMessage()} style={{marginLeft: '-40px'}}disabled={isLoading!=''}>
|
||||||
Kirim
|
<img
|
||||||
|
src="/send.png"
|
||||||
|
alt="Kirim"
|
||||||
|
style={{ height: '20px', filter: 'invert(1)' }}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button onClick={() => setIsOpenCamera(true)} disabled={isLoading!=''}>
|
||||||
|
<img
|
||||||
|
src="/camera.png"
|
||||||
|
alt="Kamera"
|
||||||
|
style={{ height: '18px', filter: 'invert(1)' }}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{isPoppedUp != '' &&
|
|
||||||
|
{isPoppedUp !== '' &&
|
||||||
<div className={styles.PopUp}>
|
<div className={styles.PopUp}>
|
||||||
<div className={`${styles.message} ${styles['bot']}`}>
|
<div className={`${styles.message} ${styles['bot']}`}>
|
||||||
Untuk bisa membantu Anda lebih jauh, boleh saya tahu nama dan nomor telepon Anda?
|
Untuk bisa membantu Anda lebih jauh, boleh saya tahu nama dan nomor telepon Anda?
|
||||||
@@ -218,7 +300,6 @@ const handleUploadImage = (e) => {
|
|||||||
<input
|
<input
|
||||||
className={styles.quickReply}
|
className={styles.quickReply}
|
||||||
placeholder="Nama Lengkapmu"
|
placeholder="Nama Lengkapmu"
|
||||||
onFocus={() => console.log('Nama focused')}
|
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
maxLength={40}
|
maxLength={40}
|
||||||
@@ -235,14 +316,11 @@ const handleUploadImage = (e) => {
|
|||||||
value={phoneNumber}
|
value={phoneNumber}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
// Hanya angka, maksimal 11 karakter
|
|
||||||
if (/^\d{0,11}$/.test(value)) {
|
if (/^\d{0,11}$/.test(value)) {
|
||||||
setPhoneNumber(value);
|
setPhoneNumber(value);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onFocus={() => console.log('Telepon focused')}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -250,13 +328,11 @@ const handleUploadImage = (e) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (name.length > 2 && phoneNumber.length >= 10) {
|
if (name.length > 2 && phoneNumber.length >= 10) {
|
||||||
const sessionData = JSON.parse(localStorage.getItem('session')) || {};
|
const sessionData = JSON.parse(localStorage.getItem('session')) || {};
|
||||||
|
|
||||||
sessionData.name = name;
|
sessionData.name = name;
|
||||||
sessionData.phoneNumber = phoneNumber;
|
sessionData.phoneNumber = phoneNumber;
|
||||||
|
|
||||||
localStorage.setItem('session', JSON.stringify(sessionData));
|
localStorage.setItem('session', JSON.stringify(sessionData));
|
||||||
setIsPoppedUp('')
|
setIsPoppedUp('');
|
||||||
sendMessage(isPoppedUp)
|
sendMessage(isPoppedUp);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -266,7 +342,13 @@ const handleUploadImage = (e) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{isOpenCamera && <Camera handleClose={()=>setIsOpenCamera(false)} handleUploadImage={(e)=>handleUploadImage(e)}/>}
|
|
||||||
|
{isOpenCamera && (
|
||||||
|
<Camera
|
||||||
|
handleClose={() => setIsOpenCamera(false)}
|
||||||
|
handleUploadImage={(e) => handleUploadImage(e)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user