ok
This commit is contained in:
@@ -9,17 +9,13 @@ const ChatBot = ({ existingConversation }) => {
|
||||
sender: 'bot',
|
||||
text: 'Hai! Saya Alle, asisten virtual Dermalounge Clinic. Ada yang bisa Alle bantu hari ini?',
|
||||
time: getTime(),
|
||||
quickReplies: [
|
||||
'Konsultasi Estetik',
|
||||
'Konsultasi Kulit & Kelamin'
|
||||
],
|
||||
quickReplies: ['Konsultasi Estetik', 'Konsultasi Kulit & Kelamin'],
|
||||
},
|
||||
]);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
const navigate = useNavigate();
|
||||
const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
|
||||
const [input, setInput] = useState('');
|
||||
const [isLoading, setIsLoading] = useState('');
|
||||
@@ -45,8 +41,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
|
||||
const sessionId = generateUUID();
|
||||
const dateNow = new Date().toISOString();
|
||||
|
||||
sessionStorage.setItem('session', JSON.stringify({ sessionId: sessionId, lastSeen: dateNow }))
|
||||
sessionStorage.setItem('session', JSON.stringify({ sessionId, lastSeen: dateNow }));
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -56,11 +51,9 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -70,7 +63,6 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
|
||||
let body;
|
||||
let headers;
|
||||
|
||||
const isBase64Image = type === 'image' && typeof content === 'string' && content.startsWith('data:image/');
|
||||
|
||||
if (isBase64Image) {
|
||||
@@ -82,7 +74,6 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
formData.append('phoneNumber', session.phoneNumber || '');
|
||||
formData.append('type', type);
|
||||
formData.append('image', file);
|
||||
|
||||
body = formData;
|
||||
headers = {};
|
||||
} else {
|
||||
@@ -103,7 +94,6 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
@@ -119,7 +109,14 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
};
|
||||
|
||||
const handleUploadImage = async (img) => {
|
||||
setSearchParams({ })
|
||||
const session = JSON.parse(sessionStorage.getItem('session'));
|
||||
if (!session?.name || !session?.phoneNumber) {
|
||||
setIsPoppedUp(img); // simpan gambar untuk dikirim setelah user isi data
|
||||
setSearchParams({});
|
||||
return;
|
||||
}
|
||||
|
||||
setSearchParams({});
|
||||
const newMessages = [
|
||||
...messages,
|
||||
{ sender: 'user', img: img, time: getTime() },
|
||||
@@ -128,14 +125,12 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
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('');
|
||||
};
|
||||
|
||||
@@ -150,25 +145,19 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
return;
|
||||
}
|
||||
|
||||
const newMessages = [
|
||||
...messages,
|
||||
{ sender: 'user', text: message, time: getTime() },
|
||||
];
|
||||
|
||||
const newMessages = [...messages, { sender: 'user', text: message, time: getTime() }];
|
||||
setMessages(newMessages);
|
||||
setInput('');
|
||||
setIsLoading('Mengetik...');
|
||||
|
||||
try {
|
||||
const data = await askToBot({ type: 'text', content: message, tryCount });
|
||||
|
||||
const botAnswer = data.jawaban || 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti';
|
||||
|
||||
setMessages(prev => [
|
||||
...prev,
|
||||
{ sender: 'bot', text: botAnswer, time: getTime() },
|
||||
]);
|
||||
|
||||
setIsLoading('');
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error);
|
||||
@@ -186,10 +175,8 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
|
||||
function formatBoldText(text) {
|
||||
const parts = text.split(/(\*\*[^\*]+\*\*)/g);
|
||||
|
||||
return parts.flatMap((part, index) => {
|
||||
const elements = [];
|
||||
|
||||
if (part.startsWith('**') && part.endsWith('**')) {
|
||||
part = part.slice(2, -2);
|
||||
part.split('\n').forEach((line, i) => {
|
||||
@@ -202,7 +189,6 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
elements.push(<span key={`text-${index}-${i}`}>{line}</span>);
|
||||
});
|
||||
}
|
||||
|
||||
return elements;
|
||||
});
|
||||
}
|
||||
@@ -215,7 +201,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
</div>
|
||||
|
||||
<div className={styles.chatBody}>
|
||||
{isLoading != '' && (
|
||||
{isLoading !== '' && (
|
||||
<div className={`${styles.messageRow} ${styles.bot}`}>
|
||||
<div className={`${styles.message} ${styles.bot}`}>
|
||||
<em>{isLoading}</em>
|
||||
@@ -223,39 +209,30 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
</div>
|
||||
)}
|
||||
{messages.slice().reverse().map((msg, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`${styles.messageRow} ${styles[msg.sender]}`}
|
||||
>
|
||||
<div key={index} className={`${styles.messageRow} ${styles[msg.sender]}`}>
|
||||
<div className={`${styles.message} ${styles[msg.sender]}`}>
|
||||
{msg.sender !== 'bot'
|
||||
? (msg.text ?
|
||||
msg.text
|
||||
:
|
||||
<img style={{ maxHeight: '300px', maxWidth: '240px', borderRadius: '12px' }} src={msg.img} />
|
||||
)
|
||||
: (() => {
|
||||
{msg.sender !== 'bot' ? (
|
||||
msg.text ? msg.text : <img style={{ maxHeight: '300px', maxWidth: '240px', borderRadius: '12px' }} src={msg.img} />
|
||||
) : (
|
||||
(() => {
|
||||
try {
|
||||
return formatBoldText(msg.text);
|
||||
} catch (e) {
|
||||
return msg.text;
|
||||
}
|
||||
})()}
|
||||
})()
|
||||
)}
|
||||
{msg.quickReplies && (
|
||||
<div className={styles.quickReplies}>
|
||||
{msg.quickReplies.map((reply, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={styles.quickReply}
|
||||
onClick={() => sendMessage(reply)}
|
||||
>
|
||||
<div key={i} className={styles.quickReply} onClick={() => sendMessage(reply)}>
|
||||
{reply}
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
className={styles.quickReply}
|
||||
onClick={() => setSearchParams({ camera: 'open' })}
|
||||
style={{ color: 'white', backgroundColor: '#075e54', display: 'flex', flexDirection: 'row', alignItems: 'center' }}
|
||||
style={{ color: 'white', backgroundColor: '#075e54', display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
<img style={{ marginRight: '5px', height: '14px', filter: 'invert(1)' }} src={'/camera.png'} />
|
||||
Analisa Gambar
|
||||
@@ -275,27 +252,17 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
|
||||
disabled={isLoading != ''}
|
||||
/>
|
||||
|
||||
<button onClick={() => sendMessage()} style={{marginLeft: '-40px'}}disabled={isLoading!=''}>
|
||||
<img
|
||||
src="/send.png"
|
||||
alt="Kirim"
|
||||
style={{ height: '20px', filter: 'invert(1)' }}
|
||||
disabled={isLoading !== ''}
|
||||
/>
|
||||
<button onClick={() => sendMessage()} style={{ marginLeft: '-40px' }} disabled={isLoading !== ''}>
|
||||
<img src="/send.png" alt="Kirim" style={{ height: '20px', filter: 'invert(1)' }} />
|
||||
</button>
|
||||
|
||||
<button onClick={() => setSearchParams({ camera: 'open' })} disabled={isLoading!=''}>
|
||||
<img
|
||||
src="/camera.png"
|
||||
alt="Kamera"
|
||||
style={{ height: '18px', filter: 'invert(1)' }}
|
||||
/>
|
||||
<button onClick={() => setSearchParams({ camera: 'open' })} disabled={isLoading !== ''}>
|
||||
<img src="/camera.png" alt="Kamera" style={{ height: '18px', filter: 'invert(1)' }} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isPoppedUp !== '' &&
|
||||
{isPoppedUp !== '' && (
|
||||
<div className={styles.PopUp}>
|
||||
<div className={`${styles.message} ${styles['bot']}`}>
|
||||
Untuk bisa membantu Anda lebih jauh, boleh saya tahu nama dan nomor telepon Anda?
|
||||
@@ -326,7 +293,6 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={styles.nextButton}
|
||||
onClick={() => {
|
||||
@@ -336,8 +302,12 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
sessionData.phoneNumber = phoneNumber;
|
||||
sessionStorage.setItem('session', JSON.stringify(sessionData));
|
||||
setIsPoppedUp('');
|
||||
if (typeof isPoppedUp === 'string' && isPoppedUp.startsWith('data:image/')) {
|
||||
handleUploadImage(isPoppedUp);
|
||||
} else {
|
||||
sendMessage(isPoppedUp);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
Lanjut
|
||||
@@ -345,7 +315,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
|
||||
{isOpenCamera && (
|
||||
<Camera
|
||||
|
||||
@@ -22,6 +22,7 @@ const Dashboard = () => {
|
||||
|
||||
const [weeklyData, setWeeklyData] = useState([]);
|
||||
const [allTimeData, setAllTimeData] = useState([]);
|
||||
const [faceAnalystList, setFaceAnalystList] = useState([]);
|
||||
|
||||
const [conversations, setConversations] = useState([]);
|
||||
const [followUps, setFollowUps] = useState([]);
|
||||
@@ -318,6 +319,8 @@ function parseGraphData(graph) {
|
||||
try{
|
||||
console.log(data);
|
||||
setDiscussedTopics(data[0]?.graph[0]?.json?.result?.topics)
|
||||
setFaceAnalystList(data[0]?.graph[0]?.json?.result?.analyst_counter);
|
||||
|
||||
setFollowUps(data[0]?.graph[0]?.json?.result?.interested_users)
|
||||
setFileList(data[0]?.files)
|
||||
setUpdateDetected(data[1]?.updateDetected)
|
||||
@@ -437,7 +440,7 @@ function parseGraphData(graph) {
|
||||
};
|
||||
|
||||
const openTopicsModal = () => {
|
||||
setModalContent(<DiscussedTopics topics={discussedTopics} />);
|
||||
setModalContent(<DiscussedTopics topics={discussedTopics} faceAnalystList={faceAnalystList} />);
|
||||
};
|
||||
|
||||
const handleDeleteFile = async (key) => {
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
// DiscussedTopics.js
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import styles from './DiscussedTopics.module.css';
|
||||
|
||||
const DiscussedTopics = ({ topics }) => {
|
||||
const DiscussedTopics = ({ topics, faceAnalystList }) => {
|
||||
const [activeTab, setActiveTab] = useState('topics'); // 'topics' or 'face'
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<h2 className={styles.title}>Top Topic</h2>
|
||||
<div className={styles.tabButtons}>
|
||||
<button
|
||||
className={`${styles.tabButton} ${activeTab === 'topics' ? styles.activeTab : ''}`}
|
||||
onClick={() => setActiveTab('topics')}
|
||||
>
|
||||
Top Topic
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.tabButton} ${activeTab === 'face' ? styles.activeTab : ''}`}
|
||||
onClick={() => setActiveTab('face')}
|
||||
>
|
||||
Face Analyst Report
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.resultCount}>
|
||||
{topics.length} topik
|
||||
{activeTab === 'topics'
|
||||
? `${topics.length} topik`
|
||||
: `${faceAnalystList.length} report`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.grid}>
|
||||
{topics.length === 0 ? (
|
||||
{activeTab === 'topics' ? (
|
||||
topics.length === 0 ? (
|
||||
<div className={styles.emptyState}>
|
||||
<div className={styles.emptyIcon}>💬</div>
|
||||
<p>Discussed Topic Is Empty</p>
|
||||
@@ -30,6 +48,22 @@ const DiscussedTopics = ({ topics }) => {
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)
|
||||
) : faceAnalystList.length === 0 ? (
|
||||
<div className={styles.emptyState}>
|
||||
<div className={styles.emptyIcon}>🧑⚕️</div>
|
||||
<p>No Face Analyst Report</p>
|
||||
</div>
|
||||
) : (
|
||||
faceAnalystList.map((item, idx) => (
|
||||
<div key={idx} className={styles.card}>
|
||||
<div className={styles.cardContent}>
|
||||
<h3 className={styles.topicName}>{item.name}</h3>
|
||||
<p className={styles.description}>{item.description}</p>
|
||||
<p className={styles.phone}>📞 {item.phone_number}</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -185,3 +185,34 @@
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.tabButtons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tabButton {
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.activeTab {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 6px;
|
||||
font-size: 0.95rem;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.phone {
|
||||
margin-top: 4px;
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user