From 71570540ccdfa03716d43ddc28a2471a8702b4b0 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Thu, 15 Jan 2026 19:53:19 +0800 Subject: [PATCH] feat(webui): migrate icons, Tooltip, WaitingMessage from vscode-ide-companion - Move icon components (FileIcons, EditIcons, NavigationIcons, StatusIcons, SpecialIcons, StopIcon) from vscode-ide-companion to webui package - Migrate Tooltip component with CSS variable theming support - Migrate WaitingMessage and InterruptedMessage components - Enhance Button component with forwardRef, new variants (ghost, outline), loading state, and icon support - Enhance Input component with forwardRef, error state, label, and helper text - Update vscode-ide-companion to import components from @qwen-code/webui - Remove replaced local components from vscode-ide-companion - Add skipLibCheck to vscode-ide-companion tsconfig for type compatibility --- .../vscode-ide-companion/src/webview/App.tsx | 5 +- .../src/webview/components/Tooltip.tsx | 61 ----- .../webview/components/layout/ChatHeader.tsx | 2 +- .../components/layout/ContextIndicator.tsx | 2 +- .../webview/components/layout/InputForm.tsx | 2 +- .../components/layout/SessionSelector.tsx | 2 +- .../messages/Waiting/WaitingMessage.css | 38 ---- .../src/webview/components/messages/index.tsx | 3 +- packages/vscode-ide-companion/tsconfig.json | 3 +- packages/webui/.storybook/main.ts | 6 + packages/webui/.storybook/preview.ts | 6 + packages/webui/README.md | 210 ++++++++++++++++++ packages/webui/example/ExampleComponent.tsx | 6 + packages/webui/package.json | 5 + packages/webui/scripts/add-license-header.sh | 48 ++++ .../webui/src/components/PermissionDrawer.tsx | 6 + .../webui/src/components/icons/CloseIcon.tsx | 36 +-- .../src}/components/icons/EditIcons.tsx | 190 ++++++++++++++++ .../src}/components/icons/FileIcons.tsx | 0 packages/webui/src/components/icons/Icon.tsx | 45 ++-- .../src}/components/icons/NavigationIcons.tsx | 0 .../webui/src/components/icons/SendIcon.tsx | 36 +-- .../src}/components/icons/SpecialIcons.tsx | 0 .../src}/components/icons/StatusIcons.tsx | 0 .../src}/components/icons/StopIcon.tsx | 0 .../src}/components/icons/index.ts | 10 +- .../src}/components/icons/types.ts | 0 .../webui/src/components/layout/Container.tsx | 10 +- .../webui/src/components/layout/Footer.tsx | 6 + .../webui/src/components/layout/Header.tsx | 6 + packages/webui/src/components/layout/Main.tsx | 6 + .../webui/src/components/layout/Sidebar.tsx | 6 + .../webui/src/components/messages/Message.tsx | 6 + .../src/components/messages/MessageInput.tsx | 10 +- .../src/components/messages/MessageList.tsx | 10 +- .../messages/Waiting/InterruptedMessage.tsx | 0 .../messages/Waiting/WaitingMessage.tsx | 14 +- packages/webui/src/components/ui/Button.tsx | 172 ++++++++++---- packages/webui/src/components/ui/Input.tsx | 164 ++++++++++++-- packages/webui/src/components/ui/Tooltip.tsx | 140 +++++------- packages/webui/src/hooks/useLocalStorage.ts | 6 + packages/webui/src/hooks/useTheme.ts | 6 + packages/webui/src/index.ts | 61 ++++- packages/webui/src/types/messages.ts | 6 + packages/webui/src/types/theme.ts | 6 + 45 files changed, 1049 insertions(+), 308 deletions(-) delete mode 100644 packages/vscode-ide-companion/src/webview/components/Tooltip.tsx delete mode 100644 packages/vscode-ide-companion/src/webview/components/messages/Waiting/WaitingMessage.css create mode 100644 packages/webui/README.md create mode 100755 packages/webui/scripts/add-license-header.sh rename packages/{vscode-ide-companion/src/webview => webui/src}/components/icons/EditIcons.tsx (52%) rename packages/{vscode-ide-companion/src/webview => webui/src}/components/icons/FileIcons.tsx (100%) rename packages/{vscode-ide-companion/src/webview => webui/src}/components/icons/NavigationIcons.tsx (100%) rename packages/{vscode-ide-companion/src/webview => webui/src}/components/icons/SpecialIcons.tsx (100%) rename packages/{vscode-ide-companion/src/webview => webui/src}/components/icons/StatusIcons.tsx (100%) rename packages/{vscode-ide-companion/src/webview => webui/src}/components/icons/StopIcon.tsx (100%) rename packages/{vscode-ide-companion/src/webview => webui/src}/components/icons/index.ts (87%) rename packages/{vscode-ide-companion/src/webview => webui/src}/components/icons/types.ts (100%) rename packages/{vscode-ide-companion/src/webview => webui/src}/components/messages/Waiting/InterruptedMessage.tsx (100%) rename packages/{vscode-ide-companion/src/webview => webui/src}/components/messages/Waiting/WaitingMessage.tsx (90%) diff --git a/packages/vscode-ide-companion/src/webview/App.tsx b/packages/vscode-ide-companion/src/webview/App.tsx index 4286cd44e..fd0a51e8f 100644 --- a/packages/vscode-ide-companion/src/webview/App.tsx +++ b/packages/vscode-ide-companion/src/webview/App.tsx @@ -37,12 +37,11 @@ import { UserMessage, AssistantMessage, ThinkingMessage, - WaitingMessage, - InterruptedMessage, } from './components/messages/index.js'; +import { WaitingMessage, InterruptedMessage } from '@qwen-code/webui'; import { InputForm } from './components/layout/InputForm.js'; import { SessionSelector } from './components/layout/SessionSelector.js'; -import { FileIcon, UserIcon } from './components/icons/index.js'; +import { FileIcon, UserIcon } from '@qwen-code/webui'; import { ApprovalMode, NEXT_APPROVAL_MODE } from '../types/acpTypes.js'; import type { ApprovalModeValue } from '../types/approvalModeValueTypes.js'; import type { PlanEntry, UsageStatsPayload } from '../types/chatTypes.js'; diff --git a/packages/vscode-ide-companion/src/webview/components/Tooltip.tsx b/packages/vscode-ide-companion/src/webview/components/Tooltip.tsx deleted file mode 100644 index 1ee10c000..000000000 --- a/packages/vscode-ide-companion/src/webview/components/Tooltip.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -import type React from 'react'; - -interface TooltipProps { - children: React.ReactNode; - content: React.ReactNode; - position?: 'top' | 'bottom' | 'left' | 'right'; -} - -export const Tooltip: React.FC = ({ - children, - content, - position = 'top', -}) => ( -
-
- {children} -
- {content} -
-
-
-
-); diff --git a/packages/vscode-ide-companion/src/webview/components/layout/ChatHeader.tsx b/packages/vscode-ide-companion/src/webview/components/layout/ChatHeader.tsx index 82cc905fb..b6b37e2cb 100644 --- a/packages/vscode-ide-companion/src/webview/components/layout/ChatHeader.tsx +++ b/packages/vscode-ide-companion/src/webview/components/layout/ChatHeader.tsx @@ -5,7 +5,7 @@ */ import type React from 'react'; -import { ChevronDownIcon, PlusIcon } from '../icons/index.js'; +import { ChevronDownIcon, PlusIcon } from '@qwen-code/webui'; interface ChatHeaderProps { currentSessionTitle: string; diff --git a/packages/vscode-ide-companion/src/webview/components/layout/ContextIndicator.tsx b/packages/vscode-ide-companion/src/webview/components/layout/ContextIndicator.tsx index b7f476c36..ce3dddfa4 100644 --- a/packages/vscode-ide-companion/src/webview/components/layout/ContextIndicator.tsx +++ b/packages/vscode-ide-companion/src/webview/components/layout/ContextIndicator.tsx @@ -5,7 +5,7 @@ */ import type React from 'react'; -import { Tooltip } from '../Tooltip.js'; +import { Tooltip } from '@qwen-code/webui'; interface ContextUsage { percentLeft: number; 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 2058b7c04..6fd6ef61d 100644 --- a/packages/vscode-ide-companion/src/webview/components/layout/InputForm.tsx +++ b/packages/vscode-ide-companion/src/webview/components/layout/InputForm.tsx @@ -16,7 +16,7 @@ import { LinkIcon, ArrowUpIcon, StopIcon, -} from '../icons/index.js'; +} from '@qwen-code/webui'; import { CompletionMenu } from '../layout/CompletionMenu.js'; import type { CompletionItem } from '../../../types/completionItemTypes.js'; import { getApprovalModeInfoFromString } from '../../../types/acpTypes.js'; diff --git a/packages/vscode-ide-companion/src/webview/components/layout/SessionSelector.tsx b/packages/vscode-ide-companion/src/webview/components/layout/SessionSelector.tsx index 1b744c1d2..a751410bc 100644 --- a/packages/vscode-ide-companion/src/webview/components/layout/SessionSelector.tsx +++ b/packages/vscode-ide-companion/src/webview/components/layout/SessionSelector.tsx @@ -9,7 +9,7 @@ import { getTimeAgo, groupSessionsByDate, } from '../../utils/sessionGrouping.js'; -import { SearchIcon } from '../icons/index.js'; +import { SearchIcon } from '@qwen-code/webui'; interface SessionSelectorProps { visible: boolean; diff --git a/packages/vscode-ide-companion/src/webview/components/messages/Waiting/WaitingMessage.css b/packages/vscode-ide-companion/src/webview/components/messages/Waiting/WaitingMessage.css deleted file mode 100644 index 9a109a082..000000000 --- a/packages/vscode-ide-companion/src/webview/components/messages/Waiting/WaitingMessage.css +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -@import url('../Assistant/AssistantMessage.css'); - -/* Subtle shimmering highlight across the loading text */ -@keyframes waitingMessageShimmer { - 0% { - background-position: -200% 0; - } - 100% { - background-position: 200% 0; - } -} - -.loading-text-shimmer { - /* Use the theme foreground as the base color, with a moving light band */ - background-image: linear-gradient( - 90deg, - var(--app-secondary-foreground) 0%, - var(--app-secondary-foreground) 40%, - rgba(255, 255, 255, 0.95) 50%, - var(--app-secondary-foreground) 60%, - var(--app-secondary-foreground) 100% - ); - background-size: 200% 100%; - -webkit-background-clip: text; - background-clip: text; - color: transparent; /* text color comes from the gradient */ - animation: waitingMessageShimmer 1.6s linear infinite; -} - -.interrupted-item::after { - display: none; -} diff --git a/packages/vscode-ide-companion/src/webview/components/messages/index.tsx b/packages/vscode-ide-companion/src/webview/components/messages/index.tsx index 2ec06e87e..1cf19e26c 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/index.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/index.tsx @@ -7,5 +7,4 @@ export { UserMessage } from './UserMessage.js'; export { AssistantMessage } from './Assistant/AssistantMessage.js'; export { ThinkingMessage } from './ThinkingMessage.js'; -export { WaitingMessage } from './Waiting/WaitingMessage.js'; -export { InterruptedMessage } from './Waiting/InterruptedMessage.js'; +// WaitingMessage and InterruptedMessage are now imported from @qwen-code/webui diff --git a/packages/vscode-ide-companion/tsconfig.json b/packages/vscode-ide-companion/tsconfig.json index 538ec461f..886ddb4b6 100644 --- a/packages/vscode-ide-companion/tsconfig.json +++ b/packages/vscode-ide-companion/tsconfig.json @@ -7,7 +7,8 @@ "jsx": "react-jsx", "jsxImportSource": "react", "sourceMap": true, - "strict": true /* enable all strict type-checking options */ + "strict": true, + "skipLibCheck": true /* Additional Checks */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ diff --git a/packages/webui/.storybook/main.ts b/packages/webui/.storybook/main.ts index b76cf892f..69c9da990 100644 --- a/packages/webui/.storybook/main.ts +++ b/packages/webui/.storybook/main.ts @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type { StorybookConfig } from '@storybook/react-vite'; import { dirname } from 'path'; diff --git a/packages/webui/.storybook/preview.ts b/packages/webui/.storybook/preview.ts index 13dd9ec87..3cc995c1f 100644 --- a/packages/webui/.storybook/preview.ts +++ b/packages/webui/.storybook/preview.ts @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type { Preview } from '@storybook/react-vite'; import './preview.css'; diff --git a/packages/webui/README.md b/packages/webui/README.md new file mode 100644 index 000000000..0558d6d4f --- /dev/null +++ b/packages/webui/README.md @@ -0,0 +1,210 @@ +# @anthropic/webui + +A shared React component library for Qwen Code applications, providing cross-platform UI components with consistent styling and behavior. + +## Features + +- **Cross-platform support**: Components work seamlessly across VS Code extension, web, and other platforms +- **Platform Context**: Abstraction layer for platform-specific capabilities +- **Tailwind CSS**: Shared styling preset for consistent design +- **TypeScript**: Full type definitions for all components +- **Storybook**: Interactive component documentation and development + +## Installation + +```bash +npm install @anthropic/webui +``` + +## Quick Start + +```tsx +import { Button, Input, Tooltip } from '@anthropic/webui'; +import { PlatformProvider } from '@anthropic/webui/context'; + +function App() { + return ( + + + + ); +} +``` + +## Components + +### UI Components + +#### Button + +```tsx +import { Button } from '@anthropic/webui'; + +; +``` + +**Props:** + +- `variant`: 'primary' | 'secondary' | 'danger' | 'ghost' | 'outline' +- `size`: 'sm' | 'md' | 'lg' +- `loading`: boolean +- `leftIcon`: ReactNode +- `rightIcon`: ReactNode +- `fullWidth`: boolean + +#### Input + +```tsx +import { Input } from '@anthropic/webui'; + +; +``` + +**Props:** + +- `size`: 'sm' | 'md' | 'lg' +- `error`: boolean +- `errorMessage`: string +- `label`: string +- `helperText`: string +- `leftElement`: ReactNode +- `rightElement`: ReactNode + +#### Tooltip + +```tsx +import { Tooltip } from '@anthropic/webui'; + + + Hover me +; +``` + +### Icons + +```tsx +import { FileIcon, FolderIcon, CheckIcon } from '@anthropic/webui/icons'; + +; +``` + +Available icon categories: + +- **FileIcons**: FileIcon, FolderIcon, SaveDocumentIcon +- **StatusIcons**: CheckIcon, ErrorIcon, WarningIcon, LoadingIcon +- **NavigationIcons**: ArrowLeftIcon, ArrowRightIcon, ChevronIcon +- **EditIcons**: EditIcon, DeleteIcon, CopyIcon +- **SpecialIcons**: SendIcon, StopIcon, CloseIcon + +### Layout Components + +- `Container`: Main layout wrapper +- `Header`: Application header +- `Footer`: Application footer +- `Sidebar`: Side navigation +- `Main`: Main content area + +### Message Components + +- `Message`: Chat message display +- `MessageList`: List of messages +- `MessageInput`: Message input field +- `WaitingMessage`: Loading/waiting state +- `InterruptedMessage`: Interrupted state display + +## Platform Context + +The Platform Context provides an abstraction layer for platform-specific capabilities: + +```tsx +import { PlatformProvider, usePlatform } from '@anthropic/webui/context'; + +const platformContext = { + postMessage: (message) => vscode.postMessage(message), + onMessage: (handler) => { + window.addEventListener('message', handler); + return () => window.removeEventListener('message', handler); + }, + openFile: (path) => { + /* platform-specific */ + }, + platform: 'vscode', +}; + +function App() { + return ( + + + + ); +} + +function Component() { + const { postMessage, platform } = usePlatform(); + // Use platform capabilities +} +``` + +## Tailwind Preset + +Use the shared Tailwind preset for consistent styling: + +```js +// tailwind.config.js +module.exports = { + presets: [require('@anthropic/webui/tailwind.preset.cjs')], + // your customizations +}; +``` + +## Development + +### Running Storybook + +```bash +cd packages/webui +npm run storybook +``` + +### Building + +```bash +npm run build +``` + +### Type Checking + +```bash +npm run typecheck +``` + +## Project Structure + +``` +packages/webui/ +├── src/ +│ ├── components/ +│ │ ├── icons/ # Icon components +│ │ ├── layout/ # Layout components +│ │ ├── messages/ # Message components +│ │ └── ui/ # UI primitives +│ ├── context/ # Platform context +│ ├── hooks/ # Custom hooks +│ └── types/ # Type definitions +├── .storybook/ # Storybook config +├── tailwind.preset.cjs # Shared Tailwind preset +└── vite.config.ts # Build configuration +``` + +## License + +Apache-2.0 diff --git a/packages/webui/example/ExampleComponent.tsx b/packages/webui/example/ExampleComponent.tsx index c29800049..c554f2b6a 100644 --- a/packages/webui/example/ExampleComponent.tsx +++ b/packages/webui/example/ExampleComponent.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + // Example of how to use shared UI components // This would typically be integrated into existing components diff --git a/packages/webui/package.json b/packages/webui/package.json index ffd2b956d..b2f5459ab 100644 --- a/packages/webui/package.json +++ b/packages/webui/package.json @@ -12,6 +12,11 @@ "import": "./dist/index.js", "require": "./dist/index.cjs" }, + "./icons": { + "types": "./dist/components/icons/index.d.ts", + "import": "./dist/components/icons/index.js", + "require": "./dist/components/icons/index.cjs" + }, "./tailwind.preset": "./tailwind.preset.cjs", "./styles.css": "./dist/styles.css" }, diff --git a/packages/webui/scripts/add-license-header.sh b/packages/webui/scripts/add-license-header.sh new file mode 100755 index 000000000..8fa424202 --- /dev/null +++ b/packages/webui/scripts/add-license-header.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Script to check and add license header to files in the packages/webui directory +# If a file doesn't have the required license header, it will be added at the top +# Excludes Markdown files and common build/dependency directories + +LICENSE_HEADER="/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */" + +# Directory to scan (relative to script location) +TARGET_DIR="$(dirname "$0")/../" + +# Find all JavaScript, TypeScript, CSS, HTML, and JSX/TSX files in the target directory, excluding Markdown files +# Also exclude common build/dependency directories +find "$TARGET_DIR" -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.cjs" -o -name "*.mjs" -o -name "*.css" -o -name "*.html" \) -not -name "*.md" \ + -not -path "*/node_modules/*" \ + -not -path "*/dist/*" \ + -not -path "*/build/*" \ + -not -path "*/coverage/*" \ + -not -path "*/.next/*" \ + -not -path "*/out/*" \ + -not -path "*/target/*" \ + -not -path "*/vendor/*" \ + -print0 | while IFS= read -r -d '' file; do + # Skip the script file itself + if [[ "$(basename "$file")" != "add-license-header.sh" ]]; then + # Check if the file starts with the license header + if ! head -n 5 "$file" | grep -Fq "@license"; then + echo "Adding license header to: $file" + + # Create a temporary file with the license header followed by the original content + temp_file=$(mktemp) + echo "$LICENSE_HEADER" > "$temp_file" + echo "" >> "$temp_file" # Add an empty line after the license header + cat "$file" >> "$temp_file" + + # Move the temporary file to replace the original file + mv "$temp_file" "$file" + else + echo "License header already present in: $file" + fi + fi +done + +echo "License header check and update completed." \ No newline at end of file diff --git a/packages/webui/src/components/PermissionDrawer.tsx b/packages/webui/src/components/PermissionDrawer.tsx index 2805915cb..e90fcde11 100644 --- a/packages/webui/src/components/PermissionDrawer.tsx +++ b/packages/webui/src/components/PermissionDrawer.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; import { useState, useEffect } from 'react'; diff --git a/packages/webui/src/components/icons/CloseIcon.tsx b/packages/webui/src/components/icons/CloseIcon.tsx index ac559d299..65d60b68a 100644 --- a/packages/webui/src/components/icons/CloseIcon.tsx +++ b/packages/webui/src/components/icons/CloseIcon.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; interface CloseIconProps { @@ -11,20 +17,20 @@ const CloseIcon: React.FC = ({ color = 'currentColor', className = '', }) => ( - - - - - ); + + + + +); export default CloseIcon; diff --git a/packages/vscode-ide-companion/src/webview/components/icons/EditIcons.tsx b/packages/webui/src/components/icons/EditIcons.tsx similarity index 52% rename from packages/vscode-ide-companion/src/webview/components/icons/EditIcons.tsx rename to packages/webui/src/components/icons/EditIcons.tsx index f5e12b330..ddec39d4a 100644 --- a/packages/vscode-ide-companion/src/webview/components/icons/EditIcons.tsx +++ b/packages/webui/src/components/icons/EditIcons.tsx @@ -213,3 +213,193 @@ 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 +}) => ( + +); + +/** + * Redo edit icon (16x16) + * Used for redoing edits in diff views + */ +export const RedoIcon: React.FC = ({ + size = 16, + className, + ...props +}) => ( + +); + +/** + * Replace all icon (16x16) + * Used for replacing all occurrences in search/replace + */ +export const ReplaceAllIcon: React.FC = ({ + size = 16, + className, + ...props +}) => ( + +); + +/** + * Copy icon (16x16) + * Used for copying content + */ +export const CopyIcon: React.FC = ({ + size = 16, + className, + ...props +}) => ( + +); + +/** + * Paste icon (16x16) + * Used for pasting content + */ +export const PasteIcon: React.FC = ({ + size = 16, + className, + ...props +}) => ( + +); + +/** + * Select all icon (16x16) + * Used for selecting all content + */ +export const SelectAllIcon: React.FC = ({ + size = 16, + className, + ...props +}) => ( + +); diff --git a/packages/vscode-ide-companion/src/webview/components/icons/FileIcons.tsx b/packages/webui/src/components/icons/FileIcons.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/icons/FileIcons.tsx rename to packages/webui/src/components/icons/FileIcons.tsx diff --git a/packages/webui/src/components/icons/Icon.tsx b/packages/webui/src/components/icons/Icon.tsx index 2f874d594..afa888cbb 100644 --- a/packages/webui/src/components/icons/Icon.tsx +++ b/packages/webui/src/components/icons/Icon.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; interface IconProps { @@ -12,27 +18,24 @@ const Icon: React.FC = ({ size = 24, color = 'currentColor', className = '', -}) => +}) => ( // This is a placeholder - in a real implementation you might use an icon library - ( - + - - {name} - - - ) -; - + {name} + + +); export default Icon; diff --git a/packages/vscode-ide-companion/src/webview/components/icons/NavigationIcons.tsx b/packages/webui/src/components/icons/NavigationIcons.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/icons/NavigationIcons.tsx rename to packages/webui/src/components/icons/NavigationIcons.tsx diff --git a/packages/webui/src/components/icons/SendIcon.tsx b/packages/webui/src/components/icons/SendIcon.tsx index d2561e033..f0c657ecc 100644 --- a/packages/webui/src/components/icons/SendIcon.tsx +++ b/packages/webui/src/components/icons/SendIcon.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; interface SendIconProps { @@ -11,20 +17,20 @@ const SendIcon: React.FC = ({ color = 'currentColor', className = '', }) => ( - - - - - ); + + + + +); export default SendIcon; diff --git a/packages/vscode-ide-companion/src/webview/components/icons/SpecialIcons.tsx b/packages/webui/src/components/icons/SpecialIcons.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/icons/SpecialIcons.tsx rename to packages/webui/src/components/icons/SpecialIcons.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/icons/StatusIcons.tsx b/packages/webui/src/components/icons/StatusIcons.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/icons/StatusIcons.tsx rename to packages/webui/src/components/icons/StatusIcons.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/icons/StopIcon.tsx b/packages/webui/src/components/icons/StopIcon.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/icons/StopIcon.tsx rename to packages/webui/src/components/icons/StopIcon.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/icons/index.ts b/packages/webui/src/components/icons/index.ts similarity index 87% rename from packages/vscode-ide-companion/src/webview/components/icons/index.ts rename to packages/webui/src/components/icons/index.ts index ffecbbced..5b48a6125 100644 --- a/packages/vscode-ide-companion/src/webview/components/icons/index.ts +++ b/packages/webui/src/components/icons/index.ts @@ -5,7 +5,14 @@ */ export type { IconProps } from './types.js'; -export { FileIcon, FileListIcon, FolderIcon } from './FileIcons.js'; + +// File icons +export { + FileIcon, + FileListIcon, + SaveDocumentIcon, + FolderIcon, +} from './FileIcons.js'; // Navigation icons export { @@ -29,6 +36,7 @@ export { SlashCommandIcon, LinkIcon, OpenDiffIcon, + UndoIcon, } from './EditIcons.js'; // Status icons diff --git a/packages/vscode-ide-companion/src/webview/components/icons/types.ts b/packages/webui/src/components/icons/types.ts similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/icons/types.ts rename to packages/webui/src/components/icons/types.ts diff --git a/packages/webui/src/components/layout/Container.tsx b/packages/webui/src/components/layout/Container.tsx index 436d4c72e..37dc27bca 100644 --- a/packages/webui/src/components/layout/Container.tsx +++ b/packages/webui/src/components/layout/Container.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; interface ContainerProps { @@ -6,7 +12,7 @@ interface ContainerProps { } const Container: React.FC = ({ children, className = '' }) => ( -
{children}
- ); +
{children}
+); export default Container; diff --git a/packages/webui/src/components/layout/Footer.tsx b/packages/webui/src/components/layout/Footer.tsx index 557a083b1..6a4f162f5 100644 --- a/packages/webui/src/components/layout/Footer.tsx +++ b/packages/webui/src/components/layout/Footer.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; const Footer: React.FC = () =>
Footer Component Placeholder
; diff --git a/packages/webui/src/components/layout/Header.tsx b/packages/webui/src/components/layout/Header.tsx index 2886c0dfb..1f7fe373c 100644 --- a/packages/webui/src/components/layout/Header.tsx +++ b/packages/webui/src/components/layout/Header.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; const Header: React.FC = () =>
Header Component Placeholder
; diff --git a/packages/webui/src/components/layout/Main.tsx b/packages/webui/src/components/layout/Main.tsx index 118f67430..b046a1827 100644 --- a/packages/webui/src/components/layout/Main.tsx +++ b/packages/webui/src/components/layout/Main.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; const Main: React.FC = () =>
Main Component Placeholder
; diff --git a/packages/webui/src/components/layout/Sidebar.tsx b/packages/webui/src/components/layout/Sidebar.tsx index eb3746c20..d8835427a 100644 --- a/packages/webui/src/components/layout/Sidebar.tsx +++ b/packages/webui/src/components/layout/Sidebar.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; const Sidebar: React.FC = () => ; diff --git a/packages/webui/src/components/messages/Message.tsx b/packages/webui/src/components/messages/Message.tsx index e09208528..cce7b53ce 100644 --- a/packages/webui/src/components/messages/Message.tsx +++ b/packages/webui/src/components/messages/Message.tsx @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; interface MessageProps { diff --git a/packages/webui/src/components/messages/MessageInput.tsx b/packages/webui/src/components/messages/MessageInput.tsx index bd4637155..281fd5b4e 100644 --- a/packages/webui/src/components/messages/MessageInput.tsx +++ b/packages/webui/src/components/messages/MessageInput.tsx @@ -1,5 +1,13 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; -const MessageInput: React.FC = () =>
MessageInput Component Placeholder
; +const MessageInput: React.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 5544865a0..5a322929c 100644 --- a/packages/webui/src/components/messages/MessageList.tsx +++ b/packages/webui/src/components/messages/MessageList.tsx @@ -1,5 +1,13 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import type React from 'react'; -const MessageList: React.FC = () =>
MessageList Component Placeholder
; +const MessageList: React.FC = () => ( +
MessageList Component Placeholder
+); export default MessageList; diff --git a/packages/vscode-ide-companion/src/webview/components/messages/Waiting/InterruptedMessage.tsx b/packages/webui/src/components/messages/Waiting/InterruptedMessage.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/messages/Waiting/InterruptedMessage.tsx rename to packages/webui/src/components/messages/Waiting/InterruptedMessage.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/messages/Waiting/WaitingMessage.tsx b/packages/webui/src/components/messages/Waiting/WaitingMessage.tsx similarity index 90% rename from packages/vscode-ide-companion/src/webview/components/messages/Waiting/WaitingMessage.tsx rename to packages/webui/src/components/messages/Waiting/WaitingMessage.tsx index 68aceac8f..95b728bf0 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/Waiting/WaitingMessage.tsx +++ b/packages/webui/src/components/messages/Waiting/WaitingMessage.tsx @@ -6,8 +6,6 @@ import type React from 'react'; import { useEffect, useMemo, useState } from 'react'; -import './WaitingMessage.css'; -import { WITTY_LOADING_PHRASES } from '../../../../constants/loadingMessages.js'; interface WaitingMessageProps { loadingMessage: string; @@ -16,6 +14,16 @@ interface WaitingMessageProps { // Rotate message every few seconds while waiting const ROTATE_INTERVAL_MS = 3000; // rotate every 3s per request +// Default witty loading phrases +const DEFAULT_LOADING_PHRASES = [ + 'Processing...', + 'Working on it...', + 'Just a moment...', + 'Loading...', + 'Hold tight...', + 'Almost there...', +]; + export const WaitingMessage: React.FC = ({ loadingMessage, }) => { @@ -27,7 +35,7 @@ export const WaitingMessage: React.FC = ({ list.push(loadingMessage); set.add(loadingMessage); } - for (const p of WITTY_LOADING_PHRASES) { + for (const p of DEFAULT_LOADING_PHRASES) { if (!set.has(p)) { list.push(p); } diff --git a/packages/webui/src/components/ui/Button.tsx b/packages/webui/src/components/ui/Button.tsx index 1ee0d3b07..b86a8dcef 100644 --- a/packages/webui/src/components/ui/Button.tsx +++ b/packages/webui/src/components/ui/Button.tsx @@ -1,49 +1,143 @@ -import type React from 'react'; +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ -interface ButtonProps { +import type React from 'react'; +import { forwardRef } from 'react'; + +/** + * Button variant types + */ +export type ButtonVariant = + | 'primary' + | 'secondary' + | 'danger' + | 'ghost' + | 'outline'; + +/** + * Button size types + */ +export type ButtonSize = 'sm' | 'md' | 'lg'; + +/** + * Button component props interface + */ +export interface ButtonProps + extends React.ButtonHTMLAttributes { + /** Button content */ children: React.ReactNode; - onClick?: () => void; - variant?: 'primary' | 'secondary' | 'danger'; - size?: 'sm' | 'md' | 'lg'; - disabled?: boolean; - className?: string; + /** Visual style variant */ + variant?: ButtonVariant; + /** Button size */ + size?: ButtonSize; + /** Loading state - shows spinner and disables button */ + loading?: boolean; + /** Icon to display before children */ + leftIcon?: React.ReactNode; + /** Icon to display after children */ + rightIcon?: React.ReactNode; + /** Full width button */ + fullWidth?: boolean; } -const Button: React.FC = ({ - children, - onClick, - variant = 'primary', - size = 'md', - disabled = false, - className = '', -}) => { - const baseClasses = - 'inline-flex items-center justify-center rounded font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2'; +/** + * Button component with multiple variants and sizes + * + * @example + * ```tsx + * + * ``` + */ +const Button = forwardRef( + ( + { + children, + variant = 'primary', + size = 'md', + disabled = false, + loading = false, + leftIcon, + rightIcon, + fullWidth = false, + className = '', + type = 'button', + ...props + }, + ref, + ) => { + const isDisabled = disabled || loading; - const variantClasses = { - primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500', - secondary: - 'bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500', - danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500', - }; + const baseClasses = + 'inline-flex items-center justify-center rounded font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2'; - const sizeClasses = { - sm: 'px-2 py-1 text-sm', - md: 'px-4 py-2', - lg: 'px-6 py-3 text-lg', - }; + const variantClasses: Record = { + primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500', + secondary: + 'bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500', + danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500', + ghost: + 'bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-gray-400', + outline: + 'bg-transparent border border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-gray-400', + }; - const disabledClass = disabled ? 'opacity-50 cursor-not-allowed' : ''; + const sizeClasses: Record = { + sm: 'px-2 py-1 text-sm gap-1', + md: 'px-4 py-2 gap-2', + lg: 'px-6 py-3 text-lg gap-2', + }; - return ( - - ); -}; + const disabledClass = isDisabled + ? 'opacity-50 cursor-not-allowed pointer-events-none' + : ''; + const widthClass = fullWidth ? 'w-full' : ''; + + return ( + + ); + }, +); + +Button.displayName = 'Button'; export default Button; diff --git a/packages/webui/src/components/ui/Input.tsx b/packages/webui/src/components/ui/Input.tsx index 8fd350c67..8ac2c8395 100644 --- a/packages/webui/src/components/ui/Input.tsx +++ b/packages/webui/src/components/ui/Input.tsx @@ -1,25 +1,149 @@ -import type React from 'react'; +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ -interface InputProps { - value: string; - onChange: (value: string) => void; - placeholder?: string; - className?: string; +import type React from 'react'; +import { forwardRef } from 'react'; + +/** + * Input size types + */ +export type InputSize = 'sm' | 'md' | 'lg'; + +/** + * Input component props interface + */ +export interface InputProps + extends Omit, 'size'> { + /** Input size */ + size?: InputSize; + /** Error state */ + error?: boolean; + /** Error message to display */ + errorMessage?: string; + /** Label for the input */ + label?: string; + /** Helper text below input */ + helperText?: string; + /** Left icon/element */ + leftElement?: React.ReactNode; + /** Right icon/element */ + rightElement?: React.ReactNode; + /** Full width input */ + fullWidth?: boolean; } -const Input: React.FC = ({ - value, - onChange, - placeholder, - className = '', -}) => ( - onChange(e.target.value)} - placeholder={placeholder} - className={`border rounded px-3 py-2 ${className}`} - /> - ); +/** + * Input component with multiple sizes and states + * + * @example + * ```tsx + * + * ``` + */ +const Input = forwardRef( + ( + { + size = 'md', + error = false, + errorMessage, + label, + helperText, + leftElement, + rightElement, + fullWidth = false, + className = '', + id, + disabled, + ...props + }, + ref, + ) => { + const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}`; + + const baseClasses = + 'border rounded transition-colors focus:outline-none focus:ring-2'; + + const sizeClasses: Record = { + sm: 'px-2 py-1 text-sm', + md: 'px-3 py-2', + lg: 'px-4 py-3 text-lg', + }; + + const stateClasses = error + ? 'border-red-500 focus:ring-red-500 focus:border-red-500' + : 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'; + + const disabledClasses = disabled + ? 'bg-gray-100 cursor-not-allowed opacity-60' + : 'bg-white'; + + const widthClass = fullWidth ? 'w-full' : ''; + + const paddingClasses = [ + leftElement ? 'pl-10' : '', + rightElement ? 'pr-10' : '', + ].join(' '); + + return ( +
+ {label && ( + + )} +
+ {leftElement && ( +
+ {leftElement} +
+ )} + + {rightElement && ( +
+ {rightElement} +
+ )} +
+ {errorMessage && error && ( +

+ {errorMessage} +

+ )} + {helperText && !error && ( +

+ {helperText} +

+ )} +
+ ); + }, +); + +Input.displayName = 'Input'; export default Input; diff --git a/packages/webui/src/components/ui/Tooltip.tsx b/packages/webui/src/components/ui/Tooltip.tsx index 6b9206af9..05846d637 100644 --- a/packages/webui/src/components/ui/Tooltip.tsx +++ b/packages/webui/src/components/ui/Tooltip.tsx @@ -1,93 +1,73 @@ -import React, { useState } from 'react'; +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ -interface ChildProps { - onMouseEnter?: () => void; - onMouseLeave?: () => void; - onFocus?: () => void; - onBlur?: () => void; - tabIndex?: number; +import type React from 'react'; + +/** + * Tooltip component props + */ +export interface TooltipProps { + /** Content to wrap with tooltip */ + children: React.ReactNode; + /** Tooltip content (can be string or ReactNode) */ + content: React.ReactNode; + /** Tooltip position relative to children */ + position?: 'top' | 'bottom' | 'left' | 'right'; } -interface TooltipProps { - children: React.ReactElement; - content: string; - position?: 'top' | 'right' | 'bottom' | 'left'; -} - -const Tooltip: React.FC = ({ +/** + * Tooltip component using CSS group-hover for display + * Supports CSS variables for theming + */ +export const Tooltip: React.FC = ({ children, content, position = 'top', -}) => { - const [isVisible, setIsVisible] = useState(false); - - const positionClasses = { - top: 'bottom-full left-1/2 transform -translate-x-1/2 mb-2', - right: 'top-1/2 left-full transform -translate-y-1/2 ml-2', - bottom: 'top-full left-1/2 transform -translate-x-1/2 mt-2', - left: 'top-1/2 right-full transform -translate-y-1/2 mr-2', - }; - - const arrowPositionClasses = { - top: 'top-full left-1/2 transform -translate-x-1/2 -mt-1', - right: 'top-1/2 left-0 transform -translate-y-1/2 -ml-1', - bottom: 'top-0 left-1/2 transform -translate-x-1/2 -mb-1', - left: 'top-1/2 right-0 transform -translate-y-1/2 -mr-1', - }; - - const tooltipClass = `absolute ${positionClasses[position]} bg-gray-800 text-white text-xs rounded py-1 px-2 pointer-events-none z-10`; - const arrowClass = `absolute w-2 h-2 bg-gray-800 transform rotate-45 ${arrowPositionClasses[position]}`; - - return ( -
+}) => ( +
+
+ {children}
setIsVisible(true)} - onMouseLeave={() => setIsVisible(false)} - onFocus={() => setIsVisible(true)} - onBlur={() => setIsVisible(false)} - tabIndex={0} + className={` + absolute z-50 px-2 py-1 text-xs rounded-md shadow-lg + bg-[var(--app-primary-background,#1f2937)] border border-[var(--app-input-border,#374151)] + text-[var(--app-primary-foreground,#f9fafb)] whitespace-nowrap + opacity-0 group-hover:opacity-100 transition-opacity duration-150 + -translate-x-1/2 left-1/2 + ${ + position === 'top' + ? '-translate-y-1 bottom-full mb-1' + : position === 'bottom' + ? 'translate-y-1 top-full mt-1' + : position === 'left' + ? '-translate-x-full left-0 translate-y-[-50%] top-1/2' + : 'translate-x-0 right-0 translate-y-[-50%] top-1/2' + } + pointer-events-none + `} > - {React.cloneElement(children, { - onMouseEnter: () => { - setIsVisible(true); - const typedChildren = children as React.ReactElement; - if (typeof typedChildren.props.onMouseEnter === 'function') { - typedChildren.props.onMouseEnter(); + {content} +
{ - setIsVisible(false); - const typedChildren = children as React.ReactElement; - if (typeof typedChildren.props.onMouseLeave === 'function') { - typedChildren.props.onMouseLeave(); - } - }, - onFocus: () => { - setIsVisible(true); - const typedChildren = children as React.ReactElement; - if (typeof typedChildren.props.onFocus === 'function') { - typedChildren.props.onFocus(); - } - }, - onBlur: () => { - setIsVisible(false); - const typedChildren = children as React.ReactElement; - if (typeof typedChildren.props.onBlur === 'function') { - typedChildren.props.onBlur(); - } - }, - tabIndex: - (children as React.ReactElement).props.tabIndex || 0, - })} + `} + />
- {isVisible && ( -
- {content} -
-
- )}
- ); -}; +
+); export default Tooltip; diff --git a/packages/webui/src/hooks/useLocalStorage.ts b/packages/webui/src/hooks/useLocalStorage.ts index 7fb675411..b0bbdcad9 100644 --- a/packages/webui/src/hooks/useLocalStorage.ts +++ b/packages/webui/src/hooks/useLocalStorage.ts @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import { useState } from 'react'; export const useLocalStorage = (key: string, initialValue: T) => { diff --git a/packages/webui/src/hooks/useTheme.ts b/packages/webui/src/hooks/useTheme.ts index c4ca0a77a..95fc8202e 100644 --- a/packages/webui/src/hooks/useTheme.ts +++ b/packages/webui/src/hooks/useTheme.ts @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + import { useState, useEffect } from 'react'; export const useTheme = () => { diff --git a/packages/webui/src/index.ts b/packages/webui/src/index.ts index 4be114226..5bfaf89cd 100644 --- a/packages/webui/src/index.ts +++ b/packages/webui/src/index.ts @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + // Shared UI Components Export // Export all shared components from this package @@ -24,11 +30,14 @@ export { default as Footer } from './components/layout/Footer'; export { default as Message } from './components/messages/Message'; export { default as MessageInput } from './components/messages/MessageInput'; export { default as MessageList } from './components/messages/MessageList'; +export { WaitingMessage } from './components/messages/Waiting/WaitingMessage'; +export { InterruptedMessage } from './components/messages/Waiting/InterruptedMessage'; // UI Elements export { default as Button } from './components/ui/Button'; export { default as Input } from './components/ui/Input'; -export { default as Tooltip } from './components/ui/Tooltip'; +export { Tooltip } from './components/ui/Tooltip'; +export type { TooltipProps } from './components/ui/Tooltip'; // Permission components export { default as PermissionDrawer } from './components/PermissionDrawer'; @@ -38,6 +47,56 @@ export { default as Icon } from './components/icons/Icon'; export { default as CloseIcon } from './components/icons/CloseIcon'; export { default as SendIcon } from './components/icons/SendIcon'; +// File Icons +export { + FileIcon, + FileListIcon, + SaveDocumentIcon, + FolderIcon, +} from './components/icons/FileIcons'; + +// Status Icons +export { + PlanCompletedIcon, + PlanInProgressIcon, + PlanPendingIcon, + WarningTriangleIcon, + UserIcon, + SymbolIcon, + SelectionIcon, +} from './components/icons/StatusIcons'; + +// Navigation Icons +export { + ChevronDownIcon, + PlusIcon, + PlusSmallIcon, + ArrowUpIcon, + CloseIcon as CloseXIcon, + CloseSmallIcon, + SearchIcon, + RefreshIcon, +} from './components/icons/NavigationIcons'; + +// Edit Icons +export { + EditPencilIcon, + AutoEditIcon, + PlanModeIcon, + CodeBracketsIcon, + HideContextIcon, + SlashCommandIcon, + LinkIcon, + OpenDiffIcon, + UndoIcon, +} from './components/icons/EditIcons'; + +// Special Icons +export { ThinkingIcon, TerminalIcon } from './components/icons/SpecialIcons'; + +// Action Icons +export { StopIcon } from './components/icons/StopIcon'; + // Hooks export { useTheme } from './hooks/useTheme'; export { useLocalStorage } from './hooks/useLocalStorage'; diff --git a/packages/webui/src/types/messages.ts b/packages/webui/src/types/messages.ts index 9556b6176..269eb1c2e 100644 --- a/packages/webui/src/types/messages.ts +++ b/packages/webui/src/types/messages.ts @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + export interface MessageProps { id: string; content: string; diff --git a/packages/webui/src/types/theme.ts b/packages/webui/src/types/theme.ts index 3418c16f4..116199991 100644 --- a/packages/webui/src/types/theme.ts +++ b/packages/webui/src/types/theme.ts @@ -1 +1,7 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + export type Theme = 'light' | 'dark' | 'auto';