feat(frontend): remove ugly canvas placeholder from chatbot
- 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
This commit is contained in:
@@ -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<HTMLCanvasElement>(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 (
|
|
||||||
<canvas
|
|
||||||
ref={canvasRef}
|
|
||||||
width={160}
|
|
||||||
height={180}
|
|
||||||
className="w-full h-full"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
import { Bot, MessageCircle, Minimize2, PanelLeft } from "lucide-react";
|
import { Bot, MessageCircle, Minimize2, PanelLeft } from "lucide-react";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { ChatPanel } from "./chat-panel";
|
import { ChatPanel } from "./chat-panel";
|
||||||
import { ChatbotCanvas } from "./chatbot-canvas";
|
|
||||||
import { useChatbot } from "./chatbot-context";
|
import { useChatbot } from "./chatbot-context";
|
||||||
|
|
||||||
export function ChatbotContainer() {
|
export function ChatbotContainer() {
|
||||||
@@ -53,7 +52,7 @@ export function ChatbotContainer() {
|
|||||||
className={`glass-intense rounded-2xl overflow-hidden transition-all duration-200 ${
|
className={`glass-intense rounded-2xl overflow-hidden transition-all duration-200 ${
|
||||||
minimized ? "w-14 h-14 cursor-pointer" : "w-[320px]"
|
minimized ? "w-14 h-14 cursor-pointer" : "w-[320px]"
|
||||||
}`}
|
}`}
|
||||||
style={{ height: minimized ? 56 : 440 }}
|
style={{ height: minimized ? 56 : 400 }}
|
||||||
>
|
>
|
||||||
{minimized ? (
|
{minimized ? (
|
||||||
<button
|
<button
|
||||||
@@ -98,15 +97,10 @@ export function ChatbotContainer() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Canvas area */}
|
|
||||||
<div className="h-[140px] flex items-center justify-center">
|
|
||||||
<ChatbotCanvas />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Chat panel (expandable) */}
|
{/* Chat panel (expandable) */}
|
||||||
<div
|
<div
|
||||||
className={`transition-all duration-200 overflow-hidden ${
|
className={`transition-all duration-200 overflow-hidden ${
|
||||||
chatOpen ? "h-[248px]" : "h-0"
|
chatOpen ? "h-[300px]" : "h-0"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<ChatPanel inputRef={inputRef} />
|
<ChatPanel inputRef={inputRef} />
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export { ChatPanel } from "./chat-panel";
|
export { ChatPanel } from "./chat-panel";
|
||||||
export { ChatbotCanvas } from "./chatbot-canvas";
|
|
||||||
export { ChatbotContainer } from "./chatbot-container";
|
export { ChatbotContainer } from "./chatbot-container";
|
||||||
export { ChatbotProvider, useChatbot } from "./chatbot-context";
|
export { ChatbotProvider, useChatbot } from "./chatbot-context";
|
||||||
|
|||||||
Reference in New Issue
Block a user