mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
fix(vscode-ide-companion): 修复 Tailwind 可重用组件类和 ESLint 配置, 调整 ChatHeader 按钮样式
- 在 tailwind.css 中正确定义可重用的 Tailwind 组件类 - 修复 ChatHeader 组件中的按钮样式,确保 hover 效果正常工作 - 修复 ESLint 配置中的 importPlugin 导入问题 - 清理 App.css 中重复的 CSS 变量定义 - 为 btn-ghost 类设置 4px border radius - 为按钮内的 span 添加左右 4px padding (使用 px-1) - 确保按钮 hover 时有背景色效果
This commit is contained in:
@@ -114,7 +114,10 @@ export default tseslint.config(
|
|||||||
'memfs/lib/volume.js',
|
'memfs/lib/volume.js',
|
||||||
'yargs/**',
|
'yargs/**',
|
||||||
'msw/node',
|
'msw/node',
|
||||||
'**/generated/**'
|
'**/generated/**',
|
||||||
|
'./styles/tailwind.css',
|
||||||
|
'./styles/App.css',
|
||||||
|
'./styles/ClaudeCodeStyles.css'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
||||||
import tsParser from '@typescript-eslint/parser';
|
import tsParser from '@typescript-eslint/parser';
|
||||||
import reactHooks from 'eslint-plugin-react-hooks';
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import importPlugin from 'eslint-plugin-import';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
@@ -55,10 +56,13 @@ export default [
|
|||||||
],
|
],
|
||||||
'react-hooks/rules-of-hooks': 'error',
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
'react-hooks/exhaustive-deps': 'error',
|
'react-hooks/exhaustive-deps': 'error',
|
||||||
|
// Restrict deep imports but allow known-safe exceptions used by the webview
|
||||||
|
// - react-dom/client: required for React 18's createRoot API
|
||||||
|
// - ./styles/**: local CSS modules loaded by the webview
|
||||||
'import/no-internal-modules': [
|
'import/no-internal-modules': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
allow: ['./styles/**'],
|
allow: ['react-dom/client', './styles/**'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2025 Qwen Team
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Session Manager Styles */
|
|
||||||
.session-manager {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
font-size: var(--vscode-chat-font-size, 13px);
|
|
||||||
font-family: var(--vscode-chat-font-family);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-manager-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
border-bottom: 1px solid var(--app-primary-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-manager-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--app-primary-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-manager-actions {
|
|
||||||
padding: 16px;
|
|
||||||
border-bottom: 1px solid var(--app-primary-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-manager-actions .secondary-button {
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
background: var(--vscode-button-secondaryBackground);
|
|
||||||
color: var(--vscode-button-secondaryForeground);
|
|
||||||
font-size: var(--vscode-chat-font-size, 13px);
|
|
||||||
font-family: var(--vscode-chat-font-family);
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-manager-actions .secondary-button:hover {
|
|
||||||
background: var(--vscode-button-secondaryHoverBackground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-search {
|
|
||||||
padding: 12px 16px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
border-bottom: 1px solid var(--app-primary-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-search svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
opacity: 0.5;
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: var(--app-primary-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-search input {
|
|
||||||
flex: 1;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
color: var(--app-primary-foreground);
|
|
||||||
font-size: var(--vscode-chat-font-size, 13px);
|
|
||||||
font-family: var(--vscode-chat-font-family);
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-search input::placeholder {
|
|
||||||
color: var(--app-input-placeholder-foreground);
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-list {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-list-loading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 32px;
|
|
||||||
gap: 8px;
|
|
||||||
color: var(--app-secondary-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-list-empty {
|
|
||||||
padding: 32px;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--app-secondary-foreground);
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 12px;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
color: var(--app-primary-foreground);
|
|
||||||
transition: background 0.1s ease;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item:hover {
|
|
||||||
background: var(--app-list-hover-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item.active {
|
|
||||||
background: var(--app-list-active-background);
|
|
||||||
color: var(--app-list-active-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item-info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item-name {
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item-meta {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
color: var(--app-secondary-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item-date,
|
|
||||||
.session-item-count {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item-actions .icon-button {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item-actions .icon-button:hover {
|
|
||||||
background: var(--app-ghost-button-hover-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item-actions .icon-button svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
color: var(--app-primary-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
border-top: 2px solid currentColor;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2025 Qwen Team
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useVSCode } from '../hooks/useVSCode.js';
|
|
||||||
import {
|
|
||||||
RefreshIcon,
|
|
||||||
SaveDocumentIcon,
|
|
||||||
SearchIcon,
|
|
||||||
PlayIcon,
|
|
||||||
SwitchIcon,
|
|
||||||
} from './icons/index.js';
|
|
||||||
|
|
||||||
interface Session {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
lastUpdated: string;
|
|
||||||
messageCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SessionManagerProps {
|
|
||||||
currentSessionId: string | null;
|
|
||||||
onSwitchSession: (sessionId: string) => void;
|
|
||||||
onSaveSession: () => void;
|
|
||||||
onResumeSession: (sessionId: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SessionManager: React.FC<SessionManagerProps> = ({
|
|
||||||
currentSessionId,
|
|
||||||
onSwitchSession,
|
|
||||||
onSaveSession,
|
|
||||||
onResumeSession,
|
|
||||||
}) => {
|
|
||||||
const vscode = useVSCode();
|
|
||||||
const [sessions, setSessions] = useState<Session[]>([]);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
|
||||||
|
|
||||||
const loadSessions = React.useCallback(() => {
|
|
||||||
setIsLoading(true);
|
|
||||||
vscode.postMessage({
|
|
||||||
type: 'listSavedSessions',
|
|
||||||
data: {},
|
|
||||||
});
|
|
||||||
}, [vscode]);
|
|
||||||
|
|
||||||
// Load sessions when component mounts
|
|
||||||
useEffect(() => {
|
|
||||||
loadSessions();
|
|
||||||
}, [loadSessions]);
|
|
||||||
|
|
||||||
// Listen for session list updates
|
|
||||||
useEffect(() => {
|
|
||||||
const handleMessage = (event: MessageEvent) => {
|
|
||||||
const message = event.data;
|
|
||||||
|
|
||||||
if (message.type === 'savedSessionsList') {
|
|
||||||
setIsLoading(false);
|
|
||||||
setSessions(message.data.sessions || []);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('message', handleMessage);
|
|
||||||
return () => window.removeEventListener('message', handleMessage);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const filteredSessions = sessions.filter((session) =>
|
|
||||||
session.name.toLowerCase().includes(searchQuery.toLowerCase()),
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSaveCurrent = () => {
|
|
||||||
onSaveSession();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleResumeSession = (sessionId: string) => {
|
|
||||||
onResumeSession(sessionId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSwitchSession = (sessionId: string) => {
|
|
||||||
onSwitchSession(sessionId);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="session-manager">
|
|
||||||
<div className="session-manager-header">
|
|
||||||
<h3>Saved Conversations</h3>
|
|
||||||
<button
|
|
||||||
className="icon-button"
|
|
||||||
onClick={loadSessions}
|
|
||||||
disabled={isLoading}
|
|
||||||
title="Refresh sessions"
|
|
||||||
>
|
|
||||||
<RefreshIcon width="16" height="16" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="session-manager-actions">
|
|
||||||
<button className="secondary-button" onClick={handleSaveCurrent}>
|
|
||||||
<SaveDocumentIcon width="16" height="16" />
|
|
||||||
Save Current
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="session-search">
|
|
||||||
<SearchIcon width="16" height="16" />
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search conversations..."
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="session-list">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="session-list-loading">
|
|
||||||
<div className="loading-spinner"></div>
|
|
||||||
<span>Loading conversations...</span>
|
|
||||||
</div>
|
|
||||||
) : filteredSessions.length === 0 ? (
|
|
||||||
<div className="session-list-empty">
|
|
||||||
{searchQuery
|
|
||||||
? 'No matching conversations'
|
|
||||||
: 'No saved conversations yet'}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
filteredSessions.map((session) => (
|
|
||||||
<div
|
|
||||||
key={session.id}
|
|
||||||
className={`session-item ${session.id === currentSessionId ? 'active' : ''}`}
|
|
||||||
>
|
|
||||||
<div className="session-item-info">
|
|
||||||
<div className="session-item-name">{session.name}</div>
|
|
||||||
<div className="session-item-meta">
|
|
||||||
<span className="session-item-date">
|
|
||||||
{new Date(session.lastUpdated).toLocaleDateString()}
|
|
||||||
</span>
|
|
||||||
<span className="session-item-count">
|
|
||||||
{session.messageCount}{' '}
|
|
||||||
{session.messageCount === 1 ? 'message' : 'messages'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="session-item-actions">
|
|
||||||
<button
|
|
||||||
className="icon-button"
|
|
||||||
onClick={() => handleResumeSession(session.id)}
|
|
||||||
title="Resume this conversation"
|
|
||||||
>
|
|
||||||
<PlayIcon width="16" height="16" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="icon-button"
|
|
||||||
onClick={() => handleSwitchSession(session.id)}
|
|
||||||
title="Switch to this conversation"
|
|
||||||
>
|
|
||||||
<SwitchIcon width="16" height="16" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -21,39 +21,26 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|||||||
onNewSession,
|
onNewSession,
|
||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
className="flex gap-1 select-none py-1.5 px-2.5"
|
className="chat-header flex items-center select-none py-1.5 px-2.5 w-full"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'var(--app-header-background)',
|
backgroundColor: 'var(--app-header-background)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Past Conversations Button */}
|
|
||||||
<button
|
<button
|
||||||
className="flex-none py-1 px-2 bg-transparent border border-transparent rounded cursor-pointer flex items-center justify-center outline-none font-medium transition-colors duration-200 hover:bg-[var(--app-ghost-button-hover-background)] focus:bg-[var(--app-ghost-button-hover-background)]"
|
className="btn-ghost btn-md px-2 flex items-center outline-none font-medium max-w-[70%] min-w-0 overflow-hidden rounded hover:bg-[var(--app-ghost-button-hover-background)] h-6 leading-6"
|
||||||
style={{
|
|
||||||
borderRadius: 'var(--corner-radius-small)',
|
|
||||||
color: 'var(--app-primary-foreground)',
|
|
||||||
fontSize: 'var(--vscode-chat-font-size, 13px)',
|
|
||||||
}}
|
|
||||||
onClick={onLoadSessions}
|
onClick={onLoadSessions}
|
||||||
title="Past conversations"
|
title="Past conversations"
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-1">
|
<span className="whitespace-nowrap overflow-hidden text-ellipsis min-w-0">
|
||||||
<span style={{ fontSize: 'var(--vscode-chat-font-size, 13px)' }}>
|
{currentSessionTitle}
|
||||||
{currentSessionTitle}
|
|
||||||
</span>
|
|
||||||
<ChevronDownIcon className="w-3.5 h-3.5" />
|
|
||||||
</span>
|
</span>
|
||||||
|
<ChevronDownIcon className="w-4 h-4 flex-shrink-0 ml-1" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Spacer */}
|
<div className="flex-1 min-w-2"></div>
|
||||||
<div className="flex-1"></div>
|
|
||||||
|
|
||||||
{/* New Session Button */}
|
|
||||||
<button
|
<button
|
||||||
className="flex-none p-0 bg-transparent border border-transparent rounded cursor-pointer flex items-center justify-center outline-none w-6 h-6 hover:bg-[var(--app-ghost-button-hover-background)] focus:bg-[var(--app-ghost-button-hover-background)]"
|
className="btn-ghost btn-sm flex items-center justify-center outline-none rounded hover:bg-[var(--app-ghost-button-hover-background)] h-6 leading-6 w-6"
|
||||||
style={{
|
|
||||||
color: 'var(--app-primary-foreground)',
|
|
||||||
}}
|
|
||||||
onClick={onNewSession}
|
onClick={onNewSession}
|
||||||
title="New Session"
|
title="New Session"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -40,22 +40,25 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="session-selector-backdrop" onClick={onClose} />
|
|
||||||
<div
|
<div
|
||||||
className="session-dropdown"
|
className="session-selector-backdrop fixed top-0 left-0 right-0 bottom-0 z-[999] bg-transparent"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="session-dropdown fixed bg-[var(--app-menu-background)] rounded-[var(--corner-radius-small)] w-[min(400px,calc(100vw-32px))] max-h-[min(500px,50vh)] flex flex-col shadow-[0_4px_16px_rgba(0,0,0,0.1)] z-[1000] outline-none text-[var(--vscode-chat-font-size,13px)] font-[var(--vscode-chat-font-family)]"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
style={{
|
style={{
|
||||||
top: '34px',
|
top: '30px',
|
||||||
left: '10px',
|
left: '10px',
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Search Box */}
|
{/* Search Box */}
|
||||||
<div className="session-search">
|
<div className="session-search p-2 flex items-center gap-2">
|
||||||
<SearchIcon className="session-search-icon" />
|
<SearchIcon className="session-search-icon w-4 h-4 opacity-50 flex-shrink-0 text-[var(--app-primary-foreground)]" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="session-search-input"
|
className="session-search-input flex-1 bg-transparent border-none outline-none text-[var(--app-menu-foreground)] text-[var(--vscode-chat-font-size,13px)] font-[var(--vscode-chat-font-family)] p-0 placeholder:text-[var(--app-input-placeholder-foreground)] placeholder:opacity-60"
|
||||||
placeholder="Search sessions…"
|
placeholder="Search sessions…"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => onSearchChange(e.target.value)}
|
onChange={(e) => onSearchChange(e.target.value)}
|
||||||
@@ -63,9 +66,10 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Session List with Grouping */}
|
{/* Session List with Grouping */}
|
||||||
<div className="session-list-content">
|
<div className="session-list-content overflow-y-auto flex-1 select-none p-2">
|
||||||
{hasNoSessions ? (
|
{hasNoSessions ? (
|
||||||
<div
|
<div
|
||||||
|
className="p-5 text-center text-[var(--app-secondary-foreground)]"
|
||||||
style={{
|
style={{
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
@@ -77,8 +81,10 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
groupSessionsByDate(sessions).map((group) => (
|
groupSessionsByDate(sessions).map((group) => (
|
||||||
<React.Fragment key={group.label}>
|
<React.Fragment key={group.label}>
|
||||||
<div className="session-group-label">{group.label}</div>
|
<div className="session-group-label p-1 px-2 text-[var(--app-primary-foreground)] opacity-50 text-[0.9em] font-medium [&:not(:first-child)]:mt-2">
|
||||||
<div className="session-group">
|
{group.label}
|
||||||
|
</div>
|
||||||
|
<div className="session-group flex flex-col gap-[2px]">
|
||||||
{group.sessions.map((session) => {
|
{group.sessions.map((session) => {
|
||||||
const sessionId =
|
const sessionId =
|
||||||
(session.id as string) ||
|
(session.id as string) ||
|
||||||
@@ -97,14 +103,20 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={sessionId}
|
key={sessionId}
|
||||||
className={`session-item ${isActive ? 'active' : ''}`}
|
className={`session-item flex items-center justify-between py-1.5 px-2 bg-transparent border-none rounded-md cursor-pointer text-left w-full text-[var(--vscode-chat-font-size,13px)] font-[var(--vscode-chat-font-family)] text-[var(--app-primary-foreground)] transition-colors duration-100 hover:bg-[var(--app-list-hover-background)] ${
|
||||||
|
isActive
|
||||||
|
? 'active bg-[var(--app-list-active-background)] text-[var(--app-list-active-foreground)] font-[600]'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSelectSession(sessionId);
|
onSelectSession(sessionId);
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="session-item-title">{title}</span>
|
<span className="session-item-title flex-1 overflow-hidden text-ellipsis whitespace-nowrap min-w-0">
|
||||||
<span className="session-item-time">
|
{title}
|
||||||
|
</span>
|
||||||
|
<span className="session-item-time opacity-60 text-[0.9em] flex-shrink-0 ml-3">
|
||||||
{getTimeAgo(lastUpdated)}
|
{getTimeAgo(lastUpdated)}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -88,9 +88,6 @@
|
|||||||
/* Widget */
|
/* Widget */
|
||||||
--app-widget-border: var(--vscode-editorWidget-border);
|
--app-widget-border: var(--vscode-editorWidget-border);
|
||||||
--app-widget-shadow: var(--vscode-widget-shadow);
|
--app-widget-shadow: var(--vscode-widget-shadow);
|
||||||
|
|
||||||
/* Ghost Button (from Claude Code) */
|
|
||||||
--app-ghost-button-hover-background: var(--vscode-toolbar-hoverBackground);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Light Theme Overrides */
|
/* Light Theme Overrides */
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
/* Import component styles */
|
/* Import component styles */
|
||||||
@import '../components/SaveSessionDialog.css';
|
@import '../components/SaveSessionDialog.css';
|
||||||
@import '../components/SessionManager.css';
|
|
||||||
@import '../components/EmptyState.css';
|
@import '../components/EmptyState.css';
|
||||||
@import '../components/CompletionMenu.css';
|
@import '../components/CompletionMenu.css';
|
||||||
@import '../components/ContextPills.css';
|
@import '../components/ContextPills.css';
|
||||||
@@ -19,220 +18,6 @@
|
|||||||
@import '../components/toolcalls/shared/DiffDisplay.css';
|
@import '../components/toolcalls/shared/DiffDisplay.css';
|
||||||
@import '../components/messages/AssistantMessage.css';
|
@import '../components/messages/AssistantMessage.css';
|
||||||
|
|
||||||
/* ===========================
|
|
||||||
Session Selector Button (from Claude Code .E)
|
|
||||||
=========================== */
|
|
||||||
.session-selector-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
min-width: 0;
|
|
||||||
max-width: 300px;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: var(--vscode-chat-font-size, 13px);
|
|
||||||
font-family: var(--vscode-chat-font-family);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-selector-button:focus,
|
|
||||||
.session-selector-button:hover {
|
|
||||||
background: var(--app-ghost-button-hover-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Session Selector Button Internal Elements */
|
|
||||||
.session-selector-button-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
max-width: 300px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-selector-button-title {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-selector-button-icon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-selector-button-icon svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
min-width: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===========================
|
|
||||||
Icon Button (from Claude Code .j)
|
|
||||||
=========================== */
|
|
||||||
.icon-button {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
outline: none;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button:focus,
|
|
||||||
.icon-button:hover {
|
|
||||||
background: var(--app-ghost-button-hover-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===========================
|
|
||||||
Session Dropdown (from Claude Code .St/.Wt)
|
|
||||||
=========================== */
|
|
||||||
.session-dropdown {
|
|
||||||
position: fixed;
|
|
||||||
background: var(--app-menu-background);
|
|
||||||
border: 1px solid var(--app-menu-border);
|
|
||||||
border-radius: var(--corner-radius-small);
|
|
||||||
width: min(400px, calc(100vw - 32px));
|
|
||||||
max-height: min(500px, 50vh);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
||||||
z-index: 1000;
|
|
||||||
outline: none;
|
|
||||||
font-size: var(--vscode-chat-font-size, 13px);
|
|
||||||
font-family: var(--vscode-chat-font-family);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===========================
|
|
||||||
Search Box Container (from Claude Code .Lt)
|
|
||||||
=========================== */
|
|
||||||
.session-search {
|
|
||||||
padding: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
border-bottom: 1px solid var(--app-menu-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-search svg,
|
|
||||||
.session-search-icon {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
opacity: 0.5;
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: var(--app-primary-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Search Input (from Claude Code .U) */
|
|
||||||
.session-search-input {
|
|
||||||
flex: 1;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
color: var(--app-menu-foreground);
|
|
||||||
font-size: var(--vscode-chat-font-size, 13px);
|
|
||||||
font-family: var(--vscode-chat-font-family);
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-search-input::placeholder {
|
|
||||||
color: var(--app-input-placeholder-foreground);
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Session List Content Area (from Claude Code .jt/.It) */
|
|
||||||
.session-list-content {
|
|
||||||
overflow-y: auto;
|
|
||||||
flex: 1;
|
|
||||||
user-select: none;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Group Label (from Claude Code .ae) */
|
|
||||||
.session-group-label {
|
|
||||||
padding: 4px 8px;
|
|
||||||
color: var(--app-primary-foreground);
|
|
||||||
opacity: 0.5;
|
|
||||||
font-size: 0.9em;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-group-label:not(:first-child) {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Session Group Container (from Claude Code .At) */
|
|
||||||
.session-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Session Item Button (from Claude Code .s) */
|
|
||||||
.session-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 6px 8px;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
font-size: var(--vscode-chat-font-size, 13px);
|
|
||||||
font-family: var(--vscode-chat-font-family);
|
|
||||||
color: var(--app-primary-foreground);
|
|
||||||
transition: background 0.1s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-item:hover {
|
|
||||||
background: var(--app-list-hover-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Active Session (from Claude Code .N) */
|
|
||||||
.session-item.active {
|
|
||||||
background: var(--app-list-active-background);
|
|
||||||
color: var(--app-list-active-foreground);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Session Title (from Claude Code .ce) */
|
|
||||||
.session-item-title {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Session Time (from Claude Code .Dt) */
|
|
||||||
.session-item-time {
|
|
||||||
opacity: 0.6;
|
|
||||||
font-size: 0.9em;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Backdrop for dropdown */
|
|
||||||
.session-selector-backdrop {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 999;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===========================
|
/* ===========================
|
||||||
CSS Variables (from Claude Code root styles)
|
CSS Variables (from Claude Code root styles)
|
||||||
|
|||||||
@@ -7,3 +7,32 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Reusable Component Classes
|
||||||
|
=========================== */
|
||||||
|
@layer components {
|
||||||
|
.btn-ghost {
|
||||||
|
@apply bg-transparent border border-transparent rounded cursor-pointer outline-none transition-colors duration-200;
|
||||||
|
color: var(--app-primary-foreground);
|
||||||
|
font-size: var(--vscode-chat-font-size, 13px);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost:hover,
|
||||||
|
.btn-ghost:focus {
|
||||||
|
background: var(--app-ghost-button-hover-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
@apply p-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-md {
|
||||||
|
@apply py-small px-medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sm {
|
||||||
|
@apply w-4 h-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ export default {
|
|||||||
'./src/webview/components/InputForm.tsx',
|
'./src/webview/components/InputForm.tsx',
|
||||||
'./src/webview/components/PermissionDrawer.tsx',
|
'./src/webview/components/PermissionDrawer.tsx',
|
||||||
'./src/webview/components/PlanDisplay.tsx',
|
'./src/webview/components/PlanDisplay.tsx',
|
||||||
|
'./src/webview/components/session/SessionSelector.tsx',
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|||||||
Reference in New Issue
Block a user