refactor(vscode-ide-companion): extract AssistantMessage as standalone component with Claude Code styles

- Extract AssistantMessage component from inline implementation
- Add status prop support (default, success, error, warning, loading)
- Implement bullet point indicator using CSS pseudo-elements (::before)
- Use inline styles for layout to prevent Tailwind override
- Add AssistantMessage.css with pseudo-element styles for different states
- Import AssistantMessage.css in ClaudeCodeStyles.css

Restores Claude Code DOM structure and styling:
- Outer container with padding-left: 30px for bullet spacing
- Bullet point colors based on status (green, red, yellow, gray)
- Loading state with pulse animation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
yiliang114
2025-11-23 21:44:40 +08:00
parent de8ea0678d
commit 4f964b5281
3 changed files with 108 additions and 31 deletions

View File

@@ -19,19 +19,7 @@
@import './components/Timeline.css'; @import './components/Timeline.css';
@import './components/shared/FileLink.css'; @import './components/shared/FileLink.css';
@import './components/toolcalls/shared/DiffDisplay.css'; @import './components/toolcalls/shared/DiffDisplay.css';
@import './components/messages/AssistantMessage.css';
/* ===========================
Header Styles (from Claude Code .he)
=========================== */
.chat-header {
display: flex;
border-bottom: 1px solid var(--app-primary-border-color);
padding: 6px 10px;
gap: 4px;
background-color: var(--app-header-background);
justify-content: flex-start;
user-select: none;
}
/* =========================== /* ===========================
Session Selector Button (from Claude Code .E) Session Selector Button (from Claude Code .E)

View File

@@ -0,0 +1,60 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*
* AssistantMessage Component Styles
* Only pseudo-elements (::before) for bullet points
*/
/* Bullet point indicator using ::before pseudo-element */
.assistant-message-container.assistant-message-default::before,
.assistant-message-container.assistant-message-success::before,
.assistant-message-container.assistant-message-error::before,
.assistant-message-container.assistant-message-warning::before,
.assistant-message-container.assistant-message-loading::before {
content: '\25cf';
position: absolute;
left: 8px;
padding-top: 2px;
font-size: 10px;
z-index: 1;
}
/* Default state - secondary foreground color */
.assistant-message-container.assistant-message-default::before {
color: var(--app-secondary-foreground);
}
/* Success state - green bullet (maps to .ge) */
.assistant-message-container.assistant-message-success::before {
color: #74c991;
}
/* Error state - red bullet (maps to .be) */
.assistant-message-container.assistant-message-error::before {
color: #c74e39;
}
/* Warning state - yellow/orange bullet (maps to .ue) */
.assistant-message-container.assistant-message-warning::before {
color: #e1c08d;
}
/* Loading state - animated bullet (maps to .he) */
.assistant-message-container.assistant-message-loading::before {
color: var(--app-secondary-foreground);
background-color: var(--app-secondary-background);
animation: assistantMessagePulse 1s linear infinite;
}
/* Pulse animation for loading state */
@keyframes assistantMessagePulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}

View File

@@ -6,37 +6,66 @@
import type React from 'react'; import type React from 'react';
import { MessageContent } from '../MessageContent.js'; import { MessageContent } from '../MessageContent.js';
import './AssistantMessage.css';
interface AssistantMessageProps { interface AssistantMessageProps {
content: string; content: string;
timestamp: number; timestamp: number;
onFileClick?: (path: string) => void; onFileClick?: (path: string) => void;
status?: 'default' | 'success' | 'error' | 'warning' | 'loading';
} }
/**
* AssistantMessage component - renders AI responses with Claude Code styling
* Supports different states: default, success, error, warning, loading
*
* Claude Code DOM structure:
* <div class="K o"><span class="i"><p>...</p></span></div>
*
* Styles:
* .o - outer container with padding-left: 30px and ::before for bullet
* .i - inner span wrapper
*/
export const AssistantMessage: React.FC<AssistantMessageProps> = ({ export const AssistantMessage: React.FC<AssistantMessageProps> = ({
content, content,
timestamp: _timestamp, timestamp: _timestamp,
onFileClick, onFileClick,
}) => ( status = 'default',
<div className="flex gap-0 items-start text-left py-2 flex-col relative animate-[fadeIn_0.2s_ease-in]"> }) => {
// Map status to CSS class (only for ::before pseudo-element)
const getStatusClass = () => {
switch (status) {
case 'success':
return 'assistant-message-success';
case 'error':
return 'assistant-message-error';
case 'warning':
return 'assistant-message-warning';
case 'loading':
return 'assistant-message-loading';
default:
return 'assistant-message-default';
}
};
return (
<div <div
className="inline-block my-1 relative whitespace-pre-wrap rounded-md max-w-full overflow-x-auto overflow-y-hidden select-text leading-[1.5]" className={`assistant-message-container ${getStatusClass()}`}
style={{ style={{
border: '1px solid var(--app-input-border)', width: '100%',
borderRadius: 'var(--corner-radius-medium)', alignItems: 'flex-start',
backgroundColor: 'var(--app-input-background)', paddingLeft: '30px',
padding: '4px 6px', userSelect: 'text',
color: 'var(--app-primary-foreground)', position: 'relative',
paddingTop: '8px',
paddingBottom: '8px',
}} }}
> >
<span style={{ color: 'var(--app-secondary-foreground)' }}>
<p style={{ margin: 0, color: 'var(--app-secondary-foreground)' }}>
<MessageContent content={content} onFileClick={onFileClick} /> <MessageContent content={content} onFileClick={onFileClick} />
</p>
</span>
</div> </div>
{/* Timestamp - temporarily hidden */} );
{/* <div };
className="text-xs opacity-60"
style={{ color: 'var(--app-secondary-foreground)' }}
>
{new Date(timestamp).toLocaleTimeString()}
</div> */}
</div>
);