diff --git a/packages/vscode-ide-companion/esbuild.js b/packages/vscode-ide-companion/esbuild.js index 032c3c138..567b6f363 100644 --- a/packages/vscode-ide-companion/esbuild.js +++ b/packages/vscode-ide-companion/esbuild.js @@ -5,10 +5,17 @@ */ import esbuild from 'esbuild'; +import { createRequire } from 'node:module'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; const production = process.argv.includes('--production'); const watch = process.argv.includes('--watch'); +const __dirname = dirname(fileURLToPath(import.meta.url)); +const repoRoot = resolve(__dirname, '..', '..'); +const rootRequire = createRequire(resolve(repoRoot, 'package.json')); + /** * @type {import('esbuild').Plugin} */ @@ -31,6 +38,42 @@ const esbuildProblemMatcherPlugin = { }, }; +/** + * Ensure a single React copy in the webview bundle by resolving from repo root. + * Prevents mixing React 18/19 element types when nested node_modules exist. + * @type {import('esbuild').Plugin} + */ +const resolveFromRoot = (moduleId) => { + try { + return rootRequire.resolve(moduleId); + } catch { + return null; + } +}; + +const reactDedupPlugin = { + name: 'react-dedup', + setup(build) { + const aliases = [ + 'react', + 'react-dom', + 'react-dom/client', + 'react/jsx-runtime', + 'react/jsx-dev-runtime', + ]; + + for (const alias of aliases) { + build.onResolve({ filter: new RegExp(`^${alias}$`) }, () => { + const resolved = resolveFromRoot(alias); + if (!resolved) { + return undefined; + } + return { path: resolved }; + }); + } + }, +}; + /** * @type {import('esbuild').Plugin} */ @@ -128,7 +171,7 @@ async function main() { platform: 'browser', outfile: 'dist/webview.js', logLevel: 'silent', - plugins: [cssInjectPlugin, esbuildProblemMatcherPlugin], + plugins: [reactDedupPlugin, cssInjectPlugin, esbuildProblemMatcherPlugin], jsx: 'automatic', // Use new JSX transform (React 17+) define: { 'process.env.NODE_ENV': production ? '"production"' : '"development"', diff --git a/packages/vscode-ide-companion/src/webview/WebViewProvider.ts b/packages/vscode-ide-companion/src/webview/WebViewProvider.ts index 5aa92c0fb..5e112673a 100644 --- a/packages/vscode-ide-companion/src/webview/WebViewProvider.ts +++ b/packages/vscode-ide-companion/src/webview/WebViewProvider.ts @@ -29,6 +29,7 @@ export class WebViewProvider { private pendingPermissionResolve: ((optionId: string) => void) | null = null; // Track current ACP mode id to influence permission/diff behavior private currentModeId: ApprovalModeValue | null = null; + private authState: boolean | null = null; constructor( private context: vscode.ExtensionContext, @@ -416,6 +417,10 @@ export class WebViewProvider { if (message.type === 'openDiff' && this.isAutoMode()) { return; } + if (message.type === 'webviewReady') { + this.handleWebviewReady(); + return; + } // Allow webview to request updating the VS Code tab title if (message.type === 'updatePanelTitle') { const title = String( @@ -874,10 +879,72 @@ export class WebViewProvider { } } + /** + * Track authentication state based on outbound messages to the webview. + */ + private updateAuthStateFromMessage(message: unknown): void { + if (!message || typeof message !== 'object') { + return; + } + const msg = message as { + type?: string; + data?: { authenticated?: boolean | null }; + }; + + switch (msg.type) { + case 'authState': + if (typeof msg.data?.authenticated === 'boolean') { + this.authState = msg.data.authenticated; + } else { + this.authState = null; + } + break; + case 'agentConnected': + case 'loginSuccess': + this.authState = true; + break; + case 'agentConnectionError': + case 'loginError': + this.authState = false; + break; + default: + break; + } + } + + /** + * Sync important initialization state when the webview signals readiness. + */ + private handleWebviewReady(): void { + if (this.currentModeId) { + this.sendMessageToWebView({ + type: 'modeChanged', + data: { modeId: this.currentModeId }, + }); + } + + if (typeof this.authState === 'boolean') { + this.sendMessageToWebView({ + type: 'authState', + data: { authenticated: this.authState }, + }); + return; + } + + if (this.agentInitialized) { + const authenticated = Boolean(this.agentManager.currentSessionId); + this.sendMessageToWebView({ + type: 'authState', + data: { authenticated }, + }); + } + } + /** * Send message to WebView */ private sendMessageToWebView(message: unknown): void { + this.updateAuthStateFromMessage(message); const panel = this.panelManager.getPanel(); panel?.webview.postMessage(message); } @@ -983,6 +1050,7 @@ export class WebViewProvider { resetAgentState(): void { console.log('[WebViewProvider] Resetting agent state'); this.agentInitialized = false; + this.authState = null; // Disconnect existing connection this.agentManager.disconnect(); } @@ -1017,6 +1085,10 @@ export class WebViewProvider { if (message.type === 'openDiff' && this.isAutoMode()) { return; } + if (message.type === 'webviewReady') { + this.handleWebviewReady(); + return; + } if (message.type === 'updatePanelTitle') { const title = String( (message.data as { title?: unknown } | undefined)?.title ?? '', @@ -1174,6 +1246,7 @@ export class WebViewProvider { console.log('[WebViewProvider] Restoring state:', state); this.messageHandler.setCurrentConversationId(state.conversationId); this.agentInitialized = state.agentInitialized; + this.authState = null; console.log( '[WebViewProvider] State restored. agentInitialized:', this.agentInitialized, diff --git a/packages/vscode-ide-companion/src/webview/components/layout/InputForm.tsx b/packages/vscode-ide-companion/src/webview/components/layout/InputForm.tsx index 2761ace87..5722c66d2 100644 --- a/packages/vscode-ide-companion/src/webview/components/layout/InputForm.tsx +++ b/packages/vscode-ide-companion/src/webview/components/layout/InputForm.tsx @@ -7,7 +7,7 @@ * This allows local ApprovalModeValue to work with webui's EditModeInfo */ -import type React from 'react'; +import type { FC } from 'react'; import { InputForm as BaseInputForm, getEditModeIcon } from '@qwen-code/webui'; import type { InputFormProps as BaseInputFormProps, @@ -44,7 +44,7 @@ const getEditModeInfo = (editMode: ApprovalModeValue): EditModeInfo => { * This is an adapter that accepts the local ApprovalModeValue type * and converts it to webui's EditModeInfo format. */ -export const InputForm: React.FC = ({ editMode, ...rest }) => { +export const InputForm: FC = ({ editMode, ...rest }) => { const editModeInfo = getEditModeInfo(editMode); return ; diff --git a/packages/vscode-ide-companion/src/webview/components/layout/Onboarding.tsx b/packages/vscode-ide-companion/src/webview/components/layout/Onboarding.tsx index 79c8791f2..b67893097 100644 --- a/packages/vscode-ide-companion/src/webview/components/layout/Onboarding.tsx +++ b/packages/vscode-ide-companion/src/webview/components/layout/Onboarding.tsx @@ -7,7 +7,7 @@ * Uses webui Onboarding component with platform-specific icon URL */ -import type React from 'react'; +import type { FC } from 'react'; import { Onboarding as BaseOnboarding } from '@qwen-code/webui'; import { generateIconUrl } from '../../utils/resourceUrl.js'; @@ -19,7 +19,7 @@ interface OnboardingPageProps { * VSCode Onboarding wrapper * Provides platform-specific icon URL to the webui Onboarding component */ -export const Onboarding: React.FC = ({ onLogin }) => { +export const Onboarding: FC = ({ onLogin }) => { const iconUri = generateIconUrl('icon.png'); return ; diff --git a/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/ToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/ToolCall.tsx index 2890cb51b..ac1fbce11 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/ToolCall.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/ToolCall.tsx @@ -9,7 +9,7 @@ * It re-exports the router and types from the toolcalls module. */ -import type React from 'react'; +import type { FC } from 'react'; import type { ToolCallData } from '@qwen-code/webui'; import { ToolCallRouter } from './index.js'; @@ -20,7 +20,7 @@ export type { ToolCallContent, } from '@qwen-code/webui'; -export const ToolCall: React.FC<{ +export const ToolCall: FC<{ toolCall: ToolCallData; isFirst?: boolean; isLast?: boolean; diff --git a/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx index 24741a567..d20e07188 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx @@ -7,7 +7,7 @@ * All UI components are now imported from @qwen-code/webui */ -import type React from 'react'; +import type { FC } from 'react'; import { shouldShowToolCall, // All ToolCall components from webui @@ -25,9 +25,7 @@ import type { BaseToolCallProps } from '@qwen-code/webui'; /** * Factory function that returns the appropriate tool call component based on kind */ -export const getToolCallComponent = ( - kind: string, -): React.FC => { +export const getToolCallComponent = (kind: string): FC => { const normalizedKind = kind.toLowerCase(); // Route to specialized components diff --git a/packages/vscode-ide-companion/src/webview/context/VSCodePlatformProvider.tsx b/packages/vscode-ide-companion/src/webview/context/VSCodePlatformProvider.tsx index da128f381..241a5fada 100644 --- a/packages/vscode-ide-companion/src/webview/context/VSCodePlatformProvider.tsx +++ b/packages/vscode-ide-companion/src/webview/context/VSCodePlatformProvider.tsx @@ -7,8 +7,8 @@ * This allows webui components to work with VSCode's messaging system */ -import type React from 'react'; import { useMemo, useCallback, useEffect, useRef } from 'react'; +import type { FC, ReactNode } from 'react'; import { PlatformProvider } from '@qwen-code/webui'; import type { PlatformContextValue } from '@qwen-code/webui'; import { useVSCode } from '../hooks/useVSCode.js'; @@ -18,7 +18,7 @@ import { generateIconUrl } from '../utils/resourceUrl.js'; * Props for VSCodePlatformProvider */ interface VSCodePlatformProviderProps { - children: React.ReactNode; + children: ReactNode; } /** @@ -27,7 +27,7 @@ interface VSCodePlatformProviderProps { * This component bridges the VSCode API with the platform-agnostic webui components. * It wraps children with PlatformProvider and provides VSCode-specific implementations. */ -export const VSCodePlatformProvider: React.FC = ({ +export const VSCodePlatformProvider: FC = ({ children, }) => { const vscode = useVSCode(); diff --git a/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts b/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts index 9d84e46f7..ae46a9b4f 100644 --- a/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts +++ b/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts @@ -886,6 +886,8 @@ export const useWebViewMessages = ({ useEffect(() => { window.addEventListener('message', handleMessage); + // Notify extension that the webview is ready to receive initialization state. + vscode.postMessage({ type: 'webviewReady', data: {} }); return () => window.removeEventListener('message', handleMessage); - }, [handleMessage]); + }, [handleMessage, vscode]); }; diff --git a/packages/vscode-ide-companion/tailwind.config.js b/packages/vscode-ide-companion/tailwind.config.js index b286d45d0..f220c40ac 100644 --- a/packages/vscode-ide-companion/tailwind.config.js +++ b/packages/vscode-ide-companion/tailwind.config.js @@ -15,7 +15,9 @@ export default { content: [ './src/webview/**/**/*.{js,jsx,ts,tsx}', // Include webui components to prevent Tailwind JIT from tree-shaking their classes - './node_modules/@qwen-code/webui/dist/**/*.js', + // Use relative path for pnpm workspace - node_modules symlinks are in root + '../webui/src/**/*.{js,jsx,ts,tsx}', + '../webui/dist/**/*.js', ], theme: { extend: { diff --git a/packages/webui/package.json b/packages/webui/package.json index e293398c1..06fda4881 100644 --- a/packages/webui/package.json +++ b/packages/webui/package.json @@ -18,7 +18,8 @@ "require": "./dist/components/icons/index.cjs" }, "./tailwind.preset": "./tailwind.preset.cjs", - "./styles.css": "./dist/styles.css" + "./styles.css": "./dist/styles.css", + "./webview.css": "./dist/webview.css" }, "files": [ "dist", diff --git a/packages/webui/src/components/PermissionDrawer.tsx b/packages/webui/src/components/PermissionDrawer.tsx index 0461dfa52..450522cbd 100644 --- a/packages/webui/src/components/PermissionDrawer.tsx +++ b/packages/webui/src/components/PermissionDrawer.tsx @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; import { useEffect, useRef, useState } from 'react'; +import type { FC, RefObject } from 'react'; export interface PermissionOption { name: string; @@ -41,7 +41,7 @@ export interface PermissionDrawerProps { onClose?: () => void; } -const PermissionDrawer: React.FC = ({ +const PermissionDrawer: FC = ({ isOpen, options, toolCall, @@ -290,10 +290,10 @@ interface CustomMessageInputRowProps { setCustomMessage: (val: string) => void; onFocusRow: () => void; onSubmitReject: () => void; - inputRef: React.RefObject; + inputRef: RefObject; } -const CustomMessageInputRow: React.FC = ({ +const CustomMessageInputRow: FC = ({ isFocused, customMessage, setCustomMessage, @@ -309,7 +309,7 @@ const CustomMessageInputRow: React.FC = ({ onClick={() => inputRef.current?.focus()} > | undefined} + ref={inputRef as unknown as RefObject} type="text" placeholder="Tell Qwen what to do instead" spellCheck={false} diff --git a/packages/webui/src/components/WebviewContainer.tsx b/packages/webui/src/components/WebviewContainer.tsx new file mode 100644 index 000000000..b8317fdc1 --- /dev/null +++ b/packages/webui/src/components/WebviewContainer.tsx @@ -0,0 +1,17 @@ +import type { PropsWithChildren } from 'react'; +import type React from 'react'; + +interface WebviewContainerProps extends PropsWithChildren { + className?: string; +} + +/** + * A container component that provides style isolation for VSCode webviews + * This component wraps content in a namespace to prevent style conflicts + */ +const WebviewContainer: React.FC = ({ + children, + className = '', +}) =>
{children}
; + +export default WebviewContainer; diff --git a/packages/webui/src/components/icons/CloseIcon.tsx b/packages/webui/src/components/icons/CloseIcon.tsx index 65d60b68a..d2324ad89 100644 --- a/packages/webui/src/components/icons/CloseIcon.tsx +++ b/packages/webui/src/components/icons/CloseIcon.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; interface CloseIconProps { size?: number; @@ -12,7 +12,7 @@ interface CloseIconProps { className?: string; } -const CloseIcon: React.FC = ({ +const CloseIcon: FC = ({ size = 24, color = 'currentColor', className = '', diff --git a/packages/webui/src/components/icons/EditIcons.tsx b/packages/webui/src/components/icons/EditIcons.tsx index ddec39d4a..511ba9bb7 100644 --- a/packages/webui/src/components/icons/EditIcons.tsx +++ b/packages/webui/src/components/icons/EditIcons.tsx @@ -6,14 +6,14 @@ * Edit mode related icons */ -import type React from 'react'; +import type { FC } from 'react'; import type { IconProps } from './types.js'; /** * Edit pencil icon (16x16) * Used for "Ask before edits" mode */ -export const EditPencilIcon: React.FC = ({ +export const EditPencilIcon: FC = ({ size = 16, className, ...props @@ -40,7 +40,7 @@ export const EditPencilIcon: React.FC = ({ * Auto/fast-forward icon (16x16) * Used for "Edit automatically" mode */ -export const AutoEditIcon: React.FC = ({ +export const AutoEditIcon: FC = ({ size = 16, className, ...props @@ -63,7 +63,7 @@ export const AutoEditIcon: React.FC = ({ * Plan mode/bars icon (16x16) * Used for "Plan mode" */ -export const PlanModeIcon: React.FC = ({ +export const PlanModeIcon: FC = ({ size = 16, className, ...props @@ -86,7 +86,7 @@ export const PlanModeIcon: React.FC = ({ * Code brackets icon (20x20) * Used for active file indicator */ -export const CodeBracketsIcon: React.FC = ({ +export const CodeBracketsIcon: FC = ({ size = 20, className, ...props @@ -113,7 +113,7 @@ export const CodeBracketsIcon: React.FC = ({ * Hide context (eye slash) icon (20x20) * Used to indicate the active selection will NOT be auto-loaded into context */ -export const HideContextIcon: React.FC = ({ +export const HideContextIcon: FC = ({ size = 20, className, ...props @@ -141,7 +141,7 @@ export const HideContextIcon: React.FC = ({ * Slash command icon (20x20) * Used for command menu button */ -export const SlashCommandIcon: React.FC = ({ +export const SlashCommandIcon: FC = ({ size = 20, className, ...props @@ -168,11 +168,7 @@ export const SlashCommandIcon: React.FC = ({ * Link/attachment icon (20x20) * Used for attach context button */ -export const LinkIcon: React.FC = ({ - size = 20, - className, - ...props -}) => ( +export const LinkIcon: FC = ({ size = 20, className, ...props }) => ( = ({ * Open diff icon (16x16) * Used for opening diff in VS Code */ -export const OpenDiffIcon: React.FC = ({ +export const OpenDiffIcon: FC = ({ size = 16, className, ...props @@ -218,11 +214,7 @@ export const OpenDiffIcon: React.FC = ({ * Undo edit icon (16x16) * Used for undoing edits in diff views */ -export const UndoIcon: React.FC = ({ - size = 16, - className, - ...props -}) => ( +export const UndoIcon: FC = ({ size = 16, className, ...props }) => ( = ({ * Redo edit icon (16x16) * Used for redoing edits in diff views */ -export const RedoIcon: React.FC = ({ - size = 16, - className, - ...props -}) => ( +export const RedoIcon: FC = ({ size = 16, className, ...props }) => ( = ({ * Replace all icon (16x16) * Used for replacing all occurrences in search/replace */ -export const ReplaceAllIcon: React.FC = ({ +export const ReplaceAllIcon: FC = ({ size = 16, className, ...props @@ -305,11 +293,7 @@ export const ReplaceAllIcon: React.FC = ({ * Copy icon (16x16) * Used for copying content */ -export const CopyIcon: React.FC = ({ - size = 16, - className, - ...props -}) => ( +export const CopyIcon: FC = ({ size = 16, className, ...props }) => ( = ({ * Paste icon (16x16) * Used for pasting content */ -export const PasteIcon: React.FC = ({ +export const PasteIcon: FC = ({ size = 16, className, ...props @@ -371,7 +355,7 @@ export const PasteIcon: React.FC = ({ * Select all icon (16x16) * Used for selecting all content */ -export const SelectAllIcon: React.FC = ({ +export const SelectAllIcon: FC = ({ size = 16, className, ...props diff --git a/packages/webui/src/components/icons/FileIcons.tsx b/packages/webui/src/components/icons/FileIcons.tsx index 38bf27f7a..bf00697a2 100644 --- a/packages/webui/src/components/icons/FileIcons.tsx +++ b/packages/webui/src/components/icons/FileIcons.tsx @@ -6,18 +6,14 @@ * File and document related icons */ -import type React from 'react'; +import type { FC } from 'react'; import type { IconProps } from './types.js'; /** * File document icon (16x16) * Used for file completion menu */ -export const FileIcon: React.FC = ({ - size = 16, - className, - ...props -}) => ( +export const FileIcon: FC = ({ size = 16, className, ...props }) => ( = ({ ); -export const FileListIcon: React.FC = ({ +export const FileListIcon: FC = ({ size = 16, className, ...props @@ -55,7 +51,7 @@ export const FileListIcon: React.FC = ({ * Save document icon (16x16) * Used for save session button */ -export const SaveDocumentIcon: React.FC = ({ +export const SaveDocumentIcon: FC = ({ size = 16, className, ...props @@ -83,7 +79,7 @@ export const SaveDocumentIcon: React.FC = ({ * Folder icon (16x16) * Useful for directory entries in completion lists */ -export const FolderIcon: React.FC = ({ +export const FolderIcon: FC = ({ size = 16, className, ...props diff --git a/packages/webui/src/components/icons/Icon.tsx b/packages/webui/src/components/icons/Icon.tsx index afa888cbb..d4ce3a8c4 100644 --- a/packages/webui/src/components/icons/Icon.tsx +++ b/packages/webui/src/components/icons/Icon.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; interface IconProps { name: string; @@ -13,7 +13,7 @@ interface IconProps { className?: string; } -const Icon: React.FC = ({ +const Icon: FC = ({ name, size = 24, color = 'currentColor', diff --git a/packages/webui/src/components/icons/NavigationIcons.tsx b/packages/webui/src/components/icons/NavigationIcons.tsx index 9a4e52fbd..541eb1ba5 100644 --- a/packages/webui/src/components/icons/NavigationIcons.tsx +++ b/packages/webui/src/components/icons/NavigationIcons.tsx @@ -6,14 +6,14 @@ * Navigation and action icons */ -import type React from 'react'; +import type { FC } from 'react'; import type { IconProps } from './types.js'; /** * Chevron down icon (20x20) * Used for dropdown arrows */ -export const ChevronDownIcon: React.FC = ({ +export const ChevronDownIcon: FC = ({ size = 20, className, ...props @@ -40,11 +40,7 @@ export const ChevronDownIcon: React.FC = ({ * Plus icon (20x20) * Used for new session button */ -export const PlusIcon: React.FC = ({ - size = 20, - className, - ...props -}) => ( +export const PlusIcon: FC = ({ size = 20, className, ...props }) => ( = ({ * Small plus icon (16x16) * Used for default attachment type */ -export const PlusSmallIcon: React.FC = ({ +export const PlusSmallIcon: FC = ({ size = 16, className, ...props @@ -86,7 +82,7 @@ export const PlusSmallIcon: React.FC = ({ * Arrow up icon (20x20) * Used for send message button */ -export const ArrowUpIcon: React.FC = ({ +export const ArrowUpIcon: FC = ({ size = 20, className, ...props @@ -113,7 +109,7 @@ export const ArrowUpIcon: React.FC = ({ * Close X icon (14x14) * Used for close buttons in banners and dialogs */ -export const CloseIcon: React.FC = ({ +export const CloseIcon: FC = ({ size = 14, className, ...props @@ -137,7 +133,7 @@ export const CloseIcon: React.FC = ({ ); -export const CloseSmallIcon: React.FC = ({ +export const CloseSmallIcon: FC = ({ size = 16, className, ...props @@ -160,7 +156,7 @@ export const CloseSmallIcon: React.FC = ({ * Search/magnifying glass icon (20x20) * Used for search input */ -export const SearchIcon: React.FC = ({ +export const SearchIcon: FC = ({ size = 20, className, ...props @@ -187,7 +183,7 @@ export const SearchIcon: React.FC = ({ * Refresh/reload icon (16x16) * Used for refresh session list */ -export const RefreshIcon: React.FC = ({ +export const RefreshIcon: FC = ({ size = 16, className, ...props diff --git a/packages/webui/src/components/icons/SendIcon.tsx b/packages/webui/src/components/icons/SendIcon.tsx index f0c657ecc..fa8a2a059 100644 --- a/packages/webui/src/components/icons/SendIcon.tsx +++ b/packages/webui/src/components/icons/SendIcon.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; interface SendIconProps { size?: number; @@ -12,7 +12,7 @@ interface SendIconProps { className?: string; } -const SendIcon: React.FC = ({ +const SendIcon: FC = ({ size = 24, color = 'currentColor', className = '', diff --git a/packages/webui/src/components/icons/SpecialIcons.tsx b/packages/webui/src/components/icons/SpecialIcons.tsx index 48c5db846..492ba926b 100644 --- a/packages/webui/src/components/icons/SpecialIcons.tsx +++ b/packages/webui/src/components/icons/SpecialIcons.tsx @@ -6,7 +6,7 @@ * Special UI icons */ -import type React from 'react'; +import type { FC } from 'react'; import type { IconProps } from './types.js'; interface ThinkingIconProps extends IconProps { @@ -16,7 +16,7 @@ interface ThinkingIconProps extends IconProps { enabled?: boolean; } -export const ThinkingIcon: React.FC = ({ +export const ThinkingIcon: FC = ({ size = 16, className, enabled = false, @@ -49,7 +49,7 @@ export const ThinkingIcon: React.FC = ({ ); -export const TerminalIcon: React.FC = ({ +export const TerminalIcon: FC = ({ size = 20, className, ...props diff --git a/packages/webui/src/components/icons/StatusIcons.tsx b/packages/webui/src/components/icons/StatusIcons.tsx index fdaa29434..231a62ed2 100644 --- a/packages/webui/src/components/icons/StatusIcons.tsx +++ b/packages/webui/src/components/icons/StatusIcons.tsx @@ -6,14 +6,14 @@ * Status and state related icons */ -import type React from 'react'; +import type { FC } from 'react'; import type { IconProps } from './types.js'; /** * Plan completed icon (14x14) * Used for completed plan items */ -export const PlanCompletedIcon: React.FC = ({ +export const PlanCompletedIcon: FC = ({ size = 14, className, ...props @@ -43,7 +43,7 @@ export const PlanCompletedIcon: React.FC = ({ * Plan in progress icon (14x14) * Used for in-progress plan items */ -export const PlanInProgressIcon: React.FC = ({ +export const PlanInProgressIcon: FC = ({ size = 14, className, ...props @@ -73,7 +73,7 @@ export const PlanInProgressIcon: React.FC = ({ * Plan pending icon (14x14) * Used for pending plan items */ -export const PlanPendingIcon: React.FC = ({ +export const PlanPendingIcon: FC = ({ size = 14, className, ...props @@ -103,7 +103,7 @@ export const PlanPendingIcon: React.FC = ({ * Warning triangle icon (20x20) * Used for warning messages */ -export const WarningTriangleIcon: React.FC = ({ +export const WarningTriangleIcon: FC = ({ size = 20, className, ...props @@ -130,11 +130,7 @@ export const WarningTriangleIcon: React.FC = ({ * User profile icon (16x16) * Used for login command */ -export const UserIcon: React.FC = ({ - size = 16, - className, - ...props -}) => ( +export const UserIcon: FC = ({ size = 16, className, ...props }) => ( = ({ ); -export const SymbolIcon: React.FC = ({ +export const SymbolIcon: FC = ({ size = 16, className, ...props @@ -168,7 +164,7 @@ export const SymbolIcon: React.FC = ({ ); -export const SelectionIcon: React.FC = ({ +export const SelectionIcon: FC = ({ size = 16, className, ...props diff --git a/packages/webui/src/components/icons/StopIcon.tsx b/packages/webui/src/components/icons/StopIcon.tsx index 40c232502..759349224 100644 --- a/packages/webui/src/components/icons/StopIcon.tsx +++ b/packages/webui/src/components/icons/StopIcon.tsx @@ -6,18 +6,14 @@ * Stop icon for canceling operations */ -import type React from 'react'; +import type { FC } from 'react'; import type { IconProps } from './types.js'; /** * Stop/square icon (16x16) * Used for stop/cancel operations */ -export const StopIcon: React.FC = ({ - size = 16, - className, - ...props -}) => ( +export const StopIcon: FC = ({ size = 16, className, ...props }) => ( { +export interface IconProps extends SVGProps { /** * Icon size (width and height) * @default 16 diff --git a/packages/webui/src/components/layout/ChatHeader.tsx b/packages/webui/src/components/layout/ChatHeader.tsx index 4ece03d78..bd4855596 100644 --- a/packages/webui/src/components/layout/ChatHeader.tsx +++ b/packages/webui/src/components/layout/ChatHeader.tsx @@ -7,7 +7,7 @@ * Displays current session title with navigation controls */ -import type React from 'react'; +import type { FC } from 'react'; import { ChevronDownIcon } from '../icons/NavigationIcons.js'; import { PlusIcon } from '../icons/NavigationIcons.js'; @@ -40,7 +40,7 @@ export interface ChatHeaderProps { * /> * ``` */ -export const ChatHeader: React.FC = ({ +export const ChatHeader: FC = ({ currentSessionTitle, onLoadSessions, onNewSession, diff --git a/packages/webui/src/components/layout/CompletionMenu.tsx b/packages/webui/src/components/layout/CompletionMenu.tsx index 698c623ca..809e24b73 100644 --- a/packages/webui/src/components/layout/CompletionMenu.tsx +++ b/packages/webui/src/components/layout/CompletionMenu.tsx @@ -7,7 +7,7 @@ * Supports keyboard navigation and mouse interaction */ -import type React from 'react'; +import type { FC } from 'react'; import { useEffect, useRef, useState } from 'react'; import type { CompletionItem } from '../../types/completion.js'; @@ -49,7 +49,7 @@ export interface CompletionMenuProps { * /> * ``` */ -export const CompletionMenu: React.FC = ({ +export const CompletionMenu: FC = ({ items, onSelect, onClose, diff --git a/packages/webui/src/components/layout/Container.tsx b/packages/webui/src/components/layout/Container.tsx index 37dc27bca..c86de299e 100644 --- a/packages/webui/src/components/layout/Container.tsx +++ b/packages/webui/src/components/layout/Container.tsx @@ -4,14 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; interface ContainerProps { children: React.ReactNode; className?: string; } -const Container: React.FC = ({ children, className = '' }) => ( +const Container: FC = ({ children, className = '' }) => (
{children}
); diff --git a/packages/webui/src/components/layout/ContextIndicator.tsx b/packages/webui/src/components/layout/ContextIndicator.tsx index 4af745abb..ceac64d58 100644 --- a/packages/webui/src/components/layout/ContextIndicator.tsx +++ b/packages/webui/src/components/layout/ContextIndicator.tsx @@ -7,7 +7,7 @@ * Displays token usage information with tooltip */ -import type React from 'react'; +import type { FC } from 'react'; import { Tooltip } from '../ui/Tooltip.js'; /** @@ -61,7 +61,7 @@ const formatNumber = (value: number): string => { * /> * ``` */ -export const ContextIndicator: React.FC = ({ +export const ContextIndicator: FC = ({ contextUsage, }) => { if (!contextUsage) { diff --git a/packages/webui/src/components/layout/EmptyState.tsx b/packages/webui/src/components/layout/EmptyState.tsx index 4c84bcdc9..de0c11f39 100644 --- a/packages/webui/src/components/layout/EmptyState.tsx +++ b/packages/webui/src/components/layout/EmptyState.tsx @@ -7,7 +7,7 @@ * Shows logo and welcome message based on authentication state */ -import type React from 'react'; +import type { FC } from 'react'; import { usePlatform } from '../../context/PlatformContext.js'; /** @@ -41,7 +41,7 @@ export interface EmptyStateProps { * /> * ``` */ -export const EmptyState: React.FC = ({ +export const EmptyState: FC = ({ isAuthenticated = false, loadingMessage, logoUrl, diff --git a/packages/webui/src/components/layout/FileLink.tsx b/packages/webui/src/components/layout/FileLink.tsx index 9c8945dd5..07cd9c593 100644 --- a/packages/webui/src/components/layout/FileLink.tsx +++ b/packages/webui/src/components/layout/FileLink.tsx @@ -8,7 +8,7 @@ * Supports clicking to open files and jump to specified line and column numbers */ -import type React from 'react'; +import type { FC } from 'react'; import { usePlatform } from '../../context/PlatformContext.js'; /** @@ -77,7 +77,7 @@ function buildFullPath( * * ``` */ -export const FileLink: React.FC = ({ +export const FileLink: FC = ({ path, line, column, diff --git a/packages/webui/src/components/layout/Footer.tsx b/packages/webui/src/components/layout/Footer.tsx index 6a4f162f5..e12c7f1fd 100644 --- a/packages/webui/src/components/layout/Footer.tsx +++ b/packages/webui/src/components/layout/Footer.tsx @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; -const Footer: React.FC = () =>
Footer Component Placeholder
; +const Footer: FC = () =>
Footer Component Placeholder
; export default Footer; diff --git a/packages/webui/src/components/layout/Header.tsx b/packages/webui/src/components/layout/Header.tsx index 1f7fe373c..28861f57d 100644 --- a/packages/webui/src/components/layout/Header.tsx +++ b/packages/webui/src/components/layout/Header.tsx @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; -const Header: React.FC = () =>
Header Component Placeholder
; +const Header: FC = () =>
Header Component Placeholder
; export default Header; diff --git a/packages/webui/src/components/layout/InputForm.stories.tsx b/packages/webui/src/components/layout/InputForm.stories.tsx index e937e988b..db5796343 100644 --- a/packages/webui/src/components/layout/InputForm.stories.tsx +++ b/packages/webui/src/components/layout/InputForm.stories.tsx @@ -5,7 +5,7 @@ */ import type { Meta, StoryObj, StoryFn, Decorator } from '@storybook/react-vite'; -import type React from 'react'; +import type { FC } from 'react'; import { useRef } from 'react'; import { InputForm, getEditModeIcon } from './InputForm.js'; import type { InputFormProps } from './InputForm.js'; @@ -15,7 +15,7 @@ type InputFormStoryProps = Omit; /** * Wrapper component to provide inputFieldRef */ -const InputFormWrapper: React.FC = (props) => { +const InputFormWrapper: FC = (props) => { const inputFieldRef = useRef(null); return ; }; diff --git a/packages/webui/src/components/layout/InputForm.tsx b/packages/webui/src/components/layout/InputForm.tsx index eb7ab2f11..e77f57e24 100644 --- a/packages/webui/src/components/layout/InputForm.tsx +++ b/packages/webui/src/components/layout/InputForm.tsx @@ -7,7 +7,7 @@ * Platform-agnostic version with configurable edit modes */ -import type React from 'react'; +import type { FC } from 'react'; import type { ReactNode } from 'react'; import { EditPencilIcon, @@ -144,7 +144,7 @@ export interface InputFormProps { * /> * ``` */ -export const InputForm: React.FC = ({ +export const InputForm: FC = ({ inputText, inputFieldRef, isStreaming, diff --git a/packages/webui/src/components/layout/Main.tsx b/packages/webui/src/components/layout/Main.tsx index b046a1827..a39181aaf 100644 --- a/packages/webui/src/components/layout/Main.tsx +++ b/packages/webui/src/components/layout/Main.tsx @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; -const Main: React.FC = () =>
Main Component Placeholder
; +const Main: FC = () =>
Main Component Placeholder
; export default Main; diff --git a/packages/webui/src/components/layout/Onboarding.tsx b/packages/webui/src/components/layout/Onboarding.tsx index bdf1a2652..9a1deed59 100644 --- a/packages/webui/src/components/layout/Onboarding.tsx +++ b/packages/webui/src/components/layout/Onboarding.tsx @@ -7,7 +7,7 @@ * Platform-specific logic (icon URL) passed via props */ -import type React from 'react'; +import type { FC } from 'react'; export interface OnboardingProps { /** URL of the application icon */ @@ -26,7 +26,7 @@ export interface OnboardingProps { * Onboarding - Welcome screen for new users * Pure presentational component */ -export const Onboarding: React.FC = ({ +export const Onboarding: FC = ({ iconUrl, onGetStarted, appName = 'Qwen Code', diff --git a/packages/webui/src/components/layout/SessionSelector.tsx b/packages/webui/src/components/layout/SessionSelector.tsx index 6e185af81..7012770ff 100644 --- a/packages/webui/src/components/layout/SessionSelector.tsx +++ b/packages/webui/src/components/layout/SessionSelector.tsx @@ -7,7 +7,7 @@ * Displays sessions grouped by date with search and infinite scroll */ -import type React from 'react'; +import type { FC } from 'react'; import { Fragment } from 'react'; import { getTimeAgo, @@ -64,7 +64,7 @@ export interface SessionSelectorProps { * /> * ``` */ -export const SessionSelector: React.FC = ({ +export const SessionSelector: FC = ({ visible, sessions, currentSessionId, diff --git a/packages/webui/src/components/layout/Sidebar.tsx b/packages/webui/src/components/layout/Sidebar.tsx index d8835427a..606c2da02 100644 --- a/packages/webui/src/components/layout/Sidebar.tsx +++ b/packages/webui/src/components/layout/Sidebar.tsx @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; -const Sidebar: React.FC = () => ; +const Sidebar: FC = () => ; export default Sidebar; diff --git a/packages/webui/src/components/messages/Assistant/AssistantMessage.tsx b/packages/webui/src/components/messages/Assistant/AssistantMessage.tsx index e0c41970f..73ed296c4 100644 --- a/packages/webui/src/components/messages/Assistant/AssistantMessage.tsx +++ b/packages/webui/src/components/messages/Assistant/AssistantMessage.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; import { MessageContent } from '../MessageContent.js'; import './AssistantMessage.css'; @@ -28,7 +28,7 @@ export interface AssistantMessageProps { * AssistantMessage component - renders AI responses with styling * Supports different states: default, success, error, warning, loading */ -export const AssistantMessage: React.FC = ({ +export const AssistantMessage: FC = ({ content, timestamp: _timestamp, onFileClick, diff --git a/packages/webui/src/components/messages/MarkdownRenderer/MarkdownRenderer.tsx b/packages/webui/src/components/messages/MarkdownRenderer/MarkdownRenderer.tsx index 7413c4041..3d52a342b 100644 --- a/packages/webui/src/components/messages/MarkdownRenderer/MarkdownRenderer.tsx +++ b/packages/webui/src/components/messages/MarkdownRenderer/MarkdownRenderer.tsx @@ -6,7 +6,7 @@ * MarkdownRenderer component - renders markdown content with syntax highlighting and clickable file paths */ -import type React from 'react'; +import type { FC } from 'react'; import { useMemo, useCallback } from 'react'; import MarkdownIt from 'markdown-it'; import type { Options as MarkdownItOptions } from 'markdown-it'; @@ -59,7 +59,7 @@ const createMarkdownInstance = (): MarkdownIt => /** * MarkdownRenderer component - renders markdown content with enhanced features */ -export const MarkdownRenderer: React.FC = ({ +export const MarkdownRenderer: FC = ({ content, onFileClick, enableFileLinks = true, diff --git a/packages/webui/src/components/messages/Message.tsx b/packages/webui/src/components/messages/Message.tsx index cce7b53ce..876307242 100644 --- a/packages/webui/src/components/messages/Message.tsx +++ b/packages/webui/src/components/messages/Message.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; interface MessageProps { id: string; @@ -14,7 +14,7 @@ interface MessageProps { className?: string; } -const Message: React.FC = ({ +const Message: FC = ({ content, sender, timestamp, diff --git a/packages/webui/src/components/messages/MessageContent.tsx b/packages/webui/src/components/messages/MessageContent.tsx index bed103b48..cdc3bf34e 100644 --- a/packages/webui/src/components/messages/MessageContent.tsx +++ b/packages/webui/src/components/messages/MessageContent.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; import { memo } from 'react'; import { MarkdownRenderer } from './MarkdownRenderer/MarkdownRenderer.js'; @@ -14,7 +14,7 @@ export interface MessageContentProps { enableFileLinks?: boolean; } -const MessageContentBase: React.FC = ({ +const MessageContentBase: FC = ({ content, onFileClick, enableFileLinks, diff --git a/packages/webui/src/components/messages/MessageInput.tsx b/packages/webui/src/components/messages/MessageInput.tsx index 281fd5b4e..878c1a70f 100644 --- a/packages/webui/src/components/messages/MessageInput.tsx +++ b/packages/webui/src/components/messages/MessageInput.tsx @@ -4,10 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; -const MessageInput: React.FC = () => ( -
MessageInput Component Placeholder
-); +const MessageInput: FC = () =>
MessageInput Component Placeholder
; export default MessageInput; diff --git a/packages/webui/src/components/messages/MessageList.tsx b/packages/webui/src/components/messages/MessageList.tsx index 5a322929c..218c843bc 100644 --- a/packages/webui/src/components/messages/MessageList.tsx +++ b/packages/webui/src/components/messages/MessageList.tsx @@ -4,10 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; -const MessageList: React.FC = () => ( -
MessageList Component Placeholder
-); +const MessageList: FC = () =>
MessageList Component Placeholder
; export default MessageList; diff --git a/packages/webui/src/components/messages/ThinkingMessage.tsx b/packages/webui/src/components/messages/ThinkingMessage.tsx index db91854f8..8e2c5e8b1 100644 --- a/packages/webui/src/components/messages/ThinkingMessage.tsx +++ b/packages/webui/src/components/messages/ThinkingMessage.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; import { MessageContent } from './MessageContent.js'; export interface ThinkingMessageProps { @@ -13,7 +13,7 @@ export interface ThinkingMessageProps { onFileClick?: (path: string) => void; } -export const ThinkingMessage: React.FC = ({ +export const ThinkingMessage: FC = ({ content, timestamp: _timestamp, onFileClick, diff --git a/packages/webui/src/components/messages/UserMessage.tsx b/packages/webui/src/components/messages/UserMessage.tsx index 02b31739b..8f772167e 100644 --- a/packages/webui/src/components/messages/UserMessage.tsx +++ b/packages/webui/src/components/messages/UserMessage.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; import { MessageContent } from './MessageContent.js'; export interface FileContext { @@ -21,7 +21,7 @@ export interface UserMessageProps { fileContext?: FileContext; } -export const UserMessage: React.FC = ({ +export const UserMessage: FC = ({ content, timestamp: _timestamp, onFileClick, diff --git a/packages/webui/src/components/messages/Waiting/InterruptedMessage.tsx b/packages/webui/src/components/messages/Waiting/InterruptedMessage.tsx index 0c0e4c8d3..f077ff0bd 100644 --- a/packages/webui/src/components/messages/Waiting/InterruptedMessage.tsx +++ b/packages/webui/src/components/messages/Waiting/InterruptedMessage.tsx @@ -4,14 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; interface InterruptedMessageProps { text?: string; } // A lightweight status line similar to WaitingMessage but without the left status icon. -export const InterruptedMessage: React.FC = ({ +export const InterruptedMessage: FC = ({ text = 'Interrupted', }) => (
diff --git a/packages/webui/src/components/messages/Waiting/WaitingMessage.tsx b/packages/webui/src/components/messages/Waiting/WaitingMessage.tsx index 95b728bf0..61d3448db 100644 --- a/packages/webui/src/components/messages/Waiting/WaitingMessage.tsx +++ b/packages/webui/src/components/messages/Waiting/WaitingMessage.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; import { useEffect, useMemo, useState } from 'react'; interface WaitingMessageProps { @@ -24,9 +24,7 @@ const DEFAULT_LOADING_PHRASES = [ 'Almost there...', ]; -export const WaitingMessage: React.FC = ({ - loadingMessage, -}) => { +export const WaitingMessage: FC = ({ loadingMessage }) => { // Build a phrase list that starts with the provided message (if any), then witty fallbacks const phrases = useMemo(() => { const set = new Set(); diff --git a/packages/webui/src/components/toolcalls/CheckboxDisplay.tsx b/packages/webui/src/components/toolcalls/CheckboxDisplay.tsx index 555c33c32..e08a02e6a 100644 --- a/packages/webui/src/components/toolcalls/CheckboxDisplay.tsx +++ b/packages/webui/src/components/toolcalls/CheckboxDisplay.tsx @@ -6,7 +6,7 @@ * Display-only checkbox component for plan entries */ -import type React from 'react'; +import type { FC } from 'react'; export interface CheckboxDisplayProps { checked?: boolean; @@ -23,7 +23,7 @@ export interface CheckboxDisplayProps { * - Supports indeterminate (middle) state using a data- attribute. * - Intended for read-only display (disabled by default). */ -export const CheckboxDisplay: React.FC = ({ +export const CheckboxDisplay: FC = ({ checked = false, indeterminate = false, disabled = true, diff --git a/packages/webui/src/components/toolcalls/GenericToolCall.tsx b/packages/webui/src/components/toolcalls/GenericToolCall.tsx index 1c003f1e4..0f62c0a0e 100644 --- a/packages/webui/src/components/toolcalls/GenericToolCall.tsx +++ b/packages/webui/src/components/toolcalls/GenericToolCall.tsx @@ -6,7 +6,7 @@ * Generic tool call component - handles all tool call types as fallback */ -import type React from 'react'; +import type { FC } from 'react'; import { ToolCallContainer, ToolCallCard, @@ -22,7 +22,7 @@ import type { BaseToolCallProps } from './shared/index.js'; * Used as fallback for unknown tool call kinds * Minimal display: show description and outcome */ -export const GenericToolCall: React.FC = ({ toolCall }) => { +export const GenericToolCall: FC = ({ toolCall }) => { const { kind, title, content, locations, toolCallId } = toolCall; const operationText = safeTitle(title); diff --git a/packages/webui/src/components/toolcalls/ReadToolCall.tsx b/packages/webui/src/components/toolcalls/ReadToolCall.tsx index 874dca50c..a397a12ee 100644 --- a/packages/webui/src/components/toolcalls/ReadToolCall.tsx +++ b/packages/webui/src/components/toolcalls/ReadToolCall.tsx @@ -7,7 +7,7 @@ * Pure UI component - platform interactions via usePlatform hook */ -import type React from 'react'; +import type { FC } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react'; import { FileLink } from '../layout/FileLink.js'; import { @@ -23,7 +23,7 @@ import type { /** * Simple container for Read tool calls */ -const ReadToolCallContainer: React.FC = ({ +const ReadToolCallContainer: FC = ({ label, status = 'success', children, @@ -56,7 +56,7 @@ const ReadToolCallContainer: React.FC = ({ * ReadToolCall - displays file reading operations * Shows: Read filename (no content preview) */ -export const ReadToolCall: React.FC = ({ toolCall }) => { +export const ReadToolCall: FC = ({ toolCall }) => { const { kind, content, locations, toolCallId } = toolCall; const platform = usePlatform(); const openedDiffsRef = useRef>(new Map()); diff --git a/packages/webui/src/components/toolcalls/SearchToolCall.tsx b/packages/webui/src/components/toolcalls/SearchToolCall.tsx index f34d355ee..c1fdb4b15 100644 --- a/packages/webui/src/components/toolcalls/SearchToolCall.tsx +++ b/packages/webui/src/components/toolcalls/SearchToolCall.tsx @@ -6,7 +6,7 @@ * Search tool call component - specialized for search operations */ -import type React from 'react'; +import type { FC } from 'react'; import { safeTitle, groupContent, @@ -18,7 +18,7 @@ import { FileLink } from '../layout/FileLink.js'; /** * Inline container for compact search results display */ -const InlineContainer: React.FC<{ +const InlineContainer: FC<{ status: 'success' | 'error' | 'warning' | 'loading' | 'default'; labelSuffix?: string; children?: React.ReactNode; @@ -68,7 +68,7 @@ const InlineContainer: React.FC<{ /** * Card layout for multi-result or error display */ -const SearchCard: React.FC<{ +const SearchCard: FC<{ status: 'success' | 'error' | 'warning' | 'loading' | 'default'; children: React.ReactNode; isFirst?: boolean; @@ -109,7 +109,7 @@ const SearchCard: React.FC<{ /** * Row component for search card layout */ -const SearchRow: React.FC<{ label: string; children: React.ReactNode }> = ({ +const SearchRow: FC<{ label: string; children: React.ReactNode }> = ({ label, children, }) => ( @@ -126,7 +126,7 @@ const SearchRow: React.FC<{ label: string; children: React.ReactNode }> = ({ /** * Local locations list component */ -const LocationsListLocal: React.FC<{ +const LocationsListLocal: FC<{ locations: Array<{ path: string; line?: number | null }>; }> = ({ locations }) => (
@@ -156,7 +156,7 @@ const getDisplayLabel = (kind: string): string => { * Specialized component for Search tool calls * Optimized for displaying search operations and results */ -export const SearchToolCall: React.FC = ({ +export const SearchToolCall: FC = ({ toolCall, isFirst, isLast, diff --git a/packages/webui/src/components/toolcalls/ShellToolCall.tsx b/packages/webui/src/components/toolcalls/ShellToolCall.tsx index 2ac4030cf..79e71b6bc 100644 --- a/packages/webui/src/components/toolcalls/ShellToolCall.tsx +++ b/packages/webui/src/components/toolcalls/ShellToolCall.tsx @@ -7,7 +7,7 @@ * Pure UI component - platform interactions via usePlatform hook */ -import type React from 'react'; +import type { FC } from 'react'; import { ToolCallContainer, CopyButton, @@ -27,7 +27,7 @@ type ShellVariant = 'execute' | 'bash'; /** * Custom container for Execute variant with different styling */ -const ExecuteToolCallContainer: React.FC = ({ +const ExecuteToolCallContainer: FC = ({ label, status = 'success', children, @@ -92,9 +92,10 @@ const getInputCommand = ( /** * Shell tool call implementation */ -const ShellToolCallImpl: React.FC< - BaseToolCallProps & { variant: ShellVariant } -> = ({ toolCall, variant }) => { +const ShellToolCallImpl: FC = ({ + toolCall, + variant, +}) => { const { title, content, rawInput, toolCallId } = toolCall; const classPrefix = variant; const platform = usePlatform(); @@ -263,7 +264,7 @@ const ShellToolCallImpl: React.FC< * ShellToolCall - displays bash/execute command tool calls * Shows command input and output with IN/OUT cards */ -export const ShellToolCall: React.FC = (props) => { +export const ShellToolCall: FC = (props) => { const normalizedKind = props.toolCall.kind.toLowerCase(); const variant: ShellVariant = normalizedKind === 'execute' ? 'execute' : 'bash'; diff --git a/packages/webui/src/components/toolcalls/ThinkToolCall.tsx b/packages/webui/src/components/toolcalls/ThinkToolCall.tsx index 1bcf393a7..2acce711b 100644 --- a/packages/webui/src/components/toolcalls/ThinkToolCall.tsx +++ b/packages/webui/src/components/toolcalls/ThinkToolCall.tsx @@ -6,7 +6,7 @@ * Think tool call component - specialized for thinking/reasoning operations */ -import type React from 'react'; +import type { FC } from 'react'; import { ToolCallContainer, ToolCallCard, @@ -20,7 +20,7 @@ import type { BaseToolCallProps } from './shared/index.js'; * Optimized for displaying AI reasoning and thought processes * Minimal display: just show the thoughts (no context) */ -export const ThinkToolCall: React.FC = ({ toolCall }) => { +export const ThinkToolCall: FC = ({ toolCall }) => { const { content } = toolCall; // Group content by type diff --git a/packages/webui/src/components/toolcalls/UpdatedPlanToolCall.tsx b/packages/webui/src/components/toolcalls/UpdatedPlanToolCall.tsx index e07694874..fcccfccfa 100644 --- a/packages/webui/src/components/toolcalls/UpdatedPlanToolCall.tsx +++ b/packages/webui/src/components/toolcalls/UpdatedPlanToolCall.tsx @@ -6,7 +6,7 @@ * UpdatedPlan tool call component - specialized for plan update operations */ -import type React from 'react'; +import type { FC } from 'react'; import { groupContent, safeTitle } from './shared/index.js'; import type { BaseToolCallProps, @@ -20,7 +20,7 @@ import { CheckboxDisplay } from './CheckboxDisplay.js'; /** * Custom container for UpdatedPlanToolCall with specific styling */ -const PlanToolCallContainer: React.FC = ({ +const PlanToolCallContainer: FC = ({ label, status = 'success', children, @@ -113,9 +113,7 @@ const parsePlanEntries = (textOutputs: string[]): PlanEntry[] => { * Specialized component for UpdatedPlan tool calls * Optimized for displaying plan update operations */ -export const UpdatedPlanToolCall: React.FC = ({ - toolCall, -}) => { +export const UpdatedPlanToolCall: FC = ({ toolCall }) => { const { content, status } = toolCall; const { errors, textOutputs } = groupContent(content); diff --git a/packages/webui/src/components/toolcalls/WriteToolCall.tsx b/packages/webui/src/components/toolcalls/WriteToolCall.tsx index a940f07af..ec2c6d1f0 100644 --- a/packages/webui/src/components/toolcalls/WriteToolCall.tsx +++ b/packages/webui/src/components/toolcalls/WriteToolCall.tsx @@ -6,7 +6,7 @@ * Write tool call component - specialized for file writing operations */ -import type React from 'react'; +import type { FC } from 'react'; import { ToolCallContainer, groupContent, @@ -19,7 +19,7 @@ import { FileLink } from '../layout/FileLink.js'; * Specialized component for Write tool calls * Shows: Write filename + error message + content preview */ -export const WriteToolCall: React.FC = ({ toolCall }) => { +export const WriteToolCall: FC = ({ toolCall }) => { const { content, locations, rawInput, toolCallId } = toolCall; // Group content by type diff --git a/packages/webui/src/components/toolcalls/shared/LayoutComponents.tsx b/packages/webui/src/components/toolcalls/shared/LayoutComponents.tsx index 1433d261b..7e0b01deb 100644 --- a/packages/webui/src/components/toolcalls/shared/LayoutComponents.tsx +++ b/packages/webui/src/components/toolcalls/shared/LayoutComponents.tsx @@ -7,7 +7,7 @@ * Platform-agnostic version using webui components */ -import type React from 'react'; +import type { FC } from 'react'; import { FileLink } from '../../layout/FileLink.js'; import './LayoutComponents.css'; @@ -33,7 +33,7 @@ export interface ToolCallContainerProps { * ToolCallContainer - Main container for tool call displays * Features timeline connector line and status bullet */ -export const ToolCallContainer: React.FC = ({ +export const ToolCallContainer: FC = ({ label, status = 'success', children, @@ -73,7 +73,7 @@ interface ToolCallCardProps { /** * ToolCallCard - Legacy card wrapper for complex layouts like diffs */ -export const ToolCallCard: React.FC = ({ +export const ToolCallCard: FC = ({ icon: _icon, children, }) => ( @@ -93,10 +93,7 @@ interface ToolCallRowProps { /** * ToolCallRow - A single row in the tool call grid (legacy - for complex layouts) */ -export const ToolCallRow: React.FC = ({ - label, - children, -}) => ( +export const ToolCallRow: FC = ({ label, children }) => (
{label} @@ -138,10 +135,7 @@ const getStatusColorClass = ( /** * StatusIndicator - Status indicator with colored dot */ -export const StatusIndicator: React.FC = ({ - status, - text, -}) => ( +export const StatusIndicator: FC = ({ status, text }) => (
= ({ children }) => ( +export const CodeBlock: FC = ({ children }) => (
     {children}
   
@@ -179,7 +173,7 @@ interface LocationsListProps { /** * LocationsList - List of file locations with clickable links */ -export const LocationsList: React.FC = ({ locations }) => ( +export const LocationsList: FC = ({ locations }) => (
{locations.map((loc, idx) => ( diff --git a/packages/webui/src/components/toolcalls/shared/copyUtils.tsx b/packages/webui/src/components/toolcalls/shared/copyUtils.tsx index e71169aa1..3ea1cf5a7 100644 --- a/packages/webui/src/components/toolcalls/shared/copyUtils.tsx +++ b/packages/webui/src/components/toolcalls/shared/copyUtils.tsx @@ -6,7 +6,7 @@ * Shared copy utilities for toolcall components */ -import type React from 'react'; +import type { FC } from 'react'; import { useState, useCallback } from 'react'; import { usePlatform } from '../../../context/PlatformContext.js'; @@ -46,7 +46,7 @@ interface CopyButtonProps { * Uses PlatformContext for platform-specific clipboard access with fallback * Note: Parent element should have 'group' class for hover effect */ -export const CopyButton: React.FC = ({ text }) => { +export const CopyButton: FC = ({ text }) => { const [showTooltip, setShowTooltip] = useState(false); const platform = usePlatform(); diff --git a/packages/webui/src/components/ui/Button.tsx b/packages/webui/src/components/ui/Button.tsx index b86a8dcef..e577b1d80 100644 --- a/packages/webui/src/components/ui/Button.tsx +++ b/packages/webui/src/components/ui/Button.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { ReactNode } from 'react'; import { forwardRef } from 'react'; /** @@ -28,7 +28,7 @@ export type ButtonSize = 'sm' | 'md' | 'lg'; export interface ButtonProps extends React.ButtonHTMLAttributes { /** Button content */ - children: React.ReactNode; + children: ReactNode; /** Visual style variant */ variant?: ButtonVariant; /** Button size */ @@ -36,9 +36,9 @@ export interface ButtonProps /** Loading state - shows spinner and disables button */ loading?: boolean; /** Icon to display before children */ - leftIcon?: React.ReactNode; + leftIcon?: ReactNode; /** Icon to display after children */ - rightIcon?: React.ReactNode; + rightIcon?: ReactNode; /** Full width button */ fullWidth?: boolean; } diff --git a/packages/webui/src/components/ui/Input.tsx b/packages/webui/src/components/ui/Input.tsx index 8ac2c8395..57ba5d76a 100644 --- a/packages/webui/src/components/ui/Input.tsx +++ b/packages/webui/src/components/ui/Input.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { ReactNode } from 'react'; import { forwardRef } from 'react'; /** @@ -28,9 +28,9 @@ export interface InputProps /** Helper text below input */ helperText?: string; /** Left icon/element */ - leftElement?: React.ReactNode; + leftElement?: ReactNode; /** Right icon/element */ - rightElement?: React.ReactNode; + rightElement?: ReactNode; /** Full width input */ fullWidth?: boolean; } diff --git a/packages/webui/src/components/ui/Tooltip.tsx b/packages/webui/src/components/ui/Tooltip.tsx index 05846d637..38ffc4f87 100644 --- a/packages/webui/src/components/ui/Tooltip.tsx +++ b/packages/webui/src/components/ui/Tooltip.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; +import type { FC } from 'react'; /** * Tooltip component props @@ -22,7 +22,7 @@ export interface TooltipProps { * Tooltip component using CSS group-hover for display * Supports CSS variables for theming */ -export const Tooltip: React.FC = ({ +export const Tooltip: FC = ({ children, content, position = 'top', diff --git a/packages/webui/src/context/PlatformContext.tsx b/packages/webui/src/context/PlatformContext.tsx index 9a48baca5..793d58518 100644 --- a/packages/webui/src/context/PlatformContext.tsx +++ b/packages/webui/src/context/PlatformContext.tsx @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; import { createContext, useContext } from 'react'; +import type { ReactNode } from 'react'; /** * Platform types supported by the webui library @@ -88,7 +88,7 @@ export function usePlatform(): PlatformContextValue { * Provider component props */ export interface PlatformProviderProps { - children: React.ReactNode; + children: ReactNode; value: PlatformContextValue; } diff --git a/packages/webui/src/index.ts b/packages/webui/src/index.ts index 2054d9e1c..0ffa52c8e 100644 --- a/packages/webui/src/index.ts +++ b/packages/webui/src/index.ts @@ -205,3 +205,6 @@ export type { CompletionItem, CompletionItemType } from './types/completion'; // Utils export { groupSessionsByDate, getTimeAgo } from './utils/sessionGrouping'; export type { SessionGroup } from './utils/sessionGrouping'; + +// VSCode Webview utilities +export { default as WebviewContainer } from './components/WebviewContainer'; diff --git a/packages/webui/src/styles/variables.css b/packages/webui/src/styles/variables.css index 81e88966a..2c955c8c6 100644 --- a/packages/webui/src/styles/variables.css +++ b/packages/webui/src/styles/variables.css @@ -78,8 +78,8 @@ --app-radius-sm: 0.25rem; --app-radius-md: 0.375rem; --app-radius-lg: 0.5rem; - --corner-radius-small: 6px; - --corner-radius-medium: 8px; + --qwen-corner-radius-small: 6px; + --qwen-corner-radius-medium: 8px; /* =========================== Spacing diff --git a/packages/webui/src/styles/webview.css b/packages/webui/src/styles/webview.css new file mode 100644 index 000000000..aaddf7861 --- /dev/null +++ b/packages/webui/src/styles/webview.css @@ -0,0 +1,254 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + * + * Isolated styles for VSCode webview environments to prevent conflicts + */ + +/* Isolate all webui styles under a specific namespace to prevent global conflicts in webviews */ +.qwen-webui-container { + /* Apply all CSS variables in the isolated namespace */ + --app-primary: var(--app-primary, #3b82f6); + --app-primary-hover: var(--app-primary-hover, #2563eb); + --app-primary-foreground: var(--app-primary-foreground, #e4e4e7); + --app-secondary-foreground: var(--app-secondary-foreground, #a1a1aa); + + --app-background: var(--app-background, #1e1e1e); + --app-primary-background: var(--app-primary-background, #1e1e1e); + --app-background-secondary: var(--app-background-secondary, #252526); + --app-secondary-background: var(--app-secondary-background, #252526); + --app-background-tertiary: var(--app-background-tertiary, #2d2d2d); + + --app-foreground: var(--app-foreground, #e4e4e7); + --app-foreground-secondary: var(--app-foreground-secondary, #a1a1aa); + --app-foreground-muted: var(--app-foreground-muted, #71717a); + + --app-border: var(--app-border, #3f3f46); + --app-border-focus: var(--app-border-focus, #3b82f6); + --app-primary-border-color: var(--app-primary-border-color, #3f3f46); + + --app-success: var(--app-success, #10b981); + --app-warning: var(--app-warning, #f59e0b); + --app-error: var(--app-error, #ef4444); + --app-info: var(--app-info, #3b82f6); + + --app-font-sans: var(--app-font-sans, system-ui, -apple-system, sans-serif); + --app-font-mono: var(--app-font-mono, ui-monospace, 'SF Mono', monospace); + --app-monospace-font-size: var(--app-monospace-font-size, 13px); + + --app-link-foreground: var(--app-link-foreground, #007acc); + --app-link-active-foreground: var(--app-link-active-foreground, #005a9e); + + --app-qwen-ivory: var(--app-qwen-ivory, #f5f5dc); + + --app-radius-sm: var(--app-radius-sm, 0.25rem); + --app-radius-md: var(--app-radius-md, 0.375rem); + --app-radius-lg: var(--app-radius-lg, 0.5rem); + --qwen-corner-radius-small: var(--qwen-corner-radius-small, 6px); + --qwen-corner-radius-medium: var(--qwen-corner-radius-medium, 8px); + + --app-spacing-xs: var(--app-spacing-xs, 0.25rem); + --app-spacing-sm: var(--app-spacing-sm, 0.5rem); + --app-spacing-md: var(--app-spacing-md, 1rem); + --app-spacing-medium: var(--app-spacing-medium, 8px); + --app-spacing-lg: var(--app-spacing-lg, 1.5rem); + --app-spacing-xl: var(--app-spacing-xl, 2rem); + + --app-input-background: var(--app-input-background, #3c3c3c); + --app-input-secondary-background: var(--app-input-secondary-background, #2d2d2d); + --app-input-border: var(--app-input-border, #3f3f46); + --app-input-foreground: var(--app-input-foreground, #e4e4e7); + --app-input-placeholder-foreground: var(--app-input-placeholder-foreground, #71717a); + + --app-ghost-button-hover-background: var(--app-ghost-button-hover-background, rgba(90, 93, 94, 0.31)); + --app-button-background: var(--app-button-background, #3c3c3c); + --app-button-foreground: var(--app-button-foreground, #ffffff); + --app-transparent-inner-border: var(--app-transparent-inner-border, rgba(255, 255, 255, 0.1)); + + --app-header-background: var(--app-header-background, #252526); + + --app-list-padding: var(--app-list-padding, 0px); + --app-list-item-padding: var(--app-list-item-padding, 4px 8px); + --app-list-border-color: var(--app-list-border-color, transparent); + --app-list-border-radius: var(--app-list-border-radius, 4px); + --app-list-hover-background: var(--app-list-hover-background, rgba(90, 93, 94, 0.31)); + --app-list-active-background: var(--app-list-active-background, #094771); + --app-list-active-foreground: var(--app-list-active-foreground, #ffffff); + --app-list-gap: var(--app-list-gap, 2px); + + --app-menu-background: var(--app-menu-background, #252526); + --app-menu-border: var(--app-menu-border, #454545); + --app-menu-foreground: var(--app-menu-foreground, #cccccc); + --app-menu-selection-background: var(--app-menu-selection-background, #094771); + --app-menu-selection-foreground: var(--app-menu-selection-foreground, #ffffff); + + --app-tool-background: var(--app-tool-background, #1e1e1e); + --app-code-background: var(--app-code-background, #2d2d2d); + + --app-warning-background: var(--app-warning-background, rgba(255, 204, 0, 0.1)); + --app-warning-border: var(--app-warning-border, #ffcc00); + --app-warning-foreground: var(--app-warning-foreground, #ffcc00); +} + +/* Reset potential conflicts with VSCode webview styles */ +.qwen-webui-container *, +.qwen-webui-container *::before, +.qwen-webui-container *::after { + box-sizing: border-box; +} + +/* Prevent styles from bleeding out of the container */ +.qwen-webui-container { + all: inherit; + font: inherit; + line-height: inherit; + letter-spacing: inherit; + color: var(--app-foreground); + background: var(--app-background); + width: 100%; + height: 100%; +} + +/* Isolated component styles */ +.qwen-webui-container .code-block { + font-family: var(--app-font-mono); + font-size: var(--app-monospace-font-size, 13px); + background: var(--app-primary-background); + border: 1px solid var(--app-input-border); + border-radius: var(--app-radius-sm, 4px); + padding: var(--app-spacing-medium, 8px); + overflow-x: auto; + margin: 4px 0 0 0; + white-space: pre-wrap; + word-break: break-word; + max-height: 300px; + overflow-y: auto; +} + +.qwen-webui-container .diff-display-container { + margin: 8px 0; + border: 1px solid var(--app-input-border); + border-radius: var(--app-radius-md, 6px); + overflow: hidden; +} + +.qwen-webui-container .diff-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + background: var(--app-input-secondary-background, var(--app-background-secondary)); + border-bottom: 1px solid var(--app-input-border); +} + +.qwen-webui-container .diff-file-path { + font-family: var(--app-font-mono); + font-size: 13px; + color: var(--app-primary-foreground); +} + +.qwen-webui-container .open-diff-button { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + background: transparent; + border: 1px solid var(--app-input-border); + border-radius: var(--app-radius-sm, 4px); + color: var(--app-primary-foreground); + cursor: pointer; + font-size: 12px; + transition: background-color 0.15s; +} + +.qwen-webui-container .open-diff-button:hover { + background: var(--app-ghost-button-hover-background); +} + +.qwen-webui-container .diff-label { + padding: 8px 12px; + background: var(--app-primary-background); + border-bottom: 1px solid var(--app-input-border); + font-size: 11px; + font-weight: 600; + color: var(--app-secondary-foreground); + text-transform: uppercase; +} + +/* Timeline styles in isolation */ +.qwen-webui-container .toolcall-container { + position: relative; + padding-left: 30px; + padding-top: 8px; + padding-bottom: 8px; +} + +.qwen-webui-container .toolcall-container::after { + content: ''; + position: absolute; + left: 12px; + top: 0; + bottom: 0; + width: 1px; + background-color: var(--app-primary-border-color); +} + +.qwen-webui-container .toolcall-container:first-child::after { + top: 24px; +} + +.qwen-webui-container .toolcall-container:last-child::after { + height: calc(100% - 24px); + top: 0; + bottom: auto; +} + +.qwen-webui-container .assistant-message-container { + position: relative; + padding-left: 30px; + padding-top: 8px; + padding-bottom: 8px; +} + +.qwen-webui-container .assistant-message-container::after { + content: ''; + position: absolute; + left: 12px; + top: 0; + bottom: 0; + width: 1px; + background-color: var(--app-primary-border-color); +} + +.qwen-webui-container .assistant-message-container:first-child::after { + top: 24px; +} + +.qwen-webui-container .assistant-message-container:last-child::after { + height: calc(100% - 24px); + top: 0; + bottom: auto; +} + +.qwen-webui-container .qwen-message.message-item:not(.user-message-container)::after { + content: ''; + position: absolute; + left: 12px; + top: 0; + bottom: 0; + width: 1px; + background-color: var(--app-primary-border-color); + z-index: 0; +} + +.qwen-webui-container .message-item { + padding: 8px 0; + width: 100%; + align-items: flex-start; + padding-left: 30px; + user-select: text; + position: relative; + padding-top: 8px; + padding-bottom: 8px; +} \ No newline at end of file diff --git a/packages/webui/src/types/completion.ts b/packages/webui/src/types/completion.ts index ebecd1b89..bef887625 100644 --- a/packages/webui/src/types/completion.ts +++ b/packages/webui/src/types/completion.ts @@ -6,7 +6,7 @@ * Completion item types for autocomplete menus */ -import type React from 'react'; +import type { ReactNode } from 'react'; /** * Completion item type categories @@ -30,7 +30,7 @@ export interface CompletionItem { /** Optional description shown below label */ description?: string; /** Optional icon to display */ - icon?: React.ReactNode; + icon?: ReactNode; /** Type of completion item */ type: CompletionItemType; /** Value inserted into the input when selected (e.g., filename or command) */