refactor: clean up spaghetti code
This commit is contained in:
parent
169b5050a3
commit
e30e126e0d
|
|
@ -28,10 +28,10 @@ async function main() {
|
||||||
modelName: "Model XGBoost (Optimized)",
|
modelName: "Model XGBoost (Optimized)",
|
||||||
description:
|
description:
|
||||||
"Model final menggunakan teknik SMOTE untuk menyeimbangkan kelas, seleksi fitur Chi-Square, dan optimasi Grid Search.",
|
"Model final menggunakan teknik SMOTE untuk menyeimbangkan kelas, seleksi fitur Chi-Square, dan optimasi Grid Search.",
|
||||||
accuracy: 0.72,
|
accuracy: 0.73,
|
||||||
macroF1: 0.66,
|
macroF1: 0.67,
|
||||||
f1Negative: 0.68,
|
f1Negative: 0.68,
|
||||||
f1Neutral: 0.43,
|
f1Neutral: 0.45,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,12 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
|
||||||
import { AnalysisResults, AnalyzeFormData } from "../types";
|
import { AnalysisResults, AnalyzeFormData } from "../types";
|
||||||
import {
|
import {
|
||||||
scrapeProduct,
|
scrapeProduct,
|
||||||
getAIRecommendation,
|
getAIRecommendation,
|
||||||
} from "../services/analyze.service";
|
} from "../services/analyze.service";
|
||||||
import { analyzeSchema } from "../app/validation/analyze.schema"; // Sesuaikan path-nya
|
import { analyzeSchema } from "../app/validation/analyze.schema";
|
||||||
import { getAnotherUserData } from "../app/profile/lib/action";
|
|
||||||
import prisma from "@/lib/prisma";
|
|
||||||
import { getMetricId } from "../services/metric.service";
|
import { getMetricId } from "../services/metric.service";
|
||||||
|
|
||||||
export const useAnalyseText = () => {
|
export const useAnalyseText = () => {
|
||||||
|
|
@ -182,7 +179,7 @@ export const useAnalyseText = () => {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.name === "AbortError" || signal.aborted) {
|
if (error.name === "AbortError" || signal.aborted) {
|
||||||
console.log("🛠️ Request dibatalkan secara aman.");
|
console.log("🛠️ Request dibatalkan secara aman.");
|
||||||
return; // Keluar dari fungsi tanpa memunculkan alert error
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error("Analysis Error:", error);
|
console.error("Analysis Error:", error);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import { ModelDB, Review, StatCounts } from "@/src/types";
|
import { ModelDB, Review, StatCounts } from "@/src/types";
|
||||||
import { getClassificationReport } from "../app/dashboard/lib/actions";
|
import { getClassificationReport } from "../app/dashboard/lib/actions";
|
||||||
|
import { sentimentStatsPath } from "../utils/const";
|
||||||
|
|
||||||
export const useDashboards = () => {
|
export const useDashboards = () => {
|
||||||
const [selectedBrand, setSelectedBrand] = useState<string | null>(null);
|
const [selectedBrand, setSelectedBrand] = useState<string | null>(null);
|
||||||
|
|
@ -20,7 +21,7 @@ export const useDashboards = () => {
|
||||||
async function fetchStats() {
|
async function fetchStats() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const res = await fetch("/api/review/sentiment-stats");
|
const res = await fetch(sentimentStatsPath);
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
|
|
||||||
const statsData = json.data;
|
const statsData = json.data;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { productPath } from "../utils/const";
|
||||||
|
|
||||||
export const useHeader = () => {
|
export const useHeader = () => {
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
|
@ -17,7 +18,7 @@ export const useHeader = () => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
const getProductCount = async () => {
|
const getProductCount = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/product");
|
const res = await fetch(productPath);
|
||||||
if (!res.ok) throw new Error("Failed to fetch product count");
|
if (!res.ok) throw new Error("Failed to fetch product count");
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { useState, useEffect } from "react";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { ProfileClientProps, ProfileFormData, ProfileState } from "@/src/types";
|
import { ProfileClientProps, ProfileFormData, ProfileState } from "@/src/types";
|
||||||
import { Brand, OS, Profession } from "@prisma/client";
|
import { OS, Profession } from "@prisma/client";
|
||||||
import { brandFormat } from "../utils/datas";
|
import { brandFormat } from "../utils/datas";
|
||||||
|
|
||||||
export const useProfileClient = (props: ProfileClientProps) => {
|
export const useProfileClient = (props: ProfileClientProps) => {
|
||||||
|
|
@ -47,7 +47,7 @@ export const useProfileClient = (props: ProfileClientProps) => {
|
||||||
(newData.profession as Profession) || prev.preference.profession,
|
(newData.profession as Profession) || prev.preference.profession,
|
||||||
|
|
||||||
preferredBrand:
|
preferredBrand:
|
||||||
(newData.preferredBrand ) || prev.preference.preferredBrand,
|
newData.preferredBrand || prev.preference.preferredBrand,
|
||||||
|
|
||||||
preferredOS: (newData.preferredOS as OS) || prev.preference.preferredOS,
|
preferredOS: (newData.preferredOS as OS) || prev.preference.preferredOS,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useEffect, useState, useMemo } from "react";
|
import { useEffect, useState, useMemo } from "react";
|
||||||
import { ApiResponse, ReviewItem } from "../types";
|
import { ApiResponse, ReviewItem } from "../types";
|
||||||
import { PaginationService } from "../services/review.service";
|
import { PaginationService } from "../services/review.service";
|
||||||
|
import { reviewPath } from "../utils/const";
|
||||||
|
|
||||||
export const useReviewTable = (
|
export const useReviewTable = (
|
||||||
itemsPerPage: number = 10,
|
itemsPerPage: number = 10,
|
||||||
|
|
@ -14,7 +15,7 @@ export const useReviewTable = (
|
||||||
const getReviewData = async () => {
|
const getReviewData = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const req = await fetch("/api/review");
|
const req = await fetch(reviewPath);
|
||||||
const res: ApiResponse = await req.json();
|
const res: ApiResponse = await req.json();
|
||||||
|
|
||||||
if (res.data && Array.isArray(res.data)) {
|
if (res.data && Array.isArray(res.data)) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { AnalysisResult } from "../types";
|
import { AnalysisResult } from "../types";
|
||||||
import { Minus, ThumbsDown, ThumbsUp } from "lucide-react";
|
import { configDisplay } from "../utils/datas";
|
||||||
|
import { models, negativeWords, positiveWords } from "../utils/const";
|
||||||
|
|
||||||
export const useSentiment = () => {
|
export const useSentiment = () => {
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState("");
|
||||||
|
|
@ -23,32 +24,6 @@ export const useSentiment = () => {
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
|
|
||||||
const positiveWords = [
|
|
||||||
"bagus",
|
|
||||||
"cepat",
|
|
||||||
"aman",
|
|
||||||
"baik",
|
|
||||||
"mulus",
|
|
||||||
"moga",
|
|
||||||
"awet",
|
|
||||||
"mantap",
|
|
||||||
"sangat",
|
|
||||||
"fungsi",
|
|
||||||
];
|
|
||||||
|
|
||||||
const negativeWords = [
|
|
||||||
"lebih",
|
|
||||||
"jual",
|
|
||||||
"baru",
|
|
||||||
"lalu",
|
|
||||||
"tahun",
|
|
||||||
"masalah",
|
|
||||||
"rusak",
|
|
||||||
"garansi",
|
|
||||||
"layar",
|
|
||||||
"kecewa",
|
|
||||||
];
|
|
||||||
|
|
||||||
const lowerText = text.toLowerCase();
|
const lowerText = text.toLowerCase();
|
||||||
let positiveScore = 0;
|
let positiveScore = 0;
|
||||||
let negativeScore = 0;
|
let negativeScore = 0;
|
||||||
|
|
@ -91,53 +66,10 @@ export const useSentiment = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSentimentDisplay = (sentiment: AnalysisResult["sentiment"]) => {
|
const getSentimentDisplay = (sentiment: AnalysisResult["sentiment"]) => {
|
||||||
const config = {
|
const config = configDisplay(sentiment);
|
||||||
POSITIVE: {
|
return config;
|
||||||
icon: ThumbsUp,
|
|
||||||
label: "Positif",
|
|
||||||
bgClass: "bg-sentiment-positive-light",
|
|
||||||
textClass: "text-sentiment-positive",
|
|
||||||
borderClass: "border-sentiment-positive/30",
|
|
||||||
},
|
|
||||||
NEGATIVE: {
|
|
||||||
icon: ThumbsDown,
|
|
||||||
label: "Negatif",
|
|
||||||
bgClass: "bg-sentiment-negative-light",
|
|
||||||
textClass: "text-sentiment-negative",
|
|
||||||
borderClass: "border-sentiment-negative/30",
|
|
||||||
},
|
|
||||||
NEUTRAL: {
|
|
||||||
icon: Minus,
|
|
||||||
label: "Netral",
|
|
||||||
bgClass: "bg-sentiment-neutral-light",
|
|
||||||
textClass: "text-sentiment-neutral",
|
|
||||||
borderClass: "border-sentiment-neutral/30",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return config[sentiment];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const models = [
|
|
||||||
{
|
|
||||||
code: "none",
|
|
||||||
value: "xgboost",
|
|
||||||
label: "XGBoost (Baseline)",
|
|
||||||
desc: "Model 1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "Grid Search",
|
|
||||||
value: "xgboost",
|
|
||||||
label: "XGBoost (Tuned)",
|
|
||||||
desc: "Model 2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "recommended",
|
|
||||||
value: "xgboost",
|
|
||||||
label: "XGBoost (Fully Optimized)",
|
|
||||||
desc: "Model 3",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const filteredItems = useMemo(() => {
|
const filteredItems = useMemo(() => {
|
||||||
if (!searchQuery) return models;
|
if (!searchQuery) return models;
|
||||||
return models.filter(
|
return models.filter(
|
||||||
|
|
@ -149,18 +81,18 @@ export const useSentiment = () => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedModel,
|
selectedModel,
|
||||||
setSelectedModel,
|
|
||||||
text,
|
text,
|
||||||
setText,
|
|
||||||
laptopName,
|
laptopName,
|
||||||
setLaptopName,
|
|
||||||
isAnalyzing,
|
isAnalyzing,
|
||||||
analyzeText,
|
|
||||||
result,
|
result,
|
||||||
getSentimentDisplay,
|
|
||||||
searchQuery,
|
searchQuery,
|
||||||
setSearchQuery,
|
|
||||||
filteredItems,
|
filteredItems,
|
||||||
isFormValid,
|
isFormValid,
|
||||||
|
setSelectedModel,
|
||||||
|
setText,
|
||||||
|
setLaptopName,
|
||||||
|
analyzeText,
|
||||||
|
getSentimentDisplay,
|
||||||
|
setSearchQuery,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { MODEL_OPTIONS } from "../utils/const";
|
import { MODEL_OPTIONS, predictPath } from "../utils/const";
|
||||||
|
|
||||||
export const useSentimentForm = () => {
|
export const useSentimentForm = () => {
|
||||||
const [selectedModel, setSelectedModel] = useState(MODEL_OPTIONS[2]);
|
const [selectedModel, setSelectedModel] = useState(MODEL_OPTIONS[2]);
|
||||||
|
|
@ -31,7 +31,7 @@ export const useSentimentForm = () => {
|
||||||
setResult(null);
|
setResult(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("http://127.0.0.1:8000/predict", {
|
const response = await fetch(predictPath, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// src/hooks/useSocket.ts
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { io, Socket } from "socket.io-client";
|
import { io, Socket } from "socket.io-client";
|
||||||
|
import { socketPath } from "../utils/const";
|
||||||
|
|
||||||
export const useSocket = () => {
|
export const useSocket = () => {
|
||||||
const [socket, setSocket] = useState<Socket | null>(null);
|
const [socket, setSocket] = useState<Socket | null>(null);
|
||||||
|
|
@ -8,7 +8,7 @@ export const useSocket = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const socketInitializer = async () => {
|
const socketInitializer = async () => {
|
||||||
await fetch("/api/socket"); // Panggil API untuk menyalakan server socket
|
await fetch(socketPath);
|
||||||
const newSocket = io();
|
const newSocket = io();
|
||||||
|
|
||||||
newSocket.on("progress", (data) => {
|
newSocket.on("progress", (data) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { KeywordStats, WordCloudReview, WordItem } from "@/src/types";
|
import { KeywordStats, WordCloudReview, WordItem } from "@/src/types";
|
||||||
import { WORD_LIMIT } from "../utils/const";
|
import { WORD_LIMIT, wordCloudPath } from "../utils/const";
|
||||||
|
|
||||||
export const useWordCloud = () => {
|
export const useWordCloud = () => {
|
||||||
const [words, setWords] = useState<WordItem[]>([]);
|
const [words, setWords] = useState<WordItem[]>([]);
|
||||||
|
|
@ -11,11 +11,11 @@ export const useWordCloud = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchWords = async () => {
|
const fetchWords = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/word-cloud");
|
const res = await fetch(wordCloudPath);
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
|
|
||||||
if (!json?.success || !Array.isArray(json.data)) {
|
if (!json?.success || !Array.isArray(json.data)) {
|
||||||
console.error("Invalid response from /api/word-cloud");
|
console.error(`Invalid response from ${wordCloudPath}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { AIRecommendationResponse } from "../types";
|
import { AIRecommendationResponse } from "../types";
|
||||||
|
import { aiRecommendPath, scrapePath } from "../utils/const";
|
||||||
|
|
||||||
export const scrapeProduct = async (
|
export const scrapeProduct = async (
|
||||||
url: string,
|
url: string,
|
||||||
options?: { signal?: AbortSignal },
|
options?: { signal?: AbortSignal },
|
||||||
) => {
|
) => {
|
||||||
const res = await fetch("/api/scrape", {
|
const res = await fetch(scrapePath, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
@ -70,7 +71,7 @@ export const getAIRecommendation = async (
|
||||||
options?: { signal?: AbortSignal },
|
options?: { signal?: AbortSignal },
|
||||||
): Promise<AIRecommendationResponse> => {
|
): Promise<AIRecommendationResponse> => {
|
||||||
console.log("Fetching to FastAPI...");
|
console.log("Fetching to FastAPI...");
|
||||||
const aiRes = await fetch("https://citot123-tokped-scraper.hf.space/recommend", {
|
const aiRes = await fetch(aiRecommendPath, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
|
|
@ -79,13 +80,7 @@ export const getAIRecommendation = async (
|
||||||
|
|
||||||
if (!aiRes.ok) {
|
if (!aiRes.ok) {
|
||||||
const errorData = await aiRes.json();
|
const errorData = await aiRes.json();
|
||||||
// DEBUG: Munculkan di console agar bisa dibaca strukturnya
|
|
||||||
console.error(
|
|
||||||
"DETAILED VALIDATION ERROR:",
|
|
||||||
JSON.stringify(errorData, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ambil pesan error pertama dari list validation FastAPI
|
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
errorData.detail?.[0]?.msg || "Gagal melakukan analisis AI";
|
errorData.detail?.[0]?.msg || "Gagal melakukan analisis AI";
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { userMetricPath } from "../utils/const";
|
||||||
|
|
||||||
export const getMetricId = async () => {
|
export const getMetricId = async () => {
|
||||||
const response = await fetch("/api/user-metric");
|
const response = await fetch(userMetricPath);
|
||||||
if (!response.ok) return null;
|
if (!response.ok) return null;
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data.metricId;
|
return data.metricId;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { ProfileFormData } from "../types";
|
import { ProfileFormData } from "../types";
|
||||||
|
import { profilePath } from "../utils/const";
|
||||||
|
|
||||||
export const updateProfileService = async (formData: ProfileFormData) => {
|
export const updateProfileService = async (formData: ProfileFormData) => {
|
||||||
const response = await fetch("/api/profile", {
|
const response = await fetch(profilePath, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: JSON.stringify(formData),
|
body: JSON.stringify(formData),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import puppeteer from "puppeteer-core";
|
||||||
import chromium from "@sparticuz/chromium-min";
|
import chromium from "@sparticuz/chromium-min";
|
||||||
import { ScrapeResult } from "../types";
|
import { ScrapeResult } from "../types";
|
||||||
import { getFallbackData } from "../utils/datas";
|
import { getFallbackData } from "../utils/datas";
|
||||||
|
import { chromiumUrl } from "../utils/const";
|
||||||
|
|
||||||
function normalizeToReviewUrl(rawUrl: string): string {
|
function normalizeToReviewUrl(rawUrl: string): string {
|
||||||
try {
|
try {
|
||||||
|
|
@ -28,8 +29,6 @@ export async function scrapeTokopediaProduct(
|
||||||
): Promise<ScrapeResult> {
|
): Promise<ScrapeResult> {
|
||||||
const targetUrl = normalizeToReviewUrl(url);
|
const targetUrl = normalizeToReviewUrl(url);
|
||||||
let browser;
|
let browser;
|
||||||
const CHROMIUM_URL =
|
|
||||||
"https://github.com/Sparticuz/chromium/releases/download/v131.0.1/chromium-v131.0.1-pack.tar";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
browser = await puppeteer.launch({
|
browser = await puppeteer.launch({
|
||||||
|
|
@ -39,7 +38,7 @@ export async function scrapeTokopediaProduct(
|
||||||
"--disable-setuid-sandbox",
|
"--disable-setuid-sandbox",
|
||||||
"--disable-blink-features=AutomationControlled",
|
"--disable-blink-features=AutomationControlled",
|
||||||
],
|
],
|
||||||
executablePath: await chromium.executablePath(CHROMIUM_URL),
|
executablePath: await chromium.executablePath(chromiumUrl),
|
||||||
headless: true,
|
headless: true,
|
||||||
defaultViewport: { width: 1280, height: 800 },
|
defaultViewport: { width: 1280, height: 800 },
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,8 @@ import {
|
||||||
import { SiAcer, SiAsus, SiLenovo, SiLinux, SiMacos } from "react-icons/si";
|
import { SiAcer, SiAsus, SiLenovo, SiLinux, SiMacos } from "react-icons/si";
|
||||||
import { FaWindows } from "react-icons/fa";
|
import { FaWindows } from "react-icons/fa";
|
||||||
import { Sentiment } from "@prisma/client";
|
import { Sentiment } from "@prisma/client";
|
||||||
import { useAnalyseText } from "../hooks/useAnalyzeText";
|
|
||||||
|
|
||||||
export const MODEL_OPTIONS = [
|
const MODEL_OPTIONS = [
|
||||||
{
|
{
|
||||||
label: "Model XGBoost (Baseline)",
|
label: "Model XGBoost (Baseline)",
|
||||||
code: "baseline",
|
code: "baseline",
|
||||||
|
|
@ -28,9 +27,9 @@ export const MODEL_OPTIONS = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const WORD_LIMIT = 30;
|
const WORD_LIMIT = 30;
|
||||||
|
|
||||||
export const professionItems = [
|
const professionItems = [
|
||||||
{ value: "PROGRAMMER", label: "Programmer", icon: Code },
|
{ value: "PROGRAMMER", label: "Programmer", icon: Code },
|
||||||
{ value: "DESIGNER", label: "Designer", icon: Palette },
|
{ value: "DESIGNER", label: "Designer", icon: Palette },
|
||||||
{ value: "STUDENT", label: "Student", icon: Book },
|
{ value: "STUDENT", label: "Student", icon: Book },
|
||||||
|
|
@ -38,21 +37,21 @@ export const professionItems = [
|
||||||
{ value: "OTHER", label: "Other", icon: LucideCircleEllipsis },
|
{ value: "OTHER", label: "Other", icon: LucideCircleEllipsis },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const brandItems = [
|
const brandItems = [
|
||||||
{ value: "ASUS", label: "Asus", icon: SiAsus },
|
{ value: "ASUS", label: "Asus", icon: SiAsus },
|
||||||
{ value: "ACER", label: "Acer", icon: SiAcer },
|
{ value: "ACER", label: "Acer", icon: SiAcer },
|
||||||
{ value: "LENOVO", label: "Lenovo", icon: SiLenovo },
|
{ value: "LENOVO", label: "Lenovo", icon: SiLenovo },
|
||||||
{ value: "OTHER", label: "Other", icon: LucideCircleEllipsis },
|
{ value: "OTHER", label: "Other", icon: LucideCircleEllipsis },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const OSItems = [
|
const OSItems = [
|
||||||
{ value: "WINDOWS", label: "Windows", icon: FaWindows },
|
{ value: "WINDOWS", label: "Windows", icon: FaWindows },
|
||||||
{ value: "MACOS", label: "Macos", icon: SiMacos },
|
{ value: "MACOS", label: "Macos", icon: SiMacos },
|
||||||
{ value: "LINUX", label: "Linux", icon: SiLinux },
|
{ value: "LINUX", label: "Linux", icon: SiLinux },
|
||||||
{ value: "OTHER", label: "Other", icon: LucideCircleEllipsis },
|
{ value: "OTHER", label: "Other", icon: LucideCircleEllipsis },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const reviewDatas = [
|
const reviewDatas = [
|
||||||
{
|
{
|
||||||
productId: 2,
|
productId: 2,
|
||||||
modelId: 1,
|
modelId: 1,
|
||||||
|
|
@ -79,3 +78,87 @@ export const reviewDatas = [
|
||||||
confidenceScore: 0.88,
|
confidenceScore: 0.88,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const scrapePath = "/api/scrape";
|
||||||
|
const backendUrl = process.env.BACKEND_URL || "http://localhost:8000";
|
||||||
|
const aiRecommendPath = `${backendUrl}/recommend`;
|
||||||
|
const userMetricPath = "/api/user-metric";
|
||||||
|
const profilePath = "/api/profile";
|
||||||
|
const chromiumUrl =
|
||||||
|
"https://github.com/Sparticuz/chromium/releases/download/v131.0.1/chromium-v131.0.1-pack.tar";
|
||||||
|
const sentimentStatsPath = "/api/review/sentiment-stats";
|
||||||
|
const productPath = "/api/product";
|
||||||
|
const reviewPath = "/api/review";
|
||||||
|
const positiveWords = [
|
||||||
|
"bagus",
|
||||||
|
"cepat",
|
||||||
|
"aman",
|
||||||
|
"baik",
|
||||||
|
"mulus",
|
||||||
|
"moga",
|
||||||
|
"awet",
|
||||||
|
"mantap",
|
||||||
|
"sangat",
|
||||||
|
"fungsi",
|
||||||
|
];
|
||||||
|
|
||||||
|
const negativeWords = [
|
||||||
|
"lebih",
|
||||||
|
"jual",
|
||||||
|
"baru",
|
||||||
|
"lalu",
|
||||||
|
"tahun",
|
||||||
|
"masalah",
|
||||||
|
"rusak",
|
||||||
|
"garansi",
|
||||||
|
"layar",
|
||||||
|
"kecewa",
|
||||||
|
];
|
||||||
|
|
||||||
|
const models = [
|
||||||
|
{
|
||||||
|
code: "none",
|
||||||
|
value: "xgboost",
|
||||||
|
label: "XGBoost (Baseline)",
|
||||||
|
desc: "Model 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "Grid Search",
|
||||||
|
value: "xgboost",
|
||||||
|
label: "XGBoost (Tuned)",
|
||||||
|
desc: "Model 2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "recommended",
|
||||||
|
value: "xgboost",
|
||||||
|
label: "XGBoost (Fully Optimized)",
|
||||||
|
desc: "Model 3",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const predictPath = `${backendUrl}/predict`;
|
||||||
|
const socketPath = "/api/socket";
|
||||||
|
const wordCloudPath = "/api/word-cloud";
|
||||||
|
|
||||||
|
export {
|
||||||
|
scrapePath,
|
||||||
|
aiRecommendPath,
|
||||||
|
userMetricPath,
|
||||||
|
profilePath,
|
||||||
|
chromiumUrl,
|
||||||
|
sentimentStatsPath,
|
||||||
|
productPath,
|
||||||
|
reviewPath,
|
||||||
|
positiveWords,
|
||||||
|
negativeWords,
|
||||||
|
models,
|
||||||
|
predictPath,
|
||||||
|
MODEL_OPTIONS,
|
||||||
|
WORD_LIMIT,
|
||||||
|
professionItems,
|
||||||
|
brandItems,
|
||||||
|
OSItems,
|
||||||
|
reviewDatas,
|
||||||
|
socketPath,
|
||||||
|
wordCloudPath,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useReviewTable } from "../hooks/useReviewTable";
|
import { Minus, ThumbsDown, ThumbsUp } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
|
AnalysisResult,
|
||||||
RadarProps,
|
RadarProps,
|
||||||
ScrapeResult,
|
ScrapeResult,
|
||||||
VisiblePageProps,
|
VisiblePageProps,
|
||||||
|
|
@ -142,3 +143,30 @@ export const radarFormat = ({ data }: RadarProps) => {
|
||||||
|
|
||||||
return { chartData, colors };
|
return { chartData, colors };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const configDisplay = (sentiment: AnalysisResult["sentiment"]) => {
|
||||||
|
const config = {
|
||||||
|
POSITIVE: {
|
||||||
|
icon: ThumbsUp,
|
||||||
|
label: "Positif",
|
||||||
|
bgClass: "bg-sentiment-positive-light",
|
||||||
|
textClass: "text-sentiment-positive",
|
||||||
|
borderClass: "border-sentiment-positive/30",
|
||||||
|
},
|
||||||
|
NEGATIVE: {
|
||||||
|
icon: ThumbsDown,
|
||||||
|
label: "Negatif",
|
||||||
|
bgClass: "bg-sentiment-negative-light",
|
||||||
|
textClass: "text-sentiment-negative",
|
||||||
|
borderClass: "border-sentiment-negative/30",
|
||||||
|
},
|
||||||
|
NEUTRAL: {
|
||||||
|
icon: Minus,
|
||||||
|
label: "Netral",
|
||||||
|
bgClass: "bg-sentiment-neutral-light",
|
||||||
|
textClass: "text-sentiment-neutral",
|
||||||
|
borderClass: "border-sentiment-neutral/30",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return config[sentiment];
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,4 @@ const iconStyles = {
|
||||||
neutral: "bg-sentiment-neutral/10 text-sentiment-neutral",
|
neutral: "bg-sentiment-neutral/10 text-sentiment-neutral",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export { variantStyles, iconStyles };
|
export { variantStyles, iconStyles };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue