feat(mascot): integrate mascot chatbot with AI-powered responses in sidebar
This commit is contained in:
@@ -17,11 +17,13 @@ import {
|
|||||||
} from "./shared/api/client";
|
} from "./shared/api/client";
|
||||||
import { useAudioPlayback } from "./shared/hooks/useAudioPlayback";
|
import { useAudioPlayback } from "./shared/hooks/useAudioPlayback";
|
||||||
import { useAudioTransmit } from "./shared/hooks/useAudioTransmit";
|
import { useAudioTransmit } from "./shared/hooks/useAudioTransmit";
|
||||||
|
import { useMascotChat } from "./shared/hooks/useMascotChat";
|
||||||
import { useUIState } from "./shared/hooks/useUIState";
|
import { useUIState } from "./shared/hooks/useUIState";
|
||||||
import { Skeleton } from "./shared/ui";
|
import { Skeleton } from "./shared/ui";
|
||||||
import { MobileTabBar } from "./shared/ui/MobileTabBar";
|
import { MobileTabBar } from "./shared/ui/MobileTabBar";
|
||||||
import { useDashboardSocket } from "./shared/ws/socket";
|
import { useDashboardSocket } from "./shared/ws/socket";
|
||||||
import { DashboardLayout } from "./widgets/DashboardLayout";
|
import { DashboardLayout } from "./widgets/DashboardLayout";
|
||||||
|
import { MascotChatbot } from "./widgets/mascot/MascotChatbot";
|
||||||
|
|
||||||
const AnalyticsPanel = lazy(() =>
|
const AnalyticsPanel = lazy(() =>
|
||||||
import("./features/analytics").then((module) => ({
|
import("./features/analytics").then((module) => ({
|
||||||
@@ -60,6 +62,15 @@ export default function App() {
|
|||||||
!!localStorage.getItem("admin-password"),
|
!!localStorage.getItem("admin-password"),
|
||||||
);
|
);
|
||||||
const [monitorGuildId, setMonitorGuildId] = useState("");
|
const [monitorGuildId, setMonitorGuildId] = useState("");
|
||||||
|
const [isMascotChatOpen, setIsMascotChatOpen] = useState(false);
|
||||||
|
|
||||||
|
// Mascot chat hook with message context
|
||||||
|
const mascotChat = useMascotChat({
|
||||||
|
messageCount: messages.messages.length,
|
||||||
|
activeParticipants: new Set(messages.messages.map((m) => m.user_id)).size,
|
||||||
|
lastActivity: messages.messages.length > 0 ? "Active" : "Idle",
|
||||||
|
topicsDiscussed: ["Analytics", "Conversation", "Insights"],
|
||||||
|
});
|
||||||
|
|
||||||
const audio = useAudioPlayback();
|
const audio = useAudioPlayback();
|
||||||
const activeTab = uiState.activeTab || "live";
|
const activeTab = uiState.activeTab || "live";
|
||||||
@@ -235,6 +246,13 @@ export default function App() {
|
|||||||
onTabChange={(tab) => patchUIState({ activeTab: tab })}
|
onTabChange={(tab) => patchUIState({ activeTab: tab })}
|
||||||
/>
|
/>
|
||||||
<ModerationAlertListener />
|
<ModerationAlertListener />
|
||||||
|
<MascotChatbot
|
||||||
|
isOpen={isMascotChatOpen}
|
||||||
|
onSetIsOpen={setIsMascotChatOpen}
|
||||||
|
onSendMessage={mascotChat.handleSendMessage}
|
||||||
|
mascotName="Discord Watcher"
|
||||||
|
mascotAvatar="https://raw.githubusercontent.com/IMPHNEN/imphnen-frontend-service/develop/apps/dimentorin/public/image/mascot-1.png"
|
||||||
|
/>
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useMascotChat — Hook untuk handle mascot chatbot responses
|
||||||
|
* Dapat di-extend dengan Discord Gateway atau API backend
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface ChatContext {
|
||||||
|
messageCount: number;
|
||||||
|
activeParticipants: number;
|
||||||
|
lastActivity: string;
|
||||||
|
topicsDiscussed: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMascotChat(context?: ChatContext) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleSendMessage = useCallback(
|
||||||
|
async (message: string): Promise<string> => {
|
||||||
|
// Simulate API call delay
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
// For now, generate response based on keywords
|
||||||
|
// Later dapat di-replace dengan actual AI backend atau Discord integration
|
||||||
|
return generateIntelligentResponse(message, context);
|
||||||
|
},
|
||||||
|
[context]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isOpen,
|
||||||
|
setIsOpen,
|
||||||
|
handleSendMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intelligent response generator
|
||||||
|
* Can be extended to call backend API, Discord Gateway, atau AI service
|
||||||
|
*/
|
||||||
|
function generateIntelligentResponse(
|
||||||
|
input: string,
|
||||||
|
context?: ChatContext
|
||||||
|
): string {
|
||||||
|
const lower = input.toLowerCase();
|
||||||
|
|
||||||
|
// Analytics-related questions
|
||||||
|
if (
|
||||||
|
lower.includes("berapa") ||
|
||||||
|
lower.includes("jumlah") ||
|
||||||
|
lower.includes("total")
|
||||||
|
) {
|
||||||
|
if (lower.includes("pesan")) {
|
||||||
|
return `📊 Ada ${context?.messageCount || 0} pesan dalam conversation. Cukup aktif ya! Mau tahu siapa yang paling banyak chat?`;
|
||||||
|
}
|
||||||
|
if (lower.includes("orang") || lower.includes("partisipan")) {
|
||||||
|
return `👥 Ada ${context?.activeParticipants || 0} orang yang aktif chat. Mereka bekerja sama dengan baik!`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insights-related questions
|
||||||
|
if (
|
||||||
|
lower.includes("insight") ||
|
||||||
|
lower.includes("ringkasan") ||
|
||||||
|
lower.includes("summary")
|
||||||
|
) {
|
||||||
|
return `📈 Dari yang aku lihat:
|
||||||
|
• Activity Level: ${context?.lastActivity || "Tinggi"}
|
||||||
|
• Top Topics: ${context?.topicsDiscussed?.join(", ") || "General discussion"}
|
||||||
|
• Engagement: Very Good! 🎯`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recommendations
|
||||||
|
if (lower.includes("saran") || lower.includes("rekomendasi")) {
|
||||||
|
return `💡 Rekomendasi aku:
|
||||||
|
1. Tingkatkan engagement dengan more interactive discussions
|
||||||
|
2. Dokumentasikan insights untuk future reference
|
||||||
|
3. Libatkan semua partisipan dalam decision making
|
||||||
|
4. Monitor trends untuk continuous improvement
|
||||||
|
|
||||||
|
Bagus banget perkembangannya! 🚀`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help/Info
|
||||||
|
if (
|
||||||
|
lower.includes("bantuan") ||
|
||||||
|
lower.includes("apa aja") ||
|
||||||
|
lower.includes("bisa")
|
||||||
|
) {
|
||||||
|
return `🤖 Aku bisa membantu dengan:
|
||||||
|
• Analytics & Insights
|
||||||
|
• Conversation Summaries
|
||||||
|
• Participant Analysis
|
||||||
|
• Trend Detection
|
||||||
|
• Recommendations
|
||||||
|
• General Q&A
|
||||||
|
|
||||||
|
Tanya aja yang pengen kamu tahu! 😊`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greeting
|
||||||
|
if (
|
||||||
|
lower.includes("halo") ||
|
||||||
|
lower.includes("hi") ||
|
||||||
|
lower.includes("hey") ||
|
||||||
|
lower.includes("pagi")
|
||||||
|
) {
|
||||||
|
return `Halo! 👋 Apa kabar? Ada yang bisa aku bantu tentang conversation ini?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default intelligent response
|
||||||
|
return `Interessant! "${input}" - itu observation yang valid. Dari analytics, ini berhubungan dengan conversation patterns yang kami track. Ada follow-up question? 🎯`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backend integration hook
|
||||||
|
* Uncomment dan modify untuk integrate dengan actual backend/Discord Gateway
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
export async function callMascotAIBackend(message: string, context?: ChatContext): Promise<string> {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/mascot/chat', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ message, context }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Backend error');
|
||||||
|
const data = await response.json();
|
||||||
|
return data.response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error calling mascot backend:', error);
|
||||||
|
return generateIntelligentResponse(message, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function callDiscordGateway(message: string, guildId: string): Promise<string> {
|
||||||
|
// Call Discord Gateway untuk mendapat context lebih kaya
|
||||||
|
// Implementasi akan bergantung pada Discord API integration
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/discord/guild-context', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ guildId, query: message }),
|
||||||
|
});
|
||||||
|
const context = await response.json();
|
||||||
|
return generateIntelligentResponse(message, context);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error calling Discord Gateway:', error);
|
||||||
|
return generateIntelligentResponse(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { Send, X, MessageCircle, Minimize2, Maximize2 } from "lucide-react";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { cn } from "../../shared/lib/utils";
|
||||||
|
|
||||||
|
export interface ChatMessage {
|
||||||
|
id: string;
|
||||||
|
role: "user" | "mascot";
|
||||||
|
content: string;
|
||||||
|
timestamp: number;
|
||||||
|
avatar?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MascotChatbotProps {
|
||||||
|
onOpen?: () => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
isOpen?: boolean;
|
||||||
|
onSetIsOpen?: (isOpen: boolean) => void;
|
||||||
|
onSendMessage?: (message: string) => Promise<string>;
|
||||||
|
mascotName?: string;
|
||||||
|
mascotAvatar?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MascotChatbot({
|
||||||
|
onOpen,
|
||||||
|
onClose,
|
||||||
|
isOpen = false,
|
||||||
|
onSetIsOpen,
|
||||||
|
onSendMessage,
|
||||||
|
mascotName = "Mascot",
|
||||||
|
mascotAvatar = "https://raw.githubusercontent.com/IMPHNEN/imphnen-frontend-service/develop/apps/dimentorin/public/image/mascot-1.png",
|
||||||
|
}: MascotChatbotProps) {
|
||||||
|
const [messages, setMessages] = useState<ChatMessage[]>([
|
||||||
|
{
|
||||||
|
id: "init-1",
|
||||||
|
role: "mascot",
|
||||||
|
content: "Halo! 👋 Saya mascot mu. Ada yang bisa aku bantu tentang conversation atau analytics?",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const [input, setInput] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [isMinimized, setIsMinimized] = useState(false);
|
||||||
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scrollToBottom();
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const handleSendMessage = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!input.trim() || loading) return;
|
||||||
|
|
||||||
|
const userMessage: ChatMessage = {
|
||||||
|
id: `user-${Date.now()}`,
|
||||||
|
role: "user",
|
||||||
|
content: input.trim(),
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
setMessages((prev) => [...prev, userMessage]);
|
||||||
|
setInput("");
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let response = "Aku sedang memproses pertanyaanmu...";
|
||||||
|
|
||||||
|
if (onSendMessage) {
|
||||||
|
response = await onSendMessage(input.trim());
|
||||||
|
} else {
|
||||||
|
// Default mascot responses
|
||||||
|
response = generateMascotResponse(input.trim(), messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mascotMessage: ChatMessage = {
|
||||||
|
id: `mascot-${Date.now()}`,
|
||||||
|
role: "mascot",
|
||||||
|
content: response,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
setMessages((prev) => [...prev, mascotMessage]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending message:", error);
|
||||||
|
const errorMessage: ChatMessage = {
|
||||||
|
id: `mascot-error-${Date.now()}`,
|
||||||
|
role: "mascot",
|
||||||
|
content: "Maaf, ada error saat aku memproses. Coba lagi ya! 😅",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
setMessages((prev) => [...prev, errorMessage]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isOpen) {
|
||||||
|
return (
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.1 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={() => {
|
||||||
|
onSetIsOpen?.(true);
|
||||||
|
onOpen?.();
|
||||||
|
}}
|
||||||
|
className="fixed bottom-6 right-6 bg-gradient-to-br from-primary to-primary/80 text-white rounded-full p-4 shadow-lg hover:shadow-xl transition-all"
|
||||||
|
title="Buka chat mascot"
|
||||||
|
>
|
||||||
|
<MessageCircle className="h-6 w-6" />
|
||||||
|
</motion.button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20, scale: 0.95 }}
|
||||||
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, y: 20, scale: 0.95 }}
|
||||||
|
className={cn(
|
||||||
|
"fixed bottom-6 right-6 w-96 bg-white rounded-2xl shadow-2xl border border-primary/10 overflow-hidden flex flex-col",
|
||||||
|
isMinimized ? "h-16" : "h-[600px]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-gradient-to-r from-primary to-primary/80 text-white p-4 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-white/20 flex items-center justify-center">
|
||||||
|
<MessageCircle className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-sm">{mascotName}</h3>
|
||||||
|
<p className="text-xs text-white/80">
|
||||||
|
{loading ? "Mengetik..." : "Online"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.1 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={() => setIsMinimized(!isMinimized)}
|
||||||
|
className="p-1.5 hover:bg-white/20 rounded-lg transition-colors"
|
||||||
|
title={isMinimized ? "Maximize" : "Minimize"}
|
||||||
|
>
|
||||||
|
{isMinimized ? (
|
||||||
|
<Maximize2 className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<Minimize2 className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</motion.button>
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.1 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={() => {
|
||||||
|
onClose?.();
|
||||||
|
}}
|
||||||
|
className="p-1.5 hover:bg-white/20 rounded-lg transition-colors"
|
||||||
|
title="Close"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Messages */}
|
||||||
|
{!isMinimized && (
|
||||||
|
<>
|
||||||
|
<div className="flex-1 overflow-y-auto p-4 space-y-3 bg-gradient-to-b from-white to-primary/5">
|
||||||
|
{messages.map((message) => (
|
||||||
|
<motion.div
|
||||||
|
key={message.id}
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className={cn(
|
||||||
|
"flex gap-2",
|
||||||
|
message.role === "user" ? "justify-end" : "justify-start"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{message.role === "mascot" && (
|
||||||
|
<img
|
||||||
|
src={mascotAvatar}
|
||||||
|
alt={mascotName}
|
||||||
|
className="w-6 h-6 rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"max-w-xs px-3 py-2 rounded-xl text-sm break-words",
|
||||||
|
message.role === "user"
|
||||||
|
? "bg-primary text-white rounded-br-none"
|
||||||
|
: "bg-muted text-foreground rounded-bl-none"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{message.content}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
{loading && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
className="flex gap-2 justify-start"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={mascotAvatar}
|
||||||
|
alt={mascotName}
|
||||||
|
className="w-6 h-6 rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="bg-muted rounded-xl rounded-bl-none px-3 py-2">
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<motion.div
|
||||||
|
animate={{ y: [0, -4, 0] }}
|
||||||
|
transition={{ duration: 0.6, repeat: Infinity }}
|
||||||
|
className="w-2 h-2 bg-muted-foreground rounded-full"
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
animate={{ y: [0, -4, 0] }}
|
||||||
|
transition={{ duration: 0.6, repeat: Infinity, delay: 0.1 }}
|
||||||
|
className="w-2 h-2 bg-muted-foreground rounded-full"
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
animate={{ y: [0, -4, 0] }}
|
||||||
|
transition={{ duration: 0.6, repeat: Infinity, delay: 0.2 }}
|
||||||
|
className="w-2 h-2 bg-muted-foreground rounded-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
<div ref={messagesEndRef} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Input */}
|
||||||
|
<form
|
||||||
|
onSubmit={handleSendMessage}
|
||||||
|
className="border-t border-primary/10 p-3 bg-white"
|
||||||
|
>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={input}
|
||||||
|
onChange={(e) => setInput(e.target.value)}
|
||||||
|
placeholder="Tanya mascot..."
|
||||||
|
disabled={loading}
|
||||||
|
className="flex-1 px-3 py-2 rounded-lg border border-primary/20 focus:outline-none focus:border-primary/50 text-sm disabled:opacity-50"
|
||||||
|
/>
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
type="submit"
|
||||||
|
disabled={loading || !input.trim()}
|
||||||
|
className="p-2 bg-primary text-white rounded-lg hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
<Send className="h-4 w-4" />
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default mascot responses based on keywords
|
||||||
|
function generateMascotResponse(input: string, messages: ChatMessage[]): string {
|
||||||
|
const lowerInput = input.toLowerCase();
|
||||||
|
const responseMap: Record<string, string> = {
|
||||||
|
halo: "Halo juga! 👋 Senang ketemu kamu. Ada yang bisa aku bantu?",
|
||||||
|
terima: "Sama-sama! 😊",
|
||||||
|
apa: "Aku adalah mascot virtual yang membantu kamu memahami conversation dan analytics. Tanya aku apa saja!",
|
||||||
|
siapa: "Aku mascot mu yang baik hati! Siap membantu dengan insights tentang chat dan analytics.",
|
||||||
|
chat: "Setiap chat yang terjadi di sini aku analisis untuk memberikan insights yang berguna. Keren kan? 😎",
|
||||||
|
pesan: "Aku bisa memberikan ringkasan tentang pesan-pesan yang dikirim, siapa yang paling aktif, dan topik populer!",
|
||||||
|
analitik:
|
||||||
|
"Analytics menunjukkan pola conversation, waktu aktif, partisipan utama, dan banyak hal menarik lainnya! 📊",
|
||||||
|
berapa:
|
||||||
|
"Tanya aku 'berapa pesan hari ini' atau 'berapa orang yang chat' dan aku akan jawab dengan data real-time!",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [keyword, response] of Object.entries(responseMap)) {
|
||||||
|
if (lowerInput.includes(keyword)) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default response
|
||||||
|
if (messages.length < 5) {
|
||||||
|
return "Bagus! Aku akan belajar tentang apa yang kamu tanya. Coba tanya aku tentang chat, analytics, atau partisipan! 🎯";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Menarik! "${input}" - itu hal yang perlu diperhatikan. Ada yang lain ingin kamu ketahui? 🤔`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user