Files
openchamber/packages/vscode/webview/components/SimpleMessageRenderer.tsx
T
Bohdan Triapitsyn bb72c0fb0c feat: vscode extension (#59)
* 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
2025-12-13 16:34:17 +02:00

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;
}