* 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
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
import React from 'react';
|
|
import type { Session } from '@opencode-ai/sdk';
|
|
|
|
interface SessionItemProps {
|
|
session: Session;
|
|
isActive: boolean;
|
|
isStreaming?: boolean;
|
|
onClick: () => void;
|
|
}
|
|
|
|
const formatRelativeTime = (timestamp: number | undefined): string => {
|
|
if (!timestamp) return '';
|
|
|
|
const date = new Date(timestamp);
|
|
const now = new Date();
|
|
const diffMs = now.getTime() - date.getTime();
|
|
const diffMins = Math.floor(diffMs / 60000);
|
|
const diffHours = Math.floor(diffMs / 3600000);
|
|
const diffDays = Math.floor(diffMs / 86400000);
|
|
|
|
if (diffMins < 1) return 'Just now';
|
|
if (diffMins < 60) return `${diffMins}m ago`;
|
|
if (diffHours < 24) return `${diffHours}h ago`;
|
|
if (diffDays === 1) return 'Yesterday';
|
|
if (diffDays < 7) return `${diffDays}d ago`;
|
|
|
|
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
};
|
|
|
|
export function SessionItem({ session, isActive, isStreaming, onClick }: SessionItemProps) {
|
|
const title = session.title || 'New Session';
|
|
const time = formatRelativeTime(session.time?.created);
|
|
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
className={`w-full text-left px-3 py-2.5 flex items-start gap-2 transition-colors ${
|
|
isActive
|
|
? 'bg-primary/10 border-l-2 border-primary'
|
|
: 'hover:bg-muted/50 border-l-2 border-transparent'
|
|
}`}
|
|
>
|
|
{/* Activity indicator */}
|
|
<div className="mt-1.5 flex-shrink-0">
|
|
{isStreaming ? (
|
|
<span className="block w-2 h-2 rounded-full bg-green-500 animate-pulse" />
|
|
) : (
|
|
<span className={`block w-2 h-2 rounded-full ${isActive ? 'bg-primary' : 'bg-muted-foreground/30'}`} />
|
|
)}
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="flex-1 min-w-0">
|
|
<div className="text-sm font-medium truncate">{title}</div>
|
|
<div className="text-xs text-muted-foreground flex items-center gap-2">
|
|
<span>{time}</span>
|
|
{session.summary && (
|
|
<span className="text-[10px]">
|
|
{session.summary.additions !== undefined && (
|
|
<span className="text-green-600">+{session.summary.additions}</span>
|
|
)}
|
|
{session.summary.deletions !== undefined && (
|
|
<span className="text-red-500 ml-1">-{session.summary.deletions}</span>
|
|
)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</button>
|
|
);
|
|
}
|