feat(vscode-ide-companion): enhance panel manager group tracking and ui improvements

chore(vscode-ide-companion): remove unused todo comment in auth state manager
This commit is contained in:
yiliang114
2025-12-10 00:46:22 +08:00
parent d2e2a07327
commit 4345b9370e
5 changed files with 49 additions and 36 deletions

View File

@@ -21,7 +21,6 @@ export class AuthStateManager {
private static context: vscode.ExtensionContext | null = null; private static context: vscode.ExtensionContext | null = null;
private static readonly AUTH_STATE_KEY = 'qwen.authState'; private static readonly AUTH_STATE_KEY = 'qwen.authState';
private static readonly AUTH_CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours private static readonly AUTH_CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
//TODO:
private constructor() {} private constructor() {}
/** /**

View File

@@ -646,8 +646,7 @@ export const App: React.FC = () => {
<div <div
ref={messagesContainerRef} ref={messagesContainerRef}
className="chat-messages flex-1 overflow-y-auto overflow-x-hidden pt-5 pr-5 pl-5 pb-[120px] flex flex-col relative min-w-0 focus:outline-none [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-white/20 [&::-webkit-scrollbar-thumb]:rounded-sm [&::-webkit-scrollbar-thumb:hover]:bg-white/30 [&>*]:flex [&>*]:gap-0 [&>*]:items-start [&>*]:text-left [&>*]:py-2 [&>*:not(:last-child)]:pb-[8px] [&>*]:flex-col [&>*]:relative [&>*]:animate-[fadeIn_0.2s_ease-in]" className="chat-messages messages-container flex-1 overflow-y-auto overflow-x-hidden pt-5 pr-5 pl-5 pb-[120px] flex flex-col relative min-w-0 focus:outline-none [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-white/20 [&::-webkit-scrollbar-thumb]:rounded-sm [&::-webkit-scrollbar-thumb]:hover:bg-white/30 [&>*]:flex [&>*]:gap-0 [&>*]:items-start [&>*]:text-left [&>*]:py-2 [&>*:not(:last-child)]:pb-[8px] [&>*]:flex-col [&>*]:relative [&>*]:animate-[fadeIn_0.2s_ease-in]"
style={{ backgroundColor: 'var(--app-primary-background)' }}
> >
{!hasContent ? ( {!hasContent ? (
<EmptyState /> <EmptyState />
@@ -655,12 +654,17 @@ export const App: React.FC = () => {
<> <>
{/* Render all messages and tool calls */} {/* Render all messages and tool calls */}
{renderMessages()} {renderMessages()}
{messageHandling.isWaitingForResponse && {/* Flow-in persistent slot: keeps a small constant height so toggling */}
messageHandling.loadingMessage && ( {/* the waiting message doesn't change list height to zero. When */}
<WaitingMessage {/* active, render the waiting message inline (not fixed). */}
loadingMessage={messageHandling.loadingMessage} <div className="waiting-message-slot min-h-[28px]">
/> {messageHandling.isWaitingForResponse &&
)} messageHandling.loadingMessage && (
<WaitingMessage
loadingMessage={messageHandling.loadingMessage}
/>
)}
</div>
<div ref={messagesEndRef} /> <div ref={messagesEndRef} />
</> </>

View File

