mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 03:30:40 +00:00
feat(webui): add webview container and isolate styles for VSCode integration
- Introduce WebviewContainer component for style isolation in VSCode webviews - Rename CSS variables from --app-* to --qwen-app-* to prevent conflicts - Add dedicated webview.css with isolated styles - Update exports to include webview.css in package - Modify all components to use new CSS variable names - Update VSCode IDE companion to use new webview container - Add style isolation to prevent conflicts with VSCode environment Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Fixes webview UI issues in VSCode IDE Companion by providing proper style encapsulation.
This commit is contained in:
parent
9a47ad5e62
commit
ff43a278dc
64 changed files with 564 additions and 216 deletions
|
|
@ -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"',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<InputFormProps> = ({ editMode, ...rest }) => {
|
||||
export const InputForm: FC<InputFormProps> = ({ editMode, ...rest }) => {
|
||||
const editModeInfo = getEditModeInfo(editMode);
|
||||
|
||||
return <BaseInputForm editModeInfo={editModeInfo} {...rest} />;
|
||||
|
|
|
|||
|
|
@ -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<OnboardingPageProps> = ({ onLogin }) => {
|
||||
export const Onboarding: FC<OnboardingPageProps> = ({ onLogin }) => {
|
||||
const iconUri = generateIconUrl('icon.png');
|
||||
|
||||
return <BaseOnboarding iconUrl={iconUri} onGetStarted={onLogin} />;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<BaseToolCallProps> => {
|
||||
export const getToolCallComponent = (kind: string): FC<BaseToolCallProps> => {
|
||||
const normalizedKind = kind.toLowerCase();
|
||||
|
||||
// Route to specialized components
|
||||
|
|
|
|||
|
|
@ -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<VSCodePlatformProviderProps> = ({
|
||||
export const VSCodePlatformProvider: FC<VSCodePlatformProviderProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const vscode = useVSCode();
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<PermissionDrawerProps> = ({
|
||||
const PermissionDrawer: FC<PermissionDrawerProps> = ({
|
||||
isOpen,
|
||||
options,
|
||||
toolCall,
|
||||
|
|
@ -290,10 +290,10 @@ interface CustomMessageInputRowProps {
|
|||
setCustomMessage: (val: string) => void;
|
||||
onFocusRow: () => void;
|
||||
onSubmitReject: () => void;
|
||||
inputRef: React.RefObject<HTMLInputElement | null>;
|
||||
inputRef: RefObject<HTMLInputElement | null>;
|
||||
}
|
||||
|
||||
const CustomMessageInputRow: React.FC<CustomMessageInputRowProps> = ({
|
||||
const CustomMessageInputRow: FC<CustomMessageInputRowProps> = ({
|
||||
isFocused,
|
||||
customMessage,
|
||||
setCustomMessage,
|
||||
|
|
@ -309,7 +309,7 @@ const CustomMessageInputRow: React.FC<CustomMessageInputRowProps> = ({
|
|||
onClick={() => inputRef.current?.focus()}
|
||||
>
|
||||
<input
|
||||
ref={inputRef as React.LegacyRef<HTMLInputElement> | undefined}
|
||||
ref={inputRef as unknown as RefObject<HTMLInputElement>}
|
||||
type="text"
|
||||
placeholder="Tell Qwen what to do instead"
|
||||
spellCheck={false}
|
||||
|
|
|
|||
17
packages/webui/src/components/WebviewContainer.tsx
Normal file
17
packages/webui/src/components/WebviewContainer.tsx
Normal file
|
|
@ -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<WebviewContainerProps> = ({
|
||||
children,
|
||||
className = '',
|
||||
}) => <div className={`qwen-webui-container ${className}`}>{children}</div>;
|
||||
|
||||
export default WebviewContainer;
|
||||
|
|
@ -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<CloseIconProps> = ({
|
||||
const CloseIcon: FC<CloseIconProps> = ({
|
||||
size = 24,
|
||||
color = 'currentColor',
|
||||
className = '',
|
||||
|
|
|
|||
|
|
@ -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<IconProps> = ({
|
||||
export const EditPencilIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -40,7 +40,7 @@ export const EditPencilIcon: React.FC<IconProps> = ({
|
|||
* Auto/fast-forward icon (16x16)
|
||||
* Used for "Edit automatically" mode
|
||||
*/
|
||||
export const AutoEditIcon: React.FC<IconProps> = ({
|
||||
export const AutoEditIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -63,7 +63,7 @@ export const AutoEditIcon: React.FC<IconProps> = ({
|
|||
* Plan mode/bars icon (16x16)
|
||||
* Used for "Plan mode"
|
||||
*/
|
||||
export const PlanModeIcon: React.FC<IconProps> = ({
|
||||
export const PlanModeIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -86,7 +86,7 @@ export const PlanModeIcon: React.FC<IconProps> = ({
|
|||
* Code brackets icon (20x20)
|
||||
* Used for active file indicator
|
||||
*/
|
||||
export const CodeBracketsIcon: React.FC<IconProps> = ({
|
||||
export const CodeBracketsIcon: FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -113,7 +113,7 @@ export const CodeBracketsIcon: React.FC<IconProps> = ({
|
|||
* Hide context (eye slash) icon (20x20)
|
||||
* Used to indicate the active selection will NOT be auto-loaded into context
|
||||
*/
|
||||
export const HideContextIcon: React.FC<IconProps> = ({
|
||||
export const HideContextIcon: FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -141,7 +141,7 @@ export const HideContextIcon: React.FC<IconProps> = ({
|
|||
* Slash command icon (20x20)
|
||||
* Used for command menu button
|
||||
*/
|
||||
export const SlashCommandIcon: React.FC<IconProps> = ({
|
||||
export const SlashCommandIcon: FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -168,11 +168,7 @@ export const SlashCommandIcon: React.FC<IconProps> = ({
|
|||
* Link/attachment icon (20x20)
|
||||
* Used for attach context button
|
||||
*/
|
||||
export const LinkIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
export const LinkIcon: FC<IconProps> = ({ size = 20, className, ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
|
|
@ -195,7 +191,7 @@ export const LinkIcon: React.FC<IconProps> = ({
|
|||
* Open diff icon (16x16)
|
||||
* Used for opening diff in VS Code
|
||||
*/
|
||||
export const OpenDiffIcon: React.FC<IconProps> = ({
|
||||
export const OpenDiffIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -218,11 +214,7 @@ export const OpenDiffIcon: React.FC<IconProps> = ({
|
|||
* Undo edit icon (16x16)
|
||||
* Used for undoing edits in diff views
|
||||
*/
|
||||
export const UndoIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
export const UndoIcon: FC<IconProps> = ({ size = 16, className, ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
|
|
@ -247,11 +239,7 @@ export const UndoIcon: React.FC<IconProps> = ({
|
|||
* Redo edit icon (16x16)
|
||||
* Used for redoing edits in diff views
|
||||
*/
|
||||
export const RedoIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
export const RedoIcon: FC<IconProps> = ({ size = 16, className, ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
|
|
@ -276,7 +264,7 @@ export const RedoIcon: React.FC<IconProps> = ({
|
|||
* Replace all icon (16x16)
|
||||
* Used for replacing all occurrences in search/replace
|
||||
*/
|
||||
export const ReplaceAllIcon: React.FC<IconProps> = ({
|
||||
export const ReplaceAllIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -305,11 +293,7 @@ export const ReplaceAllIcon: React.FC<IconProps> = ({
|
|||
* Copy icon (16x16)
|
||||
* Used for copying content
|
||||
*/
|
||||
export const CopyIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
export const CopyIcon: FC<IconProps> = ({ size = 16, className, ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
|
|
@ -342,7 +326,7 @@ export const CopyIcon: React.FC<IconProps> = ({
|
|||
* Paste icon (16x16)
|
||||
* Used for pasting content
|
||||
*/
|
||||
export const PasteIcon: React.FC<IconProps> = ({
|
||||
export const PasteIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -371,7 +355,7 @@ export const PasteIcon: React.FC<IconProps> = ({
|
|||
* Select all icon (16x16)
|
||||
* Used for selecting all content
|
||||
*/
|
||||
export const SelectAllIcon: React.FC<IconProps> = ({
|
||||
export const SelectAllIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
|
|||
|
|
@ -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<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
export const FileIcon: FC<IconProps> = ({ size = 16, className, ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
|
|
@ -32,7 +28,7 @@ export const FileIcon: React.FC<IconProps> = ({
|
|||
</svg>
|
||||
);
|
||||
|
||||
export const FileListIcon: React.FC<IconProps> = ({
|
||||
export const FileListIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -55,7 +51,7 @@ export const FileListIcon: React.FC<IconProps> = ({
|
|||
* Save document icon (16x16)
|
||||
* Used for save session button
|
||||
*/
|
||||
export const SaveDocumentIcon: React.FC<IconProps> = ({
|
||||
export const SaveDocumentIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -83,7 +79,7 @@ export const SaveDocumentIcon: React.FC<IconProps> = ({
|
|||
* Folder icon (16x16)
|
||||
* Useful for directory entries in completion lists
|
||||
*/
|
||||
export const FolderIcon: React.FC<IconProps> = ({
|
||||
export const FolderIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
|
|||
|
|
@ -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<IconProps> = ({
|
||||
const Icon: FC<IconProps> = ({
|
||||
name,
|
||||
size = 24,
|
||||
color = 'currentColor',
|
||||
|
|
|
|||
|
|
@ -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<IconProps> = ({
|
||||
export const ChevronDownIcon: FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -40,11 +40,7 @@ export const ChevronDownIcon: React.FC<IconProps> = ({
|
|||
* Plus icon (20x20)
|
||||
* Used for new session button
|
||||
*/
|
||||
export const PlusIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
export const PlusIcon: FC<IconProps> = ({ size = 20, className, ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
|
|
@ -63,7 +59,7 @@ export const PlusIcon: React.FC<IconProps> = ({
|
|||
* Small plus icon (16x16)
|
||||
* Used for default attachment type
|
||||
*/
|
||||
export const PlusSmallIcon: React.FC<IconProps> = ({
|
||||
export const PlusSmallIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -86,7 +82,7 @@ export const PlusSmallIcon: React.FC<IconProps> = ({
|
|||
* Arrow up icon (20x20)
|
||||
* Used for send message button
|
||||
*/
|
||||
export const ArrowUpIcon: React.FC<IconProps> = ({
|
||||
export const ArrowUpIcon: FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -113,7 +109,7 @@ export const ArrowUpIcon: React.FC<IconProps> = ({
|
|||
* Close X icon (14x14)
|
||||
* Used for close buttons in banners and dialogs
|
||||
*/
|
||||
export const CloseIcon: React.FC<IconProps> = ({
|
||||
export const CloseIcon: FC<IconProps> = ({
|
||||
size = 14,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -137,7 +133,7 @@ export const CloseIcon: React.FC<IconProps> = ({
|
|||
</svg>
|
||||
);
|
||||
|
||||
export const CloseSmallIcon: React.FC<IconProps> = ({
|
||||
export const CloseSmallIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -160,7 +156,7 @@ export const CloseSmallIcon: React.FC<IconProps> = ({
|
|||
* Search/magnifying glass icon (20x20)
|
||||
* Used for search input
|
||||
*/
|
||||
export const SearchIcon: React.FC<IconProps> = ({
|
||||
export const SearchIcon: FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -187,7 +183,7 @@ export const SearchIcon: React.FC<IconProps> = ({
|
|||
* Refresh/reload icon (16x16)
|
||||
* Used for refresh session list
|
||||
*/
|
||||
export const RefreshIcon: React.FC<IconProps> = ({
|
||||
export const RefreshIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
|
|||
|
|
@ -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<SendIconProps> = ({
|
||||
const SendIcon: FC<SendIconProps> = ({
|
||||
size = 24,
|
||||
color = 'currentColor',
|
||||
className = '',
|
||||
|
|
|
|||
|
|
@ -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<ThinkingIconProps> = ({
|
||||
export const ThinkingIcon: FC<ThinkingIconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
enabled = false,
|
||||
|
|
@ -49,7 +49,7 @@ export const ThinkingIcon: React.FC<ThinkingIconProps> = ({
|
|||
</svg>
|
||||
);
|
||||
|
||||
export const TerminalIcon: React.FC<IconProps> = ({
|
||||
export const TerminalIcon: FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
|
|
|
|||
|
|
@ -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<IconProps> = ({
|
||||
export const PlanCompletedIcon: FC<IconProps> = ({
|
||||
size = 14,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -43,7 +43,7 @@ export const PlanCompletedIcon: React.FC<IconProps> = ({
|
|||
* Plan in progress icon (14x14)
|
||||
* Used for in-progress plan items
|
||||
*/
|
||||
export const PlanInProgressIcon: React.FC<IconProps> = ({
|
||||
export const PlanInProgressIcon: FC<IconProps> = ({
|
||||
size = 14,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -73,7 +73,7 @@ export const PlanInProgressIcon: React.FC<IconProps> = ({
|
|||
* Plan pending icon (14x14)
|
||||
* Used for pending plan items
|
||||
*/
|
||||
export const PlanPendingIcon: React.FC<IconProps> = ({
|
||||
export const PlanPendingIcon: FC<IconProps> = ({
|
||||
size = 14,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -103,7 +103,7 @@ export const PlanPendingIcon: React.FC<IconProps> = ({
|
|||
* Warning triangle icon (20x20)
|
||||
* Used for warning messages
|
||||
*/
|
||||
export const WarningTriangleIcon: React.FC<IconProps> = ({
|
||||
export const WarningTriangleIcon: FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -130,11 +130,7 @@ export const WarningTriangleIcon: React.FC<IconProps> = ({
|
|||
* User profile icon (16x16)
|
||||
* Used for login command
|
||||
*/
|
||||
export const UserIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
export const UserIcon: FC<IconProps> = ({ size = 16, className, ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
|
|
@ -149,7 +145,7 @@ export const UserIcon: React.FC<IconProps> = ({
|
|||
</svg>
|
||||
);
|
||||
|
||||
export const SymbolIcon: React.FC<IconProps> = ({
|
||||
export const SymbolIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
@ -168,7 +164,7 @@ export const SymbolIcon: React.FC<IconProps> = ({
|
|||
</svg>
|
||||
);
|
||||
|
||||
export const SelectionIcon: React.FC<IconProps> = ({
|
||||
export const SelectionIcon: FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
|
|
|
|||
|
|
@ -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<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
export const StopIcon: FC<IconProps> = ({ size = 16, className, ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@
|
|||
* Common icon props interface
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { SVGProps } from 'react';
|
||||
|
||||
export interface IconProps extends React.SVGProps<SVGSVGElement> {
|
||||
export interface IconProps extends SVGProps<SVGSVGElement> {
|
||||
/**
|
||||
* Icon size (width and height)
|
||||
* @default 16
|
||||
|
|
|
|||
|
|
@ -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<ChatHeaderProps> = ({
|
||||
export const ChatHeader: FC<ChatHeaderProps> = ({
|
||||
currentSessionTitle,
|
||||
onLoadSessions,
|
||||
onNewSession,
|
||||
|
|
|
|||
|
|
@ -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<CompletionMenuProps> = ({
|
||||
export const CompletionMenu: FC<CompletionMenuProps> = ({
|
||||
items,
|
||||
onSelect,
|
||||
onClose,
|
||||
|
|
|
|||
|
|
@ -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<ContainerProps> = ({ children, className = '' }) => (
|
||||
const Container: FC<ContainerProps> = ({ children, className = '' }) => (
|
||||
<div className={`container mx-auto px-4 ${className}`}>{children}</div>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ContextIndicatorProps> = ({
|
||||
export const ContextIndicator: FC<ContextIndicatorProps> = ({
|
||||
contextUsage,
|
||||
}) => {
|
||||
if (!contextUsage) {
|
||||
|
|
|
|||
|
|
@ -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<EmptyStateProps> = ({
|
||||
export const EmptyState: FC<EmptyStateProps> = ({
|
||||
isAuthenticated = false,
|
||||
loadingMessage,
|
||||
logoUrl,
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
* <FileLink path="/src/components/Button.tsx" line={10} column={5} showFullPath={true} />
|
||||
* ```
|
||||
*/
|
||||
export const FileLink: React.FC<FileLinkProps> = ({
|
||||
export const FileLink: FC<FileLinkProps> = ({
|
||||
path,
|
||||
line,
|
||||
column,
|
||||
|
|
|
|||
|
|
@ -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>Footer Component Placeholder</footer>;
|
||||
const Footer: FC = () => <footer>Footer Component Placeholder</footer>;
|
||||
|
||||
export default Footer;
|
||||
|
|
|
|||
|
|
@ -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>Header Component Placeholder</header>;
|
||||
const Header: FC = () => <header>Header Component Placeholder</header>;
|
||||
|
||||
export default Header;
|
||||
|
|
|
|||
|
|
@ -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<InputFormProps, 'inputFieldRef'>;
|
|||
/**
|
||||
* Wrapper component to provide inputFieldRef
|
||||
*/
|
||||
const InputFormWrapper: React.FC<InputFormStoryProps> = (props) => {
|
||||
const InputFormWrapper: FC<InputFormStoryProps> = (props) => {
|
||||
const inputFieldRef = useRef<HTMLDivElement>(null);
|
||||
return <InputForm {...props} inputFieldRef={inputFieldRef} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<InputFormProps> = ({
|
||||
export const InputForm: FC<InputFormProps> = ({
|
||||
inputText,
|
||||
inputFieldRef,
|
||||
isStreaming,
|
||||
|
|
|
|||
|
|
@ -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>Main Component Placeholder</main>;
|
||||
const Main: FC = () => <main>Main Component Placeholder</main>;
|
||||
|
||||
export default Main;
|
||||
|
|
|
|||
|
|
@ -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<OnboardingProps> = ({
|
||||
export const Onboarding: FC<OnboardingProps> = ({
|
||||
iconUrl,
|
||||
onGetStarted,
|
||||
appName = 'Qwen Code',
|
||||
|
|
|
|||
|
|
@ -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<SessionSelectorProps> = ({
|
||||
export const SessionSelector: FC<SessionSelectorProps> = ({
|
||||
visible,
|
||||
sessions,
|
||||
currentSessionId,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
const Sidebar: React.FC = () => <aside>Sidebar Component Placeholder</aside>;
|
||||
const Sidebar: FC = () => <aside>Sidebar Component Placeholder</aside>;
|
||||
|
||||
export default Sidebar;
|
||||
|
|
|
|||
|
|
@ -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<AssistantMessageProps> = ({
|
||||
export const AssistantMessage: FC<AssistantMessageProps> = ({
|
||||
content,
|
||||
timestamp: _timestamp,
|
||||
onFileClick,
|
||||
|
|
|
|||
|
|
@ -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<MarkdownRendererProps> = ({
|
||||
export const MarkdownRenderer: FC<MarkdownRendererProps> = ({
|
||||
content,
|
||||
onFileClick,
|
||||
enableFileLinks = true,
|
||||
|
|
|
|||
|
|
@ -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<MessageProps> = ({
|
||||
const Message: FC<MessageProps> = ({
|
||||
content,
|
||||
sender,
|
||||
timestamp,
|
||||
|
|
|
|||
|
|
@ -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<MessageContentProps> = ({
|
||||
const MessageContentBase: FC<MessageContentProps> = ({
|
||||
content,
|
||||
onFileClick,
|
||||
enableFileLinks,
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
const MessageInput: React.FC = () => (
|
||||
<div>MessageInput Component Placeholder</div>
|
||||
);
|
||||
const MessageInput: FC = () => <div>MessageInput Component Placeholder</div>;
|
||||
|
||||
export default MessageInput;
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
const MessageList: React.FC = () => (
|
||||
<div>MessageList Component Placeholder</div>
|
||||
);
|
||||
const MessageList: FC = () => <div>MessageList Component Placeholder</div>;
|
||||
|
||||
export default MessageList;
|
||||
|
|
|
|||
|
|
@ -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<ThinkingMessageProps> = ({
|
||||
export const ThinkingMessage: FC<ThinkingMessageProps> = ({
|
||||
content,
|
||||
timestamp: _timestamp,
|
||||
onFileClick,
|
||||
|
|
|
|||
|
|
@ -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<UserMessageProps> = ({
|
||||
export const UserMessage: FC<UserMessageProps> = ({
|
||||
content,
|
||||
timestamp: _timestamp,
|
||||
onFileClick,
|
||||
|
|
|
|||
|
|
@ -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<InterruptedMessageProps> = ({
|
||||
export const InterruptedMessage: FC<InterruptedMessageProps> = ({
|
||||
text = 'Interrupted',
|
||||
}) => (
|
||||
<div className="flex gap-0 items-start text-left py-2 flex-col opacity-85">
|
||||
|
|
|
|||
|
|
@ -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<WaitingMessageProps> = ({
|
||||
loadingMessage,
|
||||
}) => {
|
||||
export const WaitingMessage: FC<WaitingMessageProps> = ({ loadingMessage }) => {
|
||||
// Build a phrase list that starts with the provided message (if any), then witty fallbacks
|
||||
const phrases = useMemo(() => {
|
||||
const set = new Set<string>();
|
||||
|
|
|
|||
|
|
@ -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<CheckboxDisplayProps> = ({
|
||||
export const CheckboxDisplay: FC<CheckboxDisplayProps> = ({
|
||||
checked = false,
|
||||
indeterminate = false,
|
||||
disabled = true,
|
||||
|
|
|
|||
|
|
@ -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<BaseToolCallProps> = ({ toolCall }) => {
|
||||
export const GenericToolCall: FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { kind, title, content, locations, toolCallId } = toolCall;
|
||||
const operationText = safeTitle(title);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ToolCallContainerProps> = ({
|
||||
const ReadToolCallContainer: FC<ToolCallContainerProps> = ({
|
||||
label,
|
||||
status = 'success',
|
||||
children,
|
||||
|
|
@ -56,7 +56,7 @@ const ReadToolCallContainer: React.FC<ToolCallContainerProps> = ({
|
|||
* ReadToolCall - displays file reading operations
|
||||
* Shows: Read filename (no content preview)
|
||||
*/
|
||||
export const ReadToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
export const ReadToolCall: FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { kind, content, locations, toolCallId } = toolCall;
|
||||
const platform = usePlatform();
|
||||
const openedDiffsRef = useRef<Map<string, string>>(new Map());
|
||||
|
|
|
|||
|
|
@ -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 }) => (
|
||||
<div className="flex flex-col gap-1 max-w-full">
|
||||
|
|
@ -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<BaseToolCallProps> = ({
|
||||
export const SearchToolCall: FC<BaseToolCallProps> = ({
|
||||
toolCall,
|
||||
isFirst,
|
||||
isLast,
|
||||
|
|
|
|||
|
|
@ -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<ToolCallContainerProps> = ({
|
||||
const ExecuteToolCallContainer: FC<ToolCallContainerProps> = ({
|
||||
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<BaseToolCallProps & { variant: ShellVariant }> = ({
|
||||
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<BaseToolCallProps> = (props) => {
|
||||
export const ShellToolCall: FC<BaseToolCallProps> = (props) => {
|
||||
const normalizedKind = props.toolCall.kind.toLowerCase();
|
||||
const variant: ShellVariant =
|
||||
normalizedKind === 'execute' ? 'execute' : 'bash';
|
||||
|
|
|
|||
|
|
@ -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<BaseToolCallProps> = ({ toolCall }) => {
|
||||
export const ThinkToolCall: FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { content } = toolCall;
|
||||
|
||||
// Group content by type
|
||||
|
|
|
|||
|
|
@ -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<ToolCallContainerProps> = ({
|
||||
const PlanToolCallContainer: FC<ToolCallContainerProps> = ({
|
||||
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<BaseToolCallProps> = ({
|
||||
toolCall,
|
||||
}) => {
|
||||
export const UpdatedPlanToolCall: FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { content, status } = toolCall;
|
||||
const { errors, textOutputs } = groupContent(content);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<BaseToolCallProps> = ({ toolCall }) => {
|
||||
export const WriteToolCall: FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { content, locations, rawInput, toolCallId } = toolCall;
|
||||
|
||||
// Group content by type
|
||||
|
|
|
|||
|
|
@ -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<ToolCallContainerProps> = ({
|
||||
export const ToolCallContainer: FC<ToolCallContainerProps> = ({
|
||||
label,
|
||||
status = 'success',
|
||||
children,
|
||||
|
|
@ -73,7 +73,7 @@ interface ToolCallCardProps {
|
|||
/**
|
||||
* ToolCallCard - Legacy card wrapper for complex layouts like diffs
|
||||
*/
|
||||
export const ToolCallCard: React.FC<ToolCallCardProps> = ({
|
||||
export const ToolCallCard: FC<ToolCallCardProps> = ({
|
||||
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<ToolCallRowProps> = ({
|
||||
label,
|
||||
children,
|
||||
}) => (
|
||||
export const ToolCallRow: FC<ToolCallRowProps> = ({ label, children }) => (
|
||||
<div className="grid grid-cols-[80px_1fr] gap-medium min-w-0">
|
||||
<div className="text-xs text-[var(--app-secondary-foreground)] font-medium pt-[2px]">
|
||||
{label}
|
||||
|
|
@ -138,10 +135,7 @@ const getStatusColorClass = (
|
|||
/**
|
||||
* StatusIndicator - Status indicator with colored dot
|
||||
*/
|
||||
export const StatusIndicator: React.FC<StatusIndicatorProps> = ({
|
||||
status,
|
||||
text,
|
||||
}) => (
|
||||
export const StatusIndicator: FC<StatusIndicatorProps> = ({ status, text }) => (
|
||||
<div className="inline-block font-medium relative" title={status}>
|
||||
<span
|
||||
className={`inline-block w-1.5 h-1.5 rounded-full mr-1.5 align-middle ${getStatusColorClass(status)}`}
|
||||
|
|
@ -160,7 +154,7 @@ interface CodeBlockProps {
|
|||
/**
|
||||
* CodeBlock - Code block for displaying formatted code or output
|
||||
*/
|
||||
export const CodeBlock: React.FC<CodeBlockProps> = ({ children }) => (
|
||||
export const CodeBlock: FC<CodeBlockProps> = ({ children }) => (
|
||||
<pre className="font-mono text-[var(--app-monospace-font-size)] bg-[var(--app-primary-background)] border border-[var(--app-input-border)] rounded-small p-medium overflow-x-auto mt-1 whitespace-pre-wrap break-words max-h-[300px] overflow-y-auto">
|
||||
{children}
|
||||
</pre>
|
||||
|
|
@ -179,7 +173,7 @@ interface LocationsListProps {
|
|||
/**
|
||||
* LocationsList - List of file locations with clickable links
|
||||
*/
|
||||
export const LocationsList: React.FC<LocationsListProps> = ({ locations }) => (
|
||||
export const LocationsList: FC<LocationsListProps> = ({ locations }) => (
|
||||
<div className="toolcall-locations-list flex flex-col gap-1 max-w-full">
|
||||
{locations.map((loc, idx) => (
|
||||
<FileLink key={idx} path={loc.path} line={loc.line} showFullPath={true} />
|
||||
|
|
|
|||
|
|
@ -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<CopyButtonProps> = ({ text }) => {
|
||||
export const CopyButton: FC<CopyButtonProps> = ({ text }) => {
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const platform = usePlatform();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<HTMLButtonElement> {
|
||||
/** 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<TooltipProps> = ({
|
||||
export const Tooltip: FC<TooltipProps> = ({
|
||||
children,
|
||||
content,
|
||||
position = 'top',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
254
packages/webui/src/styles/webview.css
Normal file
254
packages/webui/src/styles/webview.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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) */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue