* feat: add initial VS Code extension plan and implementation tasks * feat(vscode): added initial version of an Openchamber VSCode extension * feat(vscode): enhance VS Code extension with theme integration and session management * feat(vscode): implement connection status handling and overlay in VSCode layout * feat: move extension to secondary sidebar * chore: upgrade @opencode-ai/sdk to 1.0.150 * vscode: editor bridge, file picker, click-to-open in tool parts * vscode: layout session lifecycle, theme sync, typography overrides * ui: compact mode for vscode, model search, autocomplete width fixes * perf: scroll force flag, raf placeholder, git polling backoff * ui: tool output styling, markdown code block fix, gitignore * refactor: update typography handling for VSCode runtime, remove unused styles * docs: update README with VS Code extension details and add extension image * docs: update changelog with new features and performance improvements
89 lines
2.7 KiB
TypeScript
89 lines
2.7 KiB
TypeScript
import React from 'react';
|
|
import type { Message, Part } from '@opencode-ai/sdk';
|
|
|
|
interface SimpleMessageRendererProps {
|
|
message: { info: Message; parts: Part[] };
|
|
}
|
|
|
|
export function SimpleMessageRenderer({ message }: SimpleMessageRendererProps) {
|
|
const { info, parts } = message;
|
|
const isUser = info.role === 'user';
|
|
|
|
// Extract text content from parts
|
|
const textContent = parts
|
|
.filter((part): part is Part & { type: 'text' } => part.type === 'text')
|
|
.map((part) => part.text)
|
|
.join('\n');
|
|
|
|
// Check for tool calls
|
|
const toolParts = parts.filter((part) => part.type === 'tool-invocation' || part.type === 'tool-result');
|
|
|
|
return (
|
|
<div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>
|
|
<div
|
|
className={`max-w-[85%] rounded-lg px-3 py-2 text-sm ${
|
|
isUser
|
|
? 'bg-primary text-primary-foreground'
|
|
: 'bg-muted text-foreground'
|
|
}`}
|
|
>
|
|
{/* Role indicator for assistant */}
|
|
{!isUser && (
|
|
<div className="text-xs font-medium text-muted-foreground mb-1">
|
|
Assistant
|
|
</div>
|
|
)}
|
|
|
|
{/* Text content */}
|
|
{textContent && (
|
|
<div className="whitespace-pre-wrap break-words">{textContent}</div>
|
|
)}
|
|
|
|
{/* Tool activity indicator */}
|
|
{toolParts.length > 0 && (
|
|
<div className="mt-2 pt-2 border-t border-border/50">
|
|
{toolParts.map((part, idx) => (
|
|
<ToolPartRenderer key={idx} part={part} />
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Empty message placeholder */}
|
|
{!textContent && toolParts.length === 0 && (
|
|
<div className="text-muted-foreground italic">...</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ToolPartRenderer({ part }: { part: Part }) {
|
|
if (part.type === 'tool-invocation') {
|
|
const toolName = part.toolInvocation?.toolName || 'tool';
|
|
const state = part.toolInvocation?.state || 'pending';
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
{state === 'pending' || state === 'streaming' ? (
|
|
<span className="inline-block w-3 h-3 border-2 border-current border-t-transparent rounded-full animate-spin" />
|
|
) : state === 'result' ? (
|
|
<span className="text-green-500">✓</span>
|
|
) : (
|
|
<span className="text-red-500">✗</span>
|
|
)}
|
|
<span className="font-mono">{toolName}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (part.type === 'tool-result') {
|
|
return (
|
|
<div className="text-xs text-muted-foreground font-mono truncate">
|
|
Result: {typeof part.result === 'string' ? part.result.slice(0, 50) : '...'}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|