@@ -201,8 +201,17 @@ export class PanelManager {
*/ */
revealPanel(preserveFocus: boolean = true): void { revealPanel(preserveFocus: boolean = true): void {
if (this.panel) { if (this.panel) {
// Reveal without forcing a specific column to avoid reflowing groups. // Prefer revealing in the currently tracked group to avoid reflowing groups.
this.panel.reveal(undefined, preserveFocus); const trackedColumn = (
this.panelTab as unknown as {
group?: { viewColumn?: vscode.ViewColumn };
}
)?.group?.viewColumn as vscode.ViewColumn | undefined;
const targetColumn: vscode.ViewColumn =
trackedColumn ??
this.panelGroupViewColumn ??
vscode.window.tabGroups.activeTabGroup.viewColumn;
this.panel.reveal(targetColumn, preserveFocus);
} }
} }
@@ -236,7 +245,7 @@ export class PanelManager {
group?: { viewColumn?: vscode.ViewColumn }; group?: { viewColumn?: vscode.ViewColumn };
} }
)?.group?.viewColumn; )?.group?.viewColumn;
if (groupViewColumn != null) { if (groupViewColumn !== null) {
this.panelGroupViewColumn = groupViewColumn as vscode.ViewColumn; this.panelGroupViewColumn = groupViewColumn as vscode.ViewColumn;
} }
} catch { } catch {
@@ -257,7 +266,7 @@ export class PanelManager {
this.panel.onDidDispose( this.panel.onDidDispose(
() => { () => {
// Capture the group we intend to clean up before we clear fields // Capture the group we intend to clean up before we clear fields
const targetColumn = const targetColumn: vscode.ViewColumn | null =
// Prefer the group from the captured tab if available // Prefer the group from the captured tab if available
(( ((
this.panelTab as unknown as { this.panelTab as unknown as {
@@ -266,7 +275,7 @@ export class PanelManager {
)?.group?.viewColumn as vscode.ViewColumn | undefined) ?? )?.group?.viewColumn as vscode.ViewColumn | undefined) ??
// Fall back to our last-known group column // Fall back to our last-known group column
this.panelGroupViewColumn ?? this.panelGroupViewColumn ??
undefined; null;
this.panel = null; this.panel = null;
this.panelTab = null; this.panelTab = null;
@@ -275,18 +284,19 @@ export class PanelManager {
// After VS Code updates its tab model, check if that group is now // After VS Code updates its tab model, check if that group is now
// empty (and typically locked for Qwen). If so, close the group to // empty (and typically locked for Qwen). If so, close the group to
// avoid leaving an empty locked column when the user closes Qwen. // avoid leaving an empty locked column when the user closes Qwen.
if (targetColumn != null) { if (targetColumn !== null) {
const column: vscode.ViewColumn = targetColumn;
setTimeout(async () => { setTimeout(async () => {
try { try {
const groups = vscode.window.tabGroups.all; const groups = vscode.window.tabGroups.all;
const group = groups.find((g) => g.viewColumn === targetColumn); const group = groups.find((g) => g.viewColumn === column);
// If the group that hosted Qwen is now empty, close it to avoid // If the group that hosted Qwen is now empty, close it to avoid
// leaving an empty locked column around. VS Code's stable API // leaving an empty locked column around. VS Code's stable API
// does not expose the lock state on TabGroup, so we only check // does not expose the lock state on TabGroup, so we only check
// for emptiness here. // for emptiness here.
if (group && group.tabs.length === 0) { if (group && group.tabs.length === 0) {
// Focus the group we want to close // Focus the group we want to close
await this.focusGroupByColumn(targetColumn); await this.focusGroupByColumn(column);
// Try closeGroup first; fall back to removeActiveEditorGroup // Try closeGroup first; fall back to removeActiveEditorGroup
try { try {
await vscode.commands.executeCommand( await vscode.commands.executeCommand(

View File

@@ -65,20 +65,10 @@ export const WaitingMessage: React.FC<WaitingMessageProps> = ({
}, [phrases]); }, [phrases]);
return ( return (
<div className="flex gap-0 items-start text-left py-2 flex-col opacity-85"> <div className="waiting-message-outer flex gap-0 items-start text-left py-2 flex-col opacity-85">
{/* Use the same left status icon (pseudo-element) style as assistant-message-container */} {/* Use the same left status icon (pseudo-element) style as assistant-message-container */}
<div <div className="assistant-message-container assistant-message-loading waiting-message-inner w-full items-start pl-[30px] relative">
className="assistant-message-container assistant-message-loading" <span className="waiting-message-text opacity-70 italic loading-text-shimmer">
style={{
width: '100%',
alignItems: 'flex-start',
paddingLeft: '30px', // reserve space for ::before bullet
position: 'relative',
paddingTop: '8px',
paddingBottom: '8px',
}}
>
<span className="opacity-70 italic loading-text-shimmer">
{phrases[index]} {phrases[index]}
</span> </span>
</div> </div>

View File

@@ -47,9 +47,15 @@
--app-input-highlight: var(--app-qwen-theme); --app-input-highlight: var(--app-qwen-theme);
/* Code Highlighting */ /* Code Highlighting */
--app-code-background: var(--vscode-textCodeBlock-background, rgba(0, 0, 0, 0.05)); --app-code-background: var(
--app-link-foreground: var(--vscode-textLink-foreground, #007ACC); --vscode-textCodeBlock-background,
--app-link-active-foreground: var(--vscode-textLink-activeForeground, #005A9E); rgba(0, 0, 0, 0.05)
);
--app-link-foreground: var(--vscode-textLink-foreground, #007acc);
--app-link-active-foreground: var(
--vscode-textLink-activeForeground,
#005a9e
);
/* List Styles */ /* List Styles */
--app-list-hover-background: var(--vscode-list-hoverBackground); --app-list-hover-background: var(--vscode-list-hoverBackground);
@@ -167,7 +173,8 @@ button {
} }
@keyframes pulse { @keyframes pulse {
0%, 100% { 0%,
100% {
opacity: 1; opacity: 1;
} }
50% { 50% {
@@ -176,7 +183,9 @@ button {
} }
@keyframes typingPulse { @keyframes typingPulse {
0%, 60%, 100% { 0%,
60%,
100% {
transform: scale(0.7); transform: scale(0.7);
opacity: 0.6; opacity: 0.6;
} }
@@ -251,7 +260,8 @@ button {
/* Animation for in-progress status (used by pseudo bullets and spinners) */ /* Animation for in-progress status (used by pseudo bullets and spinners) */
@keyframes pulse { @keyframes pulse {
0%, 100% { 0%,
100% {
opacity: 1; opacity: 1;
} }
50% { 50% {