page.tsx still owns the chat main chain, but the first product surfaces now live in their own modules so later splits can land without editing the 4k-line Home. Source-lock tests follow the moved tokens; the orphan user-data contract is aligned and added to the quick gate. Co-authored-by: Cursor <cursoragent@cursor.com>
29 lines
1.5 KiB
TypeScript
29 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
import { ChatMessageContent } from "@/components/chat-message-content";
|
|
import { AgentAvatar } from "@/components/chat-message-row";
|
|
import { protectOnboardingPhrases } from "@/lib/onboarding-copy";
|
|
import type { Message } from "@/lib/home-types";
|
|
|
|
export function OnboardingChatMessage({ role, text, streaming = false, length = text.length, phraseSafe = false }: { role: Message["role"]; text: string; streaming?: boolean; length?: number; phraseSafe?: boolean }) {
|
|
const visibleText = streaming ? text.slice(0, length) : text;
|
|
const protectedVisibleText = protectOnboardingPhrases(visibleText);
|
|
return (
|
|
<article className={`message message-${role} onboarding-message${phraseSafe ? " is-phrase-safe" : ""}`} aria-label={role === "assistant" ? "Jyotisha" : "你"}>
|
|
{role === "assistant" && <AgentAvatar />}
|
|
<div className="message-content">
|
|
<div className="message-bubble">
|
|
{role === "assistant" ? (
|
|
streaming ? (
|
|
<>
|
|
<div className={`onboarding-stream ${length >= text.length ? "is-complete" : ""}`} aria-hidden="true"><ChatMessageContent text={protectedVisibleText} /></div>
|
|
<span className="sr-only" role="status" aria-live="polite" aria-atomic="true">{length >= text.length ? text : ""}</span>
|
|
</>
|
|
) : <ChatMessageContent text={protectedVisibleText} />
|
|
) : <p>{protectedVisibleText}</p>}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
);
|
|
}
|