From 7513681b4b5606ccfbf04677cc115dcd422f88b7 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Mon, 3 Aug 2026 06:11:06 +0700 Subject: [PATCH] feat(frontend): remove ugly canvas placeholder from chatbot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete chatbot-canvas.tsx (placeholder face canvas) + its export - Remove canvas area from chatbot container; chat panel gets the freed space (248px → 300px) and bubble height drops 440px → 400px --- .../src/components/chatbot/chatbot-canvas.tsx | 132 ------------------ .../components/chatbot/chatbot-container.tsx | 10 +- .../frontend/src/components/chatbot/index.ts | 1 - 3 files changed, 2 insertions(+), 141 deletions(-) delete mode 100644 services/frontend/src/components/chatbot/chatbot-canvas.tsx diff --git a/services/frontend/src/components/chatbot/chatbot-canvas.tsx b/services/frontend/src/components/chatbot/chatbot-canvas.tsx deleted file mode 100644 index 863e3fc..0000000 --- a/services/frontend/src/components/chatbot/chatbot-canvas.tsx +++ /dev/null @@ -1,132 +0,0 @@ -"use client"; - -import { useEffect, useRef } from "react"; -import { useChatbot } from "./chatbot-context"; - -/** - * Live2D Cubism WebGL canvas. - * - * This component renders the Live2D model via the Cubism SDK. - * Integration requires: - * 1. Live2D Cubism SDK for Web (npm: @live2d/cubism) - * 2. Model files: .model3.json, .moc3, .physics3.json, textures - * 3. Place model files in public/chatbot/ - * - * The current implementation shows a placeholder character. - * Replace with actual Cubism SDK integration when model files are available. - */ - -export function ChatbotCanvas() { - const canvasRef = useRef(null); - const { expression } = useChatbot(); - - // Placeholder: draw a simple avatar face that responds to expression - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - const ctx = canvas.getContext("2d"); - if (!ctx) return; - - const w = canvas.width; - const h = canvas.height; - - ctx.clearRect(0, 0, w, h); - - // Background circle - const gradient = ctx.createRadialGradient( - w / 2, - h / 2 - 10, - 10, - w / 2, - h / 2, - 80, - ); - gradient.addColorStop(0, "oklch(0.62 0.17 215 / 0.8)"); - gradient.addColorStop(0.6, "oklch(0.12 0.02 245 / 0.9)"); - gradient.addColorStop(1, "oklch(0.07 0.015 250 / 1)"); - ctx.fillStyle = gradient; - ctx.beginPath(); - ctx.arc(w / 2, h / 2, 75, 0, Math.PI * 2); - ctx.fill(); - - // Eyes - const eyeOffsetX = 20; - const eyeY = 45; - - // Expression-driven eyes - if (expression === "surprise") { - // Wide eyes - ctx.fillStyle = "oklch(0.93 0.01 245)"; - ctx.beginPath(); - ctx.ellipse(w / 2 - eyeOffsetX, eyeY, 12, 14, 0, 0, Math.PI * 2); - ctx.ellipse(w / 2 + eyeOffsetX, eyeY, 12, 14, 0, 0, Math.PI * 2); - ctx.fill(); - ctx.fillStyle = "oklch(0.62 0.17 215)"; - ctx.beginPath(); - ctx.arc(w / 2 - eyeOffsetX, eyeY, 5, 0, Math.PI * 2); - ctx.arc(w / 2 + eyeOffsetX, eyeY, 5, 0, Math.PI * 2); - ctx.fill(); - } else if (expression === "happy") { - // Happy closed crescent eyes - ctx.strokeStyle = "oklch(0.93 0.01 245)"; - ctx.lineWidth = 3; - ctx.beginPath(); - ctx.arc(w / 2 - eyeOffsetX, eyeY, 10, Math.PI * 0.1, Math.PI * 0.9); - ctx.stroke(); - ctx.beginPath(); - ctx.arc(w / 2 + eyeOffsetX, eyeY, 10, Math.PI * 0.1, Math.PI * 0.9); - ctx.stroke(); - } else if (expression === "sad") { - // Sad downcast eyes - ctx.fillStyle = "oklch(0.93 0.01 245)"; - ctx.beginPath(); - ctx.ellipse(w / 2 - eyeOffsetX, eyeY, 8, 6, 0.2, 0, Math.PI * 2); - ctx.ellipse(w / 2 + eyeOffsetX, eyeY, 8, 6, -0.2, 0, Math.PI * 2); - ctx.fill(); - } else { - // Normal eyes - ctx.fillStyle = "oklch(0.93 0.01 245)"; - ctx.beginPath(); - ctx.ellipse(w / 2 - eyeOffsetX, eyeY, 10, 8, 0, 0, Math.PI * 2); - ctx.ellipse(w / 2 + eyeOffsetX, eyeY, 10, 8, 0, 0, Math.PI * 2); - ctx.fill(); - ctx.fillStyle = "oklch(0.62 0.17 215)"; - ctx.beginPath(); - ctx.arc(w / 2 - eyeOffsetX, eyeY, 4, 0, Math.PI * 2); - ctx.arc(w / 2 + eyeOffsetX, eyeY, 4, 0, Math.PI * 2); - ctx.fill(); - } - - // Mouth - ctx.strokeStyle = "oklch(0.93 0.01 245 / 0.7)"; - ctx.lineWidth = 2; - if (expression === "talking") { - ctx.beginPath(); - ctx.ellipse(w / 2, 70, 8, 6, 0, 0, Math.PI * 2); - ctx.stroke(); - } else if (expression === "happy") { - ctx.beginPath(); - ctx.arc(w / 2, 70, 10, 0.1, Math.PI - 0.1); - ctx.stroke(); - } else if (expression === "surprise") { - ctx.beginPath(); - ctx.ellipse(w / 2, 70, 6, 8, 0, 0, Math.PI * 2); - ctx.stroke(); - ctx.fillStyle = "oklch(0.12 0.02 245)"; - ctx.fill(); - } else { - ctx.beginPath(); - ctx.arc(w / 2, 75, 6, 0.1, Math.PI - 0.1); - ctx.stroke(); - } - }, [expression]); - - return ( - - ); -} diff --git a/services/frontend/src/components/chatbot/chatbot-container.tsx b/services/frontend/src/components/chatbot/chatbot-container.tsx index 19e1c68..4919a41 100644 --- a/services/frontend/src/components/chatbot/chatbot-container.tsx +++ b/services/frontend/src/components/chatbot/chatbot-container.tsx @@ -3,7 +3,6 @@ import { Bot, MessageCircle, Minimize2, PanelLeft } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { ChatPanel } from "./chat-panel"; -import { ChatbotCanvas } from "./chatbot-canvas"; import { useChatbot } from "./chatbot-context"; export function ChatbotContainer() { @@ -53,7 +52,7 @@ export function ChatbotContainer() { className={`glass-intense rounded-2xl overflow-hidden transition-all duration-200 ${ minimized ? "w-14 h-14 cursor-pointer" : "w-[320px]" }`} - style={{ height: minimized ? 56 : 440 }} + style={{ height: minimized ? 56 : 400 }} > {minimized ? (