diff --git a/src/app/chat/components/__tests__/tailwind-components.test.tsx b/src/app/chat/components/__tests__/tailwind-components.test.tsx
new file mode 100644
index 00000000..dce97b4f
--- /dev/null
+++ b/src/app/chat/components/__tests__/tailwind-components.test.tsx
@@ -0,0 +1,40 @@
+import { renderToStaticMarkup } from "react-dom/server";
+import { describe, expect, it } from "vitest";
+
+import { MessageAvatar } from "../message-avatar";
+import { TextBubble } from "../text-bubble";
+
+describe("chat Tailwind components", () => {
+ it("renders MessageAvatar AI and user branches with image utilities", () => {
+ const aiHtml = renderToStaticMarkup();
+ const userHtml = renderToStaticMarkup(
+ ,
+ );
+
+ expect(aiHtml).toContain('aria-label="AI avatar"');
+ expect(aiHtml).toContain("size-[var(--chat-avatar-size,43px)]");
+ expect(aiHtml).toContain("size-full object-cover");
+ expect(aiHtml).toContain("%2Fimages%2Fchat%2Fpic-chat-elio.png");
+ expect(userHtml).toContain('aria-label="User avatar"');
+ expect(userHtml).toContain("%2Fuser-avatar.png");
+ });
+
+ it("renders TextBubble AI and user visual variants", () => {
+ const aiHtml = renderToStaticMarkup(
+ ,
+ );
+ const userHtml = renderToStaticMarkup(
+ ,
+ );
+
+ expect(aiHtml).toContain("whitespace-pre-wrap");
+ expect(aiHtml).toContain("rounded-tl-none");
+ expect(aiHtml).toContain("bg-white");
+ expect(aiHtml).toContain("hello\nthere");
+ expect(userHtml).toContain("rounded-tr-none");
+ expect(userHtml).toContain(
+ "bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]",
+ );
+ expect(userHtml).toContain("from user");
+ });
+});
diff --git a/src/app/chat/components/message-avatar.module.css b/src/app/chat/components/message-avatar.module.css
deleted file mode 100644
index 8ab326fc..00000000
--- a/src/app/chat/components/message-avatar.module.css
+++ /dev/null
@@ -1,30 +0,0 @@
-/* MessageAvatar 头像样式 */
-
-.avatar {
- flex: 0 0 auto;
- width: var(--chat-avatar-size, 43px);
- height: var(--chat-avatar-size, 43px);
- border-radius: 9999px;
- overflow: hidden;
- border: 2px solid var(--color-avatar-border, #fbf3f5);
- box-shadow: var(--shadow-input-box, 0 1px 2px rgba(0, 0, 0, 0.1));
- background: var(--color-avatar-border, #fbf3f5);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.avatar img {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
-.placeholder {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: var(--icon-size-xl, 24px);
-}
diff --git a/src/app/chat/components/message-avatar.tsx b/src/app/chat/components/message-avatar.tsx
index 1a189bf8..ad3b7365 100644
--- a/src/app/chat/components/message-avatar.tsx
+++ b/src/app/chat/components/message-avatar.tsx
@@ -2,23 +2,26 @@
import Image from "next/image";
import { UserMessageAvatar } from "@/app/_components";
-import styles from "./message-avatar.module.css";
export interface MessageAvatarProps {
isFromAI: boolean;
userAvatarUrl?: string | null;
}
+const AVATAR_CLASS_NAME =
+ "flex size-[var(--chat-avatar-size,43px)] shrink-0 items-center justify-center overflow-hidden rounded-full border-2 border-[var(--color-avatar-border,#fbf3f5)] bg-[var(--color-avatar-border,#fbf3f5)] shadow-[var(--shadow-input-box,0_1px_2px_rgba(0,0,0,0.1))]";
+
export function MessageAvatar({ isFromAI, userAvatarUrl }: MessageAvatarProps) {
if (isFromAI) {
return (
-
+
);
diff --git a/src/app/chat/components/text-bubble.module.css b/src/app/chat/components/text-bubble.module.css
deleted file mode 100644
index bfe00367..00000000
--- a/src/app/chat/components/text-bubble.module.css
+++ /dev/null
@@ -1,30 +0,0 @@
-/* TextBubble 文字气泡样式 */
-
-.bubble {
- /* 撑满父(MessageContent column 宽)—— 让气泡占满所在半区 */
- width: 100%;
- align-self: stretch;
- padding: var(--spacing-3, 12px) var(--spacing-4, 16px);
- border-radius: var(--radius-xxl, 24px);
- font-size: var(--chat-message-font-size, var(--responsive-body, 16px));
- line-height: var(--chat-message-line-height, 1.5);
- white-space: pre-wrap;
- word-break: break-word;
- box-shadow: var(--shadow-input-box, 0 1px 2px rgba(0, 0, 0, 0.1));
-}
-
-.bubbleAi {
- background: #fff;
- color: var(--color-text-foreground, #000);
- border-top-left-radius: 0;
-}
-
-.bubbleUser {
- background: linear-gradient(
- to right,
- var(--color-button-gradient-start, #ff67e0),
- var(--color-button-gradient-end, #ff52a2)
- );
- color: #fff;
- border-top-right-radius: 0;
-}
diff --git a/src/app/chat/components/text-bubble.tsx b/src/app/chat/components/text-bubble.tsx
index 31c84fcb..84d77338 100644
--- a/src/app/chat/components/text-bubble.tsx
+++ b/src/app/chat/components/text-bubble.tsx
@@ -8,17 +8,27 @@
* - AI 消息:白底黑字 + 左上角尖角
* - 用户消息:渐变(primaryGradient)+ 白字 + 右上角尖角
*/
-import styles from "./text-bubble.module.css";
export interface TextBubbleProps {
content: string;
isFromAI: boolean;
}
+const BASE_CLASS_NAME =
+ "w-full self-stretch whitespace-pre-wrap break-words rounded-[var(--radius-xxl,24px)] px-[var(--spacing-4,16px)] py-[var(--spacing-3,12px)] text-[length:var(--chat-message-font-size,var(--responsive-body,16px))] leading-[var(--chat-message-line-height,1.5)] shadow-[var(--shadow-input-box,0_1px_2px_rgba(0,0,0,0.1))]";
+
+const AI_CLASS_NAME =
+ "rounded-tl-none bg-white text-[var(--color-text-foreground,#000)]";
+
+const USER_CLASS_NAME =
+ "rounded-tr-none bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))] text-white";
+
export function TextBubble({ content, isFromAI }: TextBubbleProps) {
return (
{content}