feat: enhance VSCode layout with expanded view and responsive design; update sidebar visibility and settings for VSCode runtime
This commit is contained in:
+6
-1
@@ -4,7 +4,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
- Git Identities: added “default identity” setting with a one-click set/unset; automatically applies default identity in Git view for repos without a local identity, and uses it as the preferred identity for skills catalog auth flows.
|
- UI: added a new Files tab to browse workspace files directly from the interface.
|
||||||
|
- Diff: enhanced the diff viewer with mobile support and the ability to ask the agent for comments on changes.
|
||||||
|
- Git Identities: added "default identity" setting with one-click set/unset and automatic local identity detection.
|
||||||
|
- VSCode: improved server management to ensure it initializes within the workspace directory with context-aware readiness checks.
|
||||||
|
- VSCode: added responsive layout with sessions sidebar + chat side-by-side when wide, compact header, and streamlined settings.
|
||||||
|
- Web: the server now automatically resolves and uses an available port if the default is occupied.
|
||||||
|
|
||||||
|
|
||||||
## [1.4.9] - 2026-01-14
|
## [1.4.9] - 2026-01-14
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import { RiAddLine, RiArrowLeftLine, RiRobot2Line, RiSettings3Line } from '@remi
|
|||||||
|
|
||||||
// Width threshold for mobile vs desktop layout in settings
|
// Width threshold for mobile vs desktop layout in settings
|
||||||
const MOBILE_WIDTH_THRESHOLD = 550;
|
const MOBILE_WIDTH_THRESHOLD = 550;
|
||||||
|
// Width threshold for expanded layout (sidebar + chat side by side)
|
||||||
|
const EXPANDED_LAYOUT_THRESHOLD = 700;
|
||||||
|
// Sessions sidebar width in expanded layout
|
||||||
|
const SESSIONS_SIDEBAR_WIDTH = 280;
|
||||||
|
|
||||||
type VSCodeView = 'sessions' | 'chat' | 'settings';
|
type VSCodeView = 'sessions' | 'chat' | 'settings';
|
||||||
|
|
||||||
@@ -38,7 +42,7 @@ export const VSCodeLayout: React.FC = () => {
|
|||||||
|
|
||||||
const hasAppliedInitialSession = React.useRef(false);
|
const hasAppliedInitialSession = React.useRef(false);
|
||||||
|
|
||||||
const [currentView, setCurrentView] = React.useState<VSCodeView>('chat');
|
const [currentView, setCurrentView] = React.useState<VSCodeView>('sessions');
|
||||||
const [containerWidth, setContainerWidth] = React.useState<number>(0);
|
const [containerWidth, setContainerWidth] = React.useState<number>(0);
|
||||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||||
const currentSessionId = useSessionStore((state) => state.currentSessionId);
|
const currentSessionId = useSessionStore((state) => state.currentSessionId);
|
||||||
@@ -83,16 +87,16 @@ export const VSCodeLayout: React.FC = () => {
|
|||||||
void vscodeApi.executeCommand('openchamber.setActiveSession', currentSessionId, activeSessionTitle);
|
void vscodeApi.executeCommand('openchamber.setActiveSession', currentSessionId, activeSessionTitle);
|
||||||
}, [activeSessionTitle, currentSessionId, runtimeApis.vscode]);
|
}, [activeSessionTitle, currentSessionId, runtimeApis.vscode]);
|
||||||
|
|
||||||
// If the active session disappears (e.g., deleted), show a new chat view
|
// If the active session disappears (e.g., deleted), go back to sessions list
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (viewMode === 'editor') {
|
if (viewMode === 'editor') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentSessionId && !newSessionDraftOpen) {
|
if (!currentSessionId && !newSessionDraftOpen && currentView === 'chat') {
|
||||||
openNewSessionDraft();
|
setCurrentView('sessions');
|
||||||
}
|
}
|
||||||
}, [currentSessionId, newSessionDraftOpen, openNewSessionDraft, viewMode]);
|
}, [currentSessionId, newSessionDraftOpen, currentView, viewMode]);
|
||||||
|
|
||||||
const handleBackToSessions = React.useCallback(() => {
|
const handleBackToSessions = React.useCallback(() => {
|
||||||
setCurrentView('sessions');
|
setCurrentView('sessions');
|
||||||
@@ -199,19 +203,24 @@ export const VSCodeLayout: React.FC = () => {
|
|||||||
if (hasAppliedInitialSession.current) {
|
if (hasAppliedInitialSession.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!initialSessionId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!hasInitializedOnce || connectionStatus !== 'connected') {
|
if (!hasInitializedOnce || connectionStatus !== 'connected') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No initialSessionId means open a new session draft
|
||||||
|
if (!initialSessionId) {
|
||||||
|
hasAppliedInitialSession.current = true;
|
||||||
|
openNewSessionDraft();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!sessions.some((session) => session.id === initialSessionId)) {
|
if (!sessions.some((session) => session.id === initialSessionId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAppliedInitialSession.current = true;
|
hasAppliedInitialSession.current = true;
|
||||||
void useSessionStore.getState().setCurrentSession(initialSessionId);
|
void useSessionStore.getState().setCurrentSession(initialSessionId);
|
||||||
}, [connectionStatus, hasInitializedOnce, initialSessionId, sessions, viewMode]);
|
}, [connectionStatus, hasInitializedOnce, initialSessionId, openNewSessionDraft, sessions, viewMode]);
|
||||||
|
|
||||||
// Hydrate messages when viewing chat
|
// Hydrate messages when viewing chat
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -254,10 +263,20 @@ export const VSCodeLayout: React.FC = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const usesMobileLayout = containerWidth > 0 && containerWidth < MOBILE_WIDTH_THRESHOLD;
|
const usesMobileLayout = containerWidth > 0 && containerWidth < MOBILE_WIDTH_THRESHOLD;
|
||||||
|
const usesExpandedLayout = containerWidth >= EXPANDED_LAYOUT_THRESHOLD;
|
||||||
|
|
||||||
|
// In expanded layout, always show chat (with sidebar alongside)
|
||||||
|
// Navigate to chat automatically when expanded layout is enabled and we're on sessions view
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (usesExpandedLayout && currentView === 'sessions' && viewMode === 'sidebar') {
|
||||||
|
setCurrentView('chat');
|
||||||
|
}
|
||||||
|
}, [usesExpandedLayout, currentView, viewMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="h-full w-full bg-background text-foreground flex flex-col">
|
<div ref={containerRef} className="h-full w-full bg-background text-foreground flex flex-col">
|
||||||
{viewMode === 'editor' ? (
|
{viewMode === 'editor' ? (
|
||||||
|
// Editor mode: just chat, no sidebar
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<VSCodeHeader
|
<VSCodeHeader
|
||||||
title={sessions.find((session) => session.id === currentSessionId)?.title || 'Chat'}
|
title={sessions.find((session) => session.id === currentSessionId)?.title || 'Chat'}
|
||||||
@@ -270,7 +289,45 @@ export const VSCodeLayout: React.FC = () => {
|
|||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : currentView === 'settings' ? (
|
||||||
|
// Settings view
|
||||||
|
<SettingsView
|
||||||
|
onClose={() => setCurrentView(usesExpandedLayout ? 'chat' : 'sessions')}
|
||||||
|
forceMobile={usesMobileLayout}
|
||||||
|
/>
|
||||||
|
) : usesExpandedLayout ? (
|
||||||
|
// Expanded layout: sessions sidebar + chat side by side
|
||||||
|
<div className="flex h-full">
|
||||||
|
{/* Sessions sidebar */}
|
||||||
|
<div
|
||||||
|
className="h-full border-r border-border overflow-hidden flex-shrink-0"
|
||||||
|
style={{ width: SESSIONS_SIDEBAR_WIDTH }}
|
||||||
|
>
|
||||||
|
<SessionSidebar
|
||||||
|
mobileVariant
|
||||||
|
allowReselect
|
||||||
|
hideDirectoryControls
|
||||||
|
showOnlyMainWorkspace
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* Chat content */}
|
||||||
|
<div className="flex-1 flex flex-col min-w-0">
|
||||||
|
<VSCodeHeader
|
||||||
|
title={newSessionDraftOpen && !currentSessionId
|
||||||
|
? 'New session'
|
||||||
|
: sessions.find((session) => session.id === currentSessionId)?.title || 'Chat'}
|
||||||
|
showMcp
|
||||||
|
showContextUsage
|
||||||
|
/>
|
||||||
|
<div className="flex-1 overflow-hidden">
|
||||||
|
<ErrorBoundary>
|
||||||
|
<ChatView />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
) : currentView === 'sessions' ? (
|
) : currentView === 'sessions' ? (
|
||||||
|
// Compact layout: sessions list (drill-down)
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<VSCodeHeader
|
<VSCodeHeader
|
||||||
title="Sessions"
|
title="Sessions"
|
||||||
@@ -285,12 +342,8 @@ export const VSCodeLayout: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : currentView === 'settings' ? (
|
|
||||||
<SettingsView
|
|
||||||
onClose={() => setCurrentView('sessions')}
|
|
||||||
forceMobile={usesMobileLayout}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
|
// Compact layout: chat view (drill-down)
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<VSCodeHeader
|
<VSCodeHeader
|
||||||
title={newSessionDraftOpen && !currentSessionId
|
title={newSessionDraftOpen && !currentSessionId
|
||||||
@@ -336,17 +389,17 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
|||||||
const contextUsage = getContextUsage(contextLimit, outputLimit);
|
const contextUsage = getContextUsage(contextLimit, outputLimit);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-border bg-background shrink-0">
|
<div className="flex items-center gap-1.5 pl-1 pr-2 py-1 border-b border-border bg-background shrink-0">
|
||||||
{showBack && onBack && (
|
{showBack && onBack && (
|
||||||
<button
|
<button
|
||||||
onClick={onBack}
|
onClick={onBack}
|
||||||
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
className="inline-flex h-7 w-7 items-center justify-center text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||||
aria-label="Back to sessions"
|
aria-label="Back to sessions"
|
||||||
>
|
>
|
||||||
<RiArrowLeftLine className="h-5 w-5" />
|
<RiArrowLeftLine className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<h1 className="text-sm font-medium truncate flex-1 h-9 w-9 items-center justify-center p-2" title={title}>{title}</h1>
|
<h1 className="text-sm font-medium truncate flex-1" title={title}>{title}</h1>
|
||||||
{onNewSession && (
|
{onNewSession && (
|
||||||
<button
|
<button
|
||||||
onClick={onNewSession}
|
onClick={onNewSession}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ interface SectionGroup {
|
|||||||
label: string;
|
label: string;
|
||||||
items: string[];
|
items: string[];
|
||||||
webOnly?: boolean;
|
webOnly?: boolean;
|
||||||
|
hideInVSCode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OPENCHAMBER_SECTION_GROUPS: SectionGroup[] = [
|
const OPENCHAMBER_SECTION_GROUPS: SectionGroup[] = [
|
||||||
@@ -39,6 +40,7 @@ const OPENCHAMBER_SECTION_GROUPS: SectionGroup[] = [
|
|||||||
id: 'git',
|
id: 'git',
|
||||||
label: 'Git',
|
label: 'Git',
|
||||||
items: ['Commit Messages', 'Worktree'],
|
items: ['Commit Messages', 'Worktree'],
|
||||||
|
hideInVSCode: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'notifications',
|
id: 'notifications',
|
||||||
@@ -69,8 +71,12 @@ export const OpenChamberSidebar: React.FC<OpenChamberSidebarProps> = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const visibleSections = React.useMemo(() => {
|
const visibleSections = React.useMemo(() => {
|
||||||
return OPENCHAMBER_SECTION_GROUPS.filter((group) => !group.webOnly || isWeb);
|
return OPENCHAMBER_SECTION_GROUPS.filter((group) => {
|
||||||
}, [isWeb]);
|
if (group.webOnly && !isWeb) return false;
|
||||||
|
if (group.hideInVSCode && isVSCode) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [isWeb, isVSCode]);
|
||||||
|
|
||||||
// Desktop app: transparent for blur effect
|
// Desktop app: transparent for blur effect
|
||||||
// VS Code: bg-background (same as page content)
|
// VS Code: bg-background (same as page content)
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{shouldShow('diffLayout') && !isMobile && (
|
{shouldShow('diffLayout') && !isMobile && !isVSCodeRuntime() && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h3 className="typography-ui-header font-semibold text-foreground">
|
<h3 className="typography-ui-header font-semibold text-foreground">
|
||||||
|
|||||||
@@ -32,12 +32,16 @@ import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
|
|||||||
import { useDeviceInfo } from '@/lib/device';
|
import { useDeviceInfo } from '@/lib/device';
|
||||||
import { isVSCodeRuntime } from '@/lib/desktop';
|
import { isVSCodeRuntime } from '@/lib/desktop';
|
||||||
|
|
||||||
const SETTINGS_SECTIONS = (() => {
|
const getSettingsSections = (isVSCode: boolean) => {
|
||||||
const filtered = SIDEBAR_SECTIONS.filter(section => section.id !== 'sessions');
|
let filtered = SIDEBAR_SECTIONS.filter(section => section.id !== 'sessions');
|
||||||
|
// Hide Git Identities tab for VS Code
|
||||||
|
if (isVSCode) {
|
||||||
|
filtered = filtered.filter(section => section.id !== 'git-identities');
|
||||||
|
}
|
||||||
const settingsSection = filtered.find(s => s.id === 'settings');
|
const settingsSection = filtered.find(s => s.id === 'settings');
|
||||||
const otherSections = filtered.filter(s => s.id !== 'settings');
|
const otherSections = filtered.filter(s => s.id !== 'settings');
|
||||||
return settingsSection ? [settingsSection, ...otherSections] : filtered;
|
return settingsSection ? [settingsSection, ...otherSections] : filtered;
|
||||||
})();
|
};
|
||||||
|
|
||||||
// Same constraints as main sidebar
|
// Same constraints as main sidebar
|
||||||
const SETTINGS_SIDEBAR_MIN_WIDTH = 200;
|
const SETTINGS_SIDEBAR_MIN_WIDTH = 200;
|
||||||
@@ -89,6 +93,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
|||||||
|
|
||||||
const isVSCode = React.useMemo(() => isVSCodeRuntime(), []);
|
const isVSCode = React.useMemo(() => isVSCodeRuntime(), []);
|
||||||
|
|
||||||
|
const settingsSections = React.useMemo(() => getSettingsSections(isVSCode), [isVSCode]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
setIsDesktopApp(typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined');
|
setIsDesktopApp(typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined');
|
||||||
@@ -143,7 +149,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
|||||||
return formatProjectLabel(rawLabel);
|
return formatProjectLabel(rawLabel);
|
||||||
}, [activeProject, formatProjectLabel]);
|
}, [activeProject, formatProjectLabel]);
|
||||||
|
|
||||||
const showProjectSwitcher = sortedProjects.length > 0;
|
const showProjectSwitcher = sortedProjects.length > 0 && !isVSCode;
|
||||||
|
|
||||||
const showTabLabels = containerWidth === 0 || containerWidth >= TAB_LABELS_MIN_WIDTH;
|
const showTabLabels = containerWidth === 0 || containerWidth >= TAB_LABELS_MIN_WIDTH;
|
||||||
|
|
||||||
@@ -352,7 +358,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
|||||||
<div className={cn('flex items-center', isMobile ? 'gap-1' : 'h-full')}>
|
<div className={cn('flex items-center', isMobile ? 'gap-1' : 'h-full')}>
|
||||||
{/* Leading divider before first tab - only on Mac desktop */}
|
{/* Leading divider before first tab - only on Mac desktop */}
|
||||||
{!isMobile && showLeadingDivider && <div className="h-full w-px bg-border" aria-hidden="true" />}
|
{!isMobile && showLeadingDivider && <div className="h-full w-px bg-border" aria-hidden="true" />}
|
||||||
{SETTINGS_SECTIONS.map(({ id, label, icon: Icon }) => {
|
{settingsSections.map(({ id, label, icon: Icon }) => {
|
||||||
const isActive = activeTab === id;
|
const isActive = activeTab === id;
|
||||||
const PhosphorIcon = Icon as React.ComponentType<{ className?: string; weight?: string }>;
|
const PhosphorIcon = Icon as React.ComponentType<{ className?: string; weight?: string }>;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
|||||||
import { hasModifier } from '@/lib/utils';
|
import { hasModifier } from '@/lib/utils';
|
||||||
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
|
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
|
||||||
import { useConfigStore } from '@/stores/useConfigStore';
|
import { useConfigStore } from '@/stores/useConfigStore';
|
||||||
|
import { isVSCodeRuntime } from '@/lib/desktop';
|
||||||
|
|
||||||
export const useKeyboardShortcuts = () => {
|
export const useKeyboardShortcuts = () => {
|
||||||
const { openNewSessionDraft, abortCurrentOperation, armAbortPrompt, clearAbortPrompt, currentSessionId } = useSessionStore();
|
const { openNewSessionDraft, abortCurrentOperation, armAbortPrompt, clearAbortPrompt, currentSessionId } = useSessionStore();
|
||||||
@@ -83,10 +84,12 @@ export const useKeyboardShortcuts = () => {
|
|||||||
if (hasModifier(e) && e.key.toLowerCase() === 'n') {
|
if (hasModifier(e) && e.key.toLowerCase() === 'n') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
const isVSCode = isVSCodeRuntime();
|
||||||
const autoWorktree = useConfigStore.getState().settingsAutoCreateWorktree;
|
const autoWorktree = useConfigStore.getState().settingsAutoCreateWorktree;
|
||||||
// If autoWorktree is true: Cmd+N -> Worktree, Cmd+Shift+N -> Standard
|
// If autoWorktree is true: Cmd+N -> Worktree, Cmd+Shift+N -> Standard
|
||||||
// If autoWorktree is false: Cmd+N -> Standard, Cmd+Shift+N -> Worktree
|
// If autoWorktree is false: Cmd+N -> Standard, Cmd+Shift+N -> Worktree
|
||||||
const shouldCreateWorktree = autoWorktree ? !e.shiftKey : e.shiftKey;
|
// VS Code: always open standard session (no worktree support)
|
||||||
|
const shouldCreateWorktree = isVSCode ? false : (autoWorktree ? !e.shiftKey : e.shiftKey);
|
||||||
|
|
||||||
if (shouldCreateWorktree) {
|
if (shouldCreateWorktree) {
|
||||||
// Create new session with auto-generated worktree
|
// Create new session with auto-generated worktree
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
- Improved OpenCode server management to ensure it initializes within the workspace directory.
|
||||||
|
- Enhanced extension startup with context-aware readiness checks for the current workspace.
|
||||||
|
- Session tabs: fixed opening new session in editor tab; title bar button now opens new session tab, sidebar button opens current or new session.
|
||||||
|
- Layout: added responsive expanded layout showing sessions sidebar + chat side-by-side when extension is wide enough (≥700px).
|
||||||
|
- Layout: extension now opens to sessions list instead of new session draft.
|
||||||
|
- Layout: compact header with reduced padding for better space efficiency.
|
||||||
|
- Settings: hidden Git Identities tab, Git section, and Diff view settings (not applicable to VS Code).
|
||||||
|
- Settings: hidden project switcher dropdown (VS Code uses workspace).
|
||||||
|
- Shortcuts: disabled worktree session creation (Ctrl+Shift+N now opens standard session).
|
||||||
|
|
||||||
## [1.4.9] - 2026-01-14
|
## [1.4.9] - 2026-01-14
|
||||||
|
|
||||||
- Added session editor panel to view sessions alongside files.
|
- Added session editor panel to view sessions alongside files.
|
||||||
|
|||||||
@@ -93,6 +93,21 @@
|
|||||||
"title": "Open Active Session in Editor",
|
"title": "Open Active Session in Editor",
|
||||||
"icon": "$(link-external)"
|
"icon": "$(link-external)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "openchamber.openNewSessionInEditor",
|
||||||
|
"category": "OpenChamber",
|
||||||
|
"title": "Open New Session in Editor",
|
||||||
|
"icon": {
|
||||||
|
"light": "assets/icon.svg",
|
||||||
|
"dark": "assets/icon-titlebar.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "openchamber.openCurrentOrNewSessionInEditor",
|
||||||
|
"category": "OpenChamber",
|
||||||
|
"title": "Open Session in Editor",
|
||||||
|
"icon": "$(link-external)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "openchamber.addToContext",
|
"command": "openchamber.addToContext",
|
||||||
"category": "OpenChamber",
|
"category": "OpenChamber",
|
||||||
@@ -136,7 +151,7 @@
|
|||||||
],
|
],
|
||||||
"editor/title": [
|
"editor/title": [
|
||||||
{
|
{
|
||||||
"command": "openchamber.openSidebar",
|
"command": "openchamber.openNewSessionInEditor",
|
||||||
"group": "navigation@1"
|
"group": "navigation@1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -147,7 +162,7 @@
|
|||||||
"group": "navigation@1"
|
"group": "navigation@1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "openchamber.openActiveSessionInEditor",
|
"command": "openchamber.openCurrentOrNewSessionInEditor",
|
||||||
"when": "view == openchamber.chatView",
|
"when": "view == openchamber.chatView",
|
||||||
"group": "navigation@2"
|
"group": "navigation@2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ export class SessionEditorPanelProvider {
|
|||||||
private readonly _openCodeManager?: OpenCodeManager
|
private readonly _openCodeManager?: OpenCodeManager
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public createOrShowNewSession(): void {
|
||||||
|
// Generate unique panel ID for new session drafts
|
||||||
|
const panelId = `new_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
||||||
|
this._createPanel(panelId, 'New Session', null);
|
||||||
|
}
|
||||||
|
|
||||||
public createOrShow(sessionId: string, title?: string): void {
|
public createOrShow(sessionId: string, title?: string): void {
|
||||||
if (!sessionId || typeof sessionId !== 'string') {
|
if (!sessionId || typeof sessionId !== 'string') {
|
||||||
return;
|
return;
|
||||||
@@ -39,11 +45,15 @@ export class SessionEditorPanelProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._createPanel(sessionId, sessionTitle, sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createPanel(panelId: string, title: string, initialSessionId: string | null): void {
|
||||||
const distUri = vscode.Uri.joinPath(this._extensionUri, 'dist');
|
const distUri = vscode.Uri.joinPath(this._extensionUri, 'dist');
|
||||||
|
|
||||||
const panel = vscode.window.createWebviewPanel(
|
const panel = vscode.window.createWebviewPanel(
|
||||||
SessionEditorPanelProvider.viewType,
|
SessionEditorPanelProvider.viewType,
|
||||||
sessionTitle,
|
title,
|
||||||
vscode.ViewColumn.Beside,
|
vscode.ViewColumn.Beside,
|
||||||
{
|
{
|
||||||
enableScripts: true,
|
enableScripts: true,
|
||||||
@@ -63,15 +73,15 @@ export class SessionEditorPanelProvider {
|
|||||||
sseHeartbeats: new Map(),
|
sseHeartbeats: new Map(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this._panels.set(sessionId, state);
|
this._panels.set(panelId, state);
|
||||||
|
|
||||||
panel.webview.html = this._getHtmlForWebview(panel.webview, sessionId);
|
panel.webview.html = this._getHtmlForWebview(panel.webview, initialSessionId);
|
||||||
|
|
||||||
void this.updateTheme(vscode.window.activeColorTheme.kind);
|
void this.updateTheme(vscode.window.activeColorTheme.kind);
|
||||||
this._sendCachedStateToPanel(state);
|
this._sendCachedStateToPanel(state);
|
||||||
|
|
||||||
panel.onDidDispose(() => {
|
panel.onDidDispose(() => {
|
||||||
this._disposePanel(sessionId);
|
this._disposePanel(panelId);
|
||||||
}, null, this._context.subscriptions);
|
}, null, this._context.subscriptions);
|
||||||
|
|
||||||
panel.webview.onDidReceiveMessage(async (message: BridgeRequest) => {
|
panel.webview.onDidReceiveMessage(async (message: BridgeRequest) => {
|
||||||
@@ -354,7 +364,7 @@ export class SessionEditorPanelProvider {
|
|||||||
return { id, type, success: true, data: { stopped: true } };
|
return { id, type, success: true, data: { stopped: true } };
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getHtmlForWebview(webview: vscode.Webview, sessionId: string) {
|
private _getHtmlForWebview(webview: vscode.Webview, sessionId: string | null) {
|
||||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
|
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
|
||||||
const initialStatus = this._cachedStatus;
|
const initialStatus = this._cachedStatus;
|
||||||
const cliAvailable = this._openCodeManager?.isCliAvailable() ?? false;
|
const cliAvailable = this._openCodeManager?.isCliAvailable() ?? false;
|
||||||
@@ -366,7 +376,7 @@ export class SessionEditorPanelProvider {
|
|||||||
initialStatus,
|
initialStatus,
|
||||||
cliAvailable,
|
cliAvailable,
|
||||||
panelType: 'chat',
|
panelType: 'chat',
|
||||||
initialSessionId: sessionId,
|
initialSessionId: sessionId ?? undefined,
|
||||||
viewMode: 'editor',
|
viewMode: 'editor',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,6 +198,22 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('openchamber.openNewSessionInEditor', () => {
|
||||||
|
sessionEditorProvider?.createOrShowNewSession();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('openchamber.openCurrentOrNewSessionInEditor', () => {
|
||||||
|
if (activeSessionId) {
|
||||||
|
sessionEditorProvider?.createOrShow(activeSessionId, activeSessionTitle ?? undefined);
|
||||||
|
} else {
|
||||||
|
sessionEditorProvider?.createOrShowNewSession();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
vscode.commands.registerCommand('openchamber.restartApi', async () => {
|
vscode.commands.registerCommand('openchamber.restartApi', async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user