mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
refactor(vscode-ide-companion): extract InfoBanner as standalone component
Move info banner UI from App.tsx to dedicated component with open settings functionality.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -59,3 +59,4 @@ gha-creds-*.json
|
||||
patch_output.log
|
||||
|
||||
.claude/
|
||||
pnpm-lock.yaml
|
||||
|
||||
@@ -532,76 +532,6 @@ button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Info Banner (at bottom)
|
||||
=========================== */
|
||||
.info-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background-color: var(--app-input-secondary-background);
|
||||
border-top: 1px solid var(--app-primary-border-color);
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.banner-icon {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: var(--app-primary-foreground);
|
||||
}
|
||||
|
||||
.banner-content label {
|
||||
font-size: 13px;
|
||||
color: var(--app-primary-foreground);
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.banner-link {
|
||||
color: var(--app-qwen-orange);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.banner-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.banner-close {
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--corner-radius-small);
|
||||
color: var(--app-primary-foreground);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.banner-close:hover {
|
||||
background-color: var(--app-ghost-button-hover-background);
|
||||
}
|
||||
|
||||
.banner-close svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Claude Code Style Input Form (.Me > .u)
|
||||
=========================== */
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from './components/CompletionMenu.js';
|
||||
import { useCompletionTrigger } from './hooks/useCompletionTrigger.js';
|
||||
import { SaveSessionDialog } from './components/SaveSessionDialog.js';
|
||||
import { InfoBanner } from './components/InfoBanner.js';
|
||||
|
||||
interface ToolCallUpdate {
|
||||
type: 'tool_call' | 'tool_call_update';
|
||||
@@ -1402,53 +1403,14 @@ export const App: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Info Banner */}
|
||||
{showBanner && (
|
||||
<div className="info-banner">
|
||||
<div className="banner-content">
|
||||
<svg
|
||||
className="banner-icon"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M5.14648 7.14648C5.34175 6.95122 5.65825 6.95122 5.85352 7.14648L8.35352 9.64648C8.44728 9.74025 8.5 9.86739 8.5 10C8.5 10.0994 8.47037 10.1958 8.41602 10.2773L8.35352 10.3535L5.85352 12.8535C5.65825 13.0488 5.34175 13.0488 5.14648 12.8535C4.95122 12.6583 4.95122 12.3417 5.14648 12.1465L7.29297 10L5.14648 7.85352C4.95122 7.65825 4.95122 7.34175 5.14648 7.14648Z"></path>
|
||||
<path d="M14.5 12C14.7761 12 15 12.2239 15 12.5C15 12.7761 14.7761 13 14.5 13H9.5C9.22386 13 9 12.7761 9 12.5C9 12.2239 9.22386 12 9.5 12H14.5Z"></path>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16.5 4C17.3284 4 18 4.67157 18 5.5V14.5C18 15.3284 17.3284 16 16.5 16H3.5C2.67157 16 2 15.3284 2 14.5V5.5C2 4.67157 2.67157 4 3.5 4H16.5ZM3.5 5C3.22386 5 3 5.22386 3 5.5V14.5C3 14.7761 3.22386 15 3.5 15H16.5C16.7761 15 17 14.7761 17 14.5V5.5C17 5.22386 16.7761 5 16.5 5H3.5Z"
|
||||
></path>
|
||||
</svg>
|
||||
<label>
|
||||
Prefer the Terminal experience?{' '}
|
||||
<a href="#" className="banner-link">
|
||||
Switch back in Settings.
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
className="banner-close"
|
||||
aria-label="Close banner"
|
||||
onClick={() => setShowBanner(false)}
|
||||
>
|
||||
<svg
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1 1L13 13M1 13L13 1"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<InfoBanner
|
||||
visible={showBanner}
|
||||
onDismiss={() => setShowBanner(false)}
|
||||
onLinkClick={(e) => {
|
||||
e.preventDefault();
|
||||
vscode.postMessage({ type: 'openSettings', data: {} });
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="input-form-container">
|
||||
<div className="input-form-wrapper">
|
||||
|
||||
@@ -207,6 +207,10 @@ export class MessageHandler {
|
||||
await this.handleResumeSession(data?.sessionId as string);
|
||||
break;
|
||||
|
||||
case 'openSettings':
|
||||
await this.handleOpenSettings();
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('[MessageHandler] Unknown message type:', message.type);
|
||||
break;
|
||||
@@ -922,4 +926,24 @@ export class MessageHandler {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理打开设置请求
|
||||
* 打开 VSCode 设置页面并定位到扩展配置
|
||||
*/
|
||||
private async handleOpenSettings(): Promise<void> {
|
||||
try {
|
||||
console.log('[MessageHandler] Opening settings');
|
||||
await vscode.commands.executeCommand(
|
||||
'workbench.action.openSettings',
|
||||
'qwenCode',
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('[MessageHandler] Failed to open settings:', error);
|
||||
this.sendToWebView({
|
||||
type: 'error',
|
||||
data: { message: `Failed to open settings: ${error}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
interface InfoBannerProps {
|
||||
/**
|
||||
* Whether the banner is visible
|
||||
*/
|
||||
visible: boolean;
|
||||
|
||||
/**
|
||||
* Callback when the banner is dismissed
|
||||
*/
|
||||
onDismiss: () => void;
|
||||
|
||||
/**
|
||||
* Optional: Custom message content (if not provided, uses default)
|
||||
*/
|
||||
message?: React.ReactNode;
|
||||
|
||||
/**
|
||||
* Optional: Custom link text
|
||||
*/
|
||||
linkText?: string;
|
||||
|
||||
/**
|
||||
* Optional: Callback when the link is clicked
|
||||
*/
|
||||
onLinkClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
|
||||
}
|
||||
|
||||
export const InfoBanner: React.FC<InfoBannerProps> = ({
|
||||
visible,
|
||||
onDismiss,
|
||||
message,
|
||||
linkText = 'Switch back in Settings.',
|
||||
onLinkClick,
|
||||
}) => {
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border-t"
|
||||
style={{
|
||||
gap: '12px',
|
||||
padding: '12px 16px',
|
||||
backgroundColor: 'var(--app-input-secondary-background)',
|
||||
borderColor: 'var(--app-primary-border-color)',
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center flex-1 min-w-0" style={{ gap: '12px' }}>
|
||||
{/* Icon */}
|
||||
<svg
|
||||
className="flex-shrink-0 w-4 h-4"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ fill: 'var(--app-primary-foreground)' }}
|
||||
>
|
||||
<path d="M5.14648 7.14648C5.34175 6.95122 5.65825 6.95122 5.85352 7.14648L8.35352 9.64648C8.44728 9.74025 8.5 9.86739 8.5 10C8.5 10.0994 8.47037 10.1958 8.41602 10.2773L8.35352 10.3535L5.85352 12.8535C5.65825 13.0488 5.34175 13.0488 5.14648 12.8535C4.95122 12.6583 4.95122 12.3417 5.14648 12.1465L7.29297 10L5.14648 7.85352C4.95122 7.65825 4.95122 7.34175 5.14648 7.14648Z"></path>
|
||||
<path d="M14.5 12C14.7761 12 15 12.2239 15 12.5C15 12.7761 14.7761 13 14.5 13H9.5C9.22386 13 9 12.7761 9 12.5C9 12.2239 9.22386 12 9.5 12H14.5Z"></path>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16.5 4C17.3284 4 18 4.67157 18 5.5V14.5C18 15.3284 17.3284 16 16.5 16H3.5C2.67157 16 2 15.3284 2 14.5V5.5C2 4.67157 2.67157 4 3.5 4H16.5ZM3.5 5C3.22386 5 3 5.22386 3 5.5V14.5C3 14.7761 3.22386 15 3.5 15H16.5C16.7761 15 17 14.7761 17 14.5V5.5C17 5.22386 16.7761 5 16.5 5H3.5Z"
|
||||
></path>
|
||||
</svg>
|
||||
|
||||
{/* Message */}
|
||||
<label
|
||||
className="m-0 leading-snug text-[13px]"
|
||||
style={{ color: 'var(--app-primary-foreground)' }}
|
||||
>
|
||||
{message || (
|
||||
<>
|
||||
Prefer the Terminal experience?{' '}
|
||||
{onLinkClick && (
|
||||
<a
|
||||
href="#"
|
||||
className="no-underline hover:underline cursor-pointer outline-none"
|
||||
style={{ color: 'var(--app-qwen-orange)' }}
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
{linkText}
|
||||
</a>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Close button */}
|
||||
<button
|
||||
className="flex items-center justify-center cursor-pointer p-1.5 rounded"
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: 'var(--app-secondary-foreground)',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor =
|
||||
'var(--app-ghost-button-hover-background)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
}}
|
||||
aria-label="Close banner"
|
||||
onClick={onDismiss}
|
||||
>
|
||||
<svg
|
||||
className="w-[10px] h-[10px]"
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1 1L13 13M1 13L13 1"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user