+ {result.keywords && result.keywords.length > 0 && (
+
+
Kata Kunci Terdeteksi:
{result.keywords.map((keyword, index) => (
{keyword}
diff --git a/src/components/dashboards/SentimentBadge.tsx b/src/components/dashboards/SentimentBadge.tsx
index fc9c1ad..23aaf97 100644
--- a/src/components/dashboards/SentimentBadge.tsx
+++ b/src/components/dashboards/SentimentBadge.tsx
@@ -3,16 +3,16 @@ import { Badge } from "../ui/badge";
import { cn } from "@/lib/utils";
const getSentimentBadge = (sentiment: Review["sentiment"]) => {
- const styles = {
- positif: "sentiment-positive",
- negatif: "sentiment-negative",
- netral: "sentiment-neutral",
+ const styles: Record = {
+ positive: "sentiment-positive",
+ negative: "sentiment-negative",
+ neutral: "sentiment-neutral",
};
- const labels = {
- positif: "Positif",
- negatif: "Negatif",
- netral: "Netral",
+ const labels: Record = {
+ positive: "Positif",
+ negative: "Negatif",
+ neutral: "Netral",
};
return (
diff --git a/src/hooks/useSentimentForm.ts b/src/hooks/useSentimentForm.ts
new file mode 100644
index 0000000..4fc00bc
--- /dev/null
+++ b/src/hooks/useSentimentForm.ts
@@ -0,0 +1,81 @@
+"use client";
+
+import { useState } from "react";
+import { MODEL_OPTIONS } from "../utils/datas";
+
+export const useSentimentForm = () => {
+ const [selectedModel, setSelectedModel] = useState(MODEL_OPTIONS[2]);
+ const [searchQuery, setSearchQuery] = useState("");
+ const [laptopName, setLaptopName] = useState("");
+ const [text, setText] = useState("");
+ const [isAnalyzing, setIsAnalyzing] = useState(false);
+ const [result, setResult] = useState<{
+ sentiment: string;
+ confidence: number;
+ keywords: string[];
+ } | null>(null);
+ const [error, setError] = useState(null);
+
+ const filteredItems = MODEL_OPTIONS.filter((item) =>
+ item.label.toLowerCase().includes(searchQuery.toLowerCase()),
+ );
+
+ const isFormValid = selectedModel && laptopName.trim() && text.trim();
+
+ const analyzeText = async (e: any) => {
+ e.preventDefault();
+ if (!isFormValid) return;
+
+ setIsAnalyzing(true);
+ setError(null);
+ setResult(null);
+
+ try {
+ const response = await fetch("http://127.0.0.1:8000/predict", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ laptop_name: laptopName,
+ review_text: text,
+ model_type: selectedModel.code,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Error: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ setResult({
+ sentiment: data.sentiment,
+ confidence: data.confidenceScore,
+ keywords: data.keywords || [],
+ });
+ } catch (err) {
+ console.error("Failed to analyze:", err);
+ setError("Gagal menghubungi server. Pastikan API berjalan.");
+ } finally {
+ setIsAnalyzing(false);
+ }
+ };
+
+ return {
+ selectedModel,
+ searchQuery,
+ laptopName,
+ text,
+ isAnalyzing,
+ result,
+ filteredItems,
+ isFormValid,
+ error,
+ analyzeText,
+ setSelectedModel,
+ setSearchQuery,
+ setLaptopName,
+ setText,
+ };
+};
diff --git a/src/utils/datas.ts b/src/utils/datas.ts
new file mode 100644
index 0000000..b3d2964
--- /dev/null
+++ b/src/utils/datas.ts
@@ -0,0 +1,51 @@
+import { Frown, Meh, Smile } from "lucide-react";
+
+export const MODEL_OPTIONS = [
+ {
+ label: "Model XGBoost (Baseline)",
+ code: "baseline",
+ desc: "Raw Data (Imbalanced)",
+ },
+ {
+ label: "Model XGBoost (Tuned)",
+ code: "tuned",
+ desc: "Hyperparameter Tuned",
+ },
+ {
+ label: "Model XGBoost (Optimized)",
+ code: "optimized",
+ desc: "Pipeline (SMOTE + Chi2)",
+ },
+];
+
+export const getSentimentDisplay = (sentiment: string) => {
+ switch (sentiment?.toLowerCase()) {
+ case "positive":
+ return {
+ label: "Positif",
+ icon: Smile,
+ bgClass:
+ "bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800",
+ textClass: "text-green-600 dark:text-green-400",
+ borderClass: "border-green-200",
+ };
+ case "negative":
+ return {
+ label: "Negatif",
+ icon: Frown,
+ bgClass:
+ "bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-800",
+ textClass: "text-red-600 dark:text-red-400",
+ borderClass: "border-red-200",
+ };
+ default:
+ return {
+ label: "Netral",
+ icon: Meh,
+ bgClass:
+ "bg-gray-50 border-gray-200 dark:bg-gray-800 dark:border-gray-700",
+ textClass: "text-gray-600 dark:text-gray-400",
+ borderClass: "border-gray-200",
+ };
+ }
+};