Files
openchamber/packages/ui/src/components/chat/message/parts/UserTextPart.tsx
T

89 lines
2.7 KiB
TypeScript

import React from 'react';
import { cn } from '@/lib/utils';
import type { Part } from '@opencode-ai/sdk';
import type { AgentMentionInfo } from '../types';
type PartWithText = Part & { text?: string; content?: string; value?: string };
type UserTextPartProps = {
part: Part;
messageId: string;
isMobile: boolean;
agentMention?: AgentMentionInfo;
};
const buildMentionUrl = (name: string): string => {
const encoded = encodeURIComponent(name);
return `https://opencode.ai/docs/agents/#${encoded}`;
};
const UserTextPart: React.FC<UserTextPartProps> = ({ part, messageId, agentMention }) => {
const partWithText = part as PartWithText;
const rawText = partWithText.text;
const textContent = typeof rawText === 'string' ? rawText : partWithText.content || partWithText.value || '';
const [isExpanded, setIsExpanded] = React.useState(false);
const [isTruncated, setIsTruncated] = React.useState(false);
const textRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const el = textRef.current;
if (el && !isExpanded) {
setIsTruncated(el.scrollHeight > el.clientHeight);
}
}, [textContent, isExpanded]);
const handleClick = React.useCallback(() => {
if (isTruncated || isExpanded) {
setIsExpanded((prev) => !prev);
}
}, [isTruncated, isExpanded]);
if (!textContent || textContent.trim().length === 0) {
return null;
}
// Render content with optional agent mention link
const renderContent = () => {
if (!agentMention?.token || !textContent.includes(agentMention.token)) {
return textContent;
}
const idx = textContent.indexOf(agentMention.token);
const before = textContent.slice(0, idx);
const after = textContent.slice(idx + agentMention.token.length);
return (
<>
{before}
<a
href={buildMentionUrl(agentMention.name)}
className="text-primary hover:underline"
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
{agentMention.token}
</a>
{after}
</>
);
};
return (
<div
className={cn(
"break-words whitespace-pre-wrap",
!isExpanded && "line-clamp-3",
(isTruncated || isExpanded) && "cursor-pointer"
)}
ref={textRef}
onClick={handleClick}
key={part.id || `${messageId}-user-text`}
>
{renderContent()}
</div>
);
};
export default React.memo(UserTextPart);