mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-22 09:47:47 +00:00
wip(vscode-ide-companion): OnboardingPage
This commit is contained in:
@@ -29,7 +29,7 @@ import { PermissionDrawer } from './components/PermissionDrawer/PermissionDrawer
|
||||
import { ToolCall } from './components/messages/toolcalls/ToolCall.js';
|
||||
import { hasToolCallOutput } from './components/messages/toolcalls/shared/utils.js';
|
||||
import { EmptyState } from './components/layout/EmptyState.js';
|
||||
import { OnboardingPage } from './components/layout/OnboardingPage.js';
|
||||
import { Onboarding } from './components/layout/Onboarding.js';
|
||||
import { type CompletionItem } from '../types/completionItemTypes.js';
|
||||
import { useCompletionTrigger } from './hooks/useCompletionTrigger.js';
|
||||
import { ChatHeader } from './components/layout/ChatHeader.js';
|
||||
@@ -670,7 +670,7 @@ export const App: React.FC = () => {
|
||||
>
|
||||
{!hasContent ? (
|
||||
isAuthenticated === false ? (
|
||||
<OnboardingPage
|
||||
<Onboarding
|
||||
onLogin={() => {
|
||||
vscode.postMessage({ type: 'login', data: {} });
|
||||
messageHandling.setWaitingForResponse(
|
||||
|
||||
@@ -17,6 +17,19 @@ import { getFileName } from './utils/webviewUtils.js';
|
||||
import { type ApprovalModeValue } from '../types/acpTypes.js';
|
||||
import { isAuthenticationRequiredError } from '../utils/authErrors.js';
|
||||
|
||||
/**
|
||||
* WebView Provider Class
|
||||
*
|
||||
* Manages the WebView panel lifecycle, agent connection, and message handling.
|
||||
* Acts as the central coordinator between VS Code extension and WebView UI.
|
||||
*
|
||||
* Key responsibilities:
|
||||
* - WebView panel creation and management
|
||||
* - Qwen agent connection and session management
|
||||
* - Message routing between extension and WebView
|
||||
* - Authentication state handling
|
||||
* - Permission request processing
|
||||
*/
|
||||
export class WebViewProvider {
|
||||
private panelManager: PanelManager;
|
||||
private messageHandler: MessageHandler;
|
||||
@@ -122,7 +135,6 @@ export class WebViewProvider {
|
||||
|
||||
// Setup end-turn handler from ACP stopReason notifications
|
||||
this.agentManager.onEndTurn((reason) => {
|
||||
console.log(' ============== ', reason);
|
||||
// Ensure WebView exits streaming state even if no explicit streamEnd was emitted elsewhere
|
||||
this.sendMessageToWebView({
|
||||
type: 'streamEnd',
|
||||
@@ -521,6 +533,12 @@ export class WebViewProvider {
|
||||
/**
|
||||
* Attempt to restore authentication state and initialize connection
|
||||
* This is called when the webview is first shown
|
||||
*
|
||||
* This method tries to establish a connection without forcing authentication,
|
||||
* allowing detection of existing authentication state. If connection fails,
|
||||
* initializes an empty conversation to allow browsing history.
|
||||
*
|
||||
* @returns Promise<void> - Resolves when auth state restoration attempt is complete
|
||||
*/
|
||||
private async attemptAuthStateRestoration(): Promise<void> {
|
||||
try {
|
||||
@@ -550,6 +568,16 @@ export class WebViewProvider {
|
||||
|
||||
/**
|
||||
* Internal: perform actual connection/initialization (no auth locking).
|
||||
*
|
||||
* This method handles the complete agent connection and initialization workflow:
|
||||
* 1. Detects if Qwen CLI is installed
|
||||
* 2. If CLI is not installed, prompts user for installation
|
||||
* 3. If CLI is installed, attempts to connect to the agent
|
||||
* 4. Handles authentication requirements and session creation
|
||||
* 5. Notifies WebView of connection status
|
||||
*
|
||||
* @param options - Connection options including auto-authentication setting
|
||||
* @returns Promise<void> - Resolves when initialization is complete
|
||||
*/
|
||||
private async doInitializeAgentConnection(options?: {
|
||||
autoAuthenticate?: boolean;
|
||||
|
||||
@@ -172,7 +172,7 @@ export const InputForm: React.FC<InputFormProps> = ({
|
||||
|
||||
<div
|
||||
ref={inputFieldRef}
|
||||
contentEditable={'plaintext-only'}
|
||||
contentEditable="plaintext-only"
|
||||
className="composer-input"
|
||||
role="textbox"
|
||||
aria-label="Message input"
|
||||
@@ -186,9 +186,6 @@ export const InputForm: React.FC<InputFormProps> = ({
|
||||
: 'false'
|
||||
}
|
||||
onInput={(e) => {
|
||||
if (composerDisabled) {
|
||||
return;
|
||||
}
|
||||
const target = e.target as HTMLDivElement;
|
||||
// Filter out zero-width space that we use to maintain height
|
||||
const text = target.textContent?.replace(/\u200B/g, '') || '';
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
import type React from 'react';
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { generateIconUrl } from '../../utils/resourceUrl.js';
|
||||
|
||||
interface OnboardingPageProps {
|
||||
onLogin: () => void;
|
||||
}
|
||||
|
||||
export const OnboardingPage: React.FC<OnboardingPageProps> = ({ onLogin }) => {
|
||||
export const Onboarding: React.FC<OnboardingPageProps> = ({ onLogin }) => {
|
||||
const iconUri = generateIconUrl('icon.png');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full p-5 md:p-10">
|
||||
<div className="flex flex-col items-center gap-8 w-full max-w-md">
|
||||
<div className="flex flex-col items-center gap-6">
|
||||
{/* Application icon container with brand logo and decorative close icon */}
|
||||
<div className="relative">
|
||||
<img
|
||||
src={iconUri}
|
||||
alt="Qwen Code Logo"
|
||||
className="w-[80px] h-[80px] object-contain"
|
||||
/>
|
||||
{/* Decorative close icon for enhanced visual effect */}
|
||||
<div className="absolute -top-2 -right-2 w-6 h-6 bg-[#4f46e5] rounded-full flex items-center justify-center">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="white">
|
||||
<path
|
||||
@@ -30,6 +37,7 @@ export const OnboardingPage: React.FC<OnboardingPageProps> = ({ onLogin }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Text content area */}
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-app-primary-foreground mb-2">
|
||||
Welcome to Qwen Code
|
||||
@@ -40,40 +48,12 @@ export const OnboardingPage: React.FC<OnboardingPageProps> = ({ onLogin }) => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* <div className="flex flex-col gap-5 w-full">
|
||||
<div className="bg-app-secondary-background rounded-xl p-5 border border-app-primary-border-color shadow-sm">
|
||||
<h2 className="font-semibold text-app-primary-foreground mb-3">Get Started</h2>
|
||||
<ul className="space-y-2">
|
||||
<li className="flex items-start gap-2">
|
||||
<div className="mt-1 w-1.5 h-1.5 rounded-full bg-[#4f46e5] flex-shrink-0"></div>
|
||||
<span className="text-sm text-app-secondary-foreground">Understand complex codebases faster</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<div className="mt-1 w-1.5 h-1.5 rounded-full bg-[#4f46e5] flex-shrink-0"></div>
|
||||
<span className="text-sm text-app-secondary-foreground">Navigate with AI-powered suggestions</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<div className="mt-1 w-1.5 h-1.5 rounded-full bg-[#4f46e5] flex-shrink-0"></div>
|
||||
<span className="text-sm text-app-secondary-foreground">Transform code with confidence</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div> */}
|
||||
|
||||
<button
|
||||
onClick={onLogin}
|
||||
className="w-full px-4 py-3 bg-[#4f46e5] text-white font-medium rounded-lg shadow-sm"
|
||||
>
|
||||
Log in to Qwen Code
|
||||
</button>
|
||||
|
||||
{/* <div className="text-center">
|
||||
<p className="text-xs text-app-secondary-foreground">
|
||||
By logging in, you agree to the Terms of Service and Privacy
|
||||
Policy.
|
||||
</p>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -329,7 +329,6 @@ export const useWebViewMessages = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('[useWebViewMessages1111]__________ other message:', msg);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -351,30 +350,42 @@ export const useWebViewMessages = ({
|
||||
}
|
||||
|
||||
case 'streamEnd': {
|
||||
// Always end local streaming state and collapse any thoughts
|
||||
// Always end local streaming state and clear thinking state
|
||||
handlers.messageHandling.endStreaming();
|
||||
handlers.messageHandling.clearThinking();
|
||||
|
||||
// If the stream ended due to explicit user cancel, proactively
|
||||
// clear the waiting indicator and reset any tracked exec calls.
|
||||
// This avoids the UI being stuck with the Stop button visible
|
||||
// after rejecting a permission request.
|
||||
// If stream ended due to explicit user cancellation, proactively clear
|
||||
// waiting indicator and reset tracked execution calls.
|
||||
// This avoids UI getting stuck with Stop button visible after
|
||||
// rejecting a permission request.
|
||||
try {
|
||||
const reason = (
|
||||
(message.data as { reason?: string } | undefined)?.reason || ''
|
||||
).toLowerCase();
|
||||
|
||||
/**
|
||||
* Handle different types of stream end reasons:
|
||||
* - 'user_cancelled': User explicitly cancelled operation
|
||||
* - 'cancelled': General cancellation
|
||||
* For these cases, immediately clear all active states
|
||||
*/
|
||||
if (reason === 'user_cancelled' || reason === 'cancelled') {
|
||||
// Clear active execution tool call tracking, reset state
|
||||
activeExecToolCallsRef.current.clear();
|
||||
// Clear waiting response state to ensure UI returns to normal
|
||||
handlers.messageHandling.clearWaitingForResponse();
|
||||
break;
|
||||
}
|
||||
} catch (_error) {
|
||||
// best-effort
|
||||
// Best-effort handling, errors don't affect main flow
|
||||
}
|
||||
|
||||
// Otherwise, clear the generic waiting indicator only if there are
|
||||
// no active long-running tool calls. If there are still active
|
||||
// execute/bash/command calls, keep the hint visible.
|
||||
/**
|
||||
* For other types of stream end (non-user cancellation):
|
||||
* Only clear generic waiting indicator when there are no active
|
||||
* long-running tool calls. If there are still active execute/bash/command
|
||||
* calls, keep the hint visible.
|
||||
*/
|
||||
if (activeExecToolCallsRef.current.size === 0) {
|
||||
handlers.messageHandling.clearWaitingForResponse();
|
||||
}
|
||||
@@ -575,15 +586,21 @@ export const useWebViewMessages = ({
|
||||
// While long-running tools (e.g., execute/bash/command) are in progress,
|
||||
// surface a lightweight loading indicator and expose the Stop button.
|
||||
try {
|
||||
const id = (toolCallData.toolCallId || '').toString();
|
||||
const kind = (toolCallData.kind || '').toString().toLowerCase();
|
||||
const isExec =
|
||||
const isExecKind =
|
||||
kind === 'execute' || kind === 'bash' || kind === 'command';
|
||||
// CLI sometimes omits kind in tool_call_update payloads; fall back to
|
||||
// whether we've already tracked this ID as an exec tool.
|
||||
const wasTrackedExec = activeExecToolCallsRef.current.has(id);
|
||||
const isExec = isExecKind || wasTrackedExec;
|
||||
|
||||
if (isExec) {
|
||||
const id = (toolCallData.toolCallId || '').toString();
|
||||
if (!isExec || !id) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Maintain the active set by status
|
||||
if (status === 'pending' || status === 'in_progress') {
|
||||
if (status === 'pending' || status === 'in_progress') {
|
||||
if (isExecKind) {
|
||||
activeExecToolCallsRef.current.add(id);
|
||||
|
||||
// Build a helpful hint from rawInput
|
||||
@@ -597,14 +614,14 @@ export const useWebViewMessages = ({
|
||||
}
|
||||
const hint = cmd ? `Running: ${cmd}` : 'Running command...';
|
||||
handlers.messageHandling.setWaitingForResponse(hint);
|
||||
} else if (status === 'completed' || status === 'failed') {
|
||||
activeExecToolCallsRef.current.delete(id);
|
||||
}
|
||||
} else if (status === 'completed' || status === 'failed') {
|
||||
activeExecToolCallsRef.current.delete(id);
|
||||
}
|
||||
|
||||
// If no active exec tool remains, clear the waiting message.
|
||||
if (activeExecToolCallsRef.current.size === 0) {
|
||||
handlers.messageHandling.clearWaitingForResponse();
|
||||
}
|
||||
// If no active exec tool remains, clear the waiting message.
|
||||
if (activeExecToolCallsRef.current.size === 0) {
|
||||
handlers.messageHandling.clearWaitingForResponse();
|
||||
}
|
||||
} catch (_error) {
|
||||
// Best-effort UI hint; ignore errors
|
||||
|
||||
Reference in New Issue
Block a user