feat(webui): Infrastructure Setup (Prerequisites)

This commit is contained in:
yiliang114 2026-01-15 14:32:21 +08:00
parent ec0586b135
commit af76450dee
23 changed files with 4367 additions and 374 deletions

3
.gitignore vendored
View file

@ -63,3 +63,6 @@ patch_output.log
docs-site/.next docs-site/.next
# content is a symlink to ../docs # content is a symlink to ../docs
docs-site/content docs-site/content
*storybook.log
storybook-static

View file

@ -1,3 +1,6 @@
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
import storybook from "eslint-plugin-storybook";
/** /**
* @license * @license
* Copyright 2025 Google LLC * Copyright 2025 Google LLC
@ -13,8 +16,7 @@ import importPlugin from 'eslint-plugin-import';
import vitest from '@vitest/eslint-plugin'; import vitest from '@vitest/eslint-plugin';
import globals from 'globals'; import globals from 'globals';
export default tseslint.config( export default tseslint.config({
{
// Global ignores // Global ignores
ignores: [ ignores: [
'node_modules/*', 'node_modules/*',
@ -27,21 +29,15 @@ export default tseslint.config(
'docs-site/.next/**', 'docs-site/.next/**',
'docs-site/out/**', 'docs-site/out/**',
], ],
}, }, eslint.configs.recommended, ...tseslint.configs.recommended, reactHooks.configs['recommended-latest'], reactPlugin.configs.flat.recommended, // Add this if you are using React 17+
eslint.configs.recommended, reactPlugin.configs.flat['jsx-runtime'], {
...tseslint.configs.recommended,
reactHooks.configs['recommended-latest'],
reactPlugin.configs.flat.recommended,
reactPlugin.configs.flat['jsx-runtime'], // Add this if you are using React 17+
{
// Settings for eslint-plugin-react // Settings for eslint-plugin-react
settings: { settings: {
react: { react: {
version: 'detect', version: 'detect',
}, },
}, },
}, }, {
{
// Import specific config // Import specific config
files: ['packages/cli/src/**/*.{ts,tsx}'], // Target only TS/TSX in the cli package files: ['packages/cli/src/**/*.{ts,tsx}'], // Target only TS/TSX in the cli package
plugins: { plugins: {
@ -58,8 +54,7 @@ export default tseslint.config(
'import/no-default-export': 'warn', 'import/no-default-export': 'warn',
'import/no-unresolved': 'off', // Disable for now, can be noisy with monorepos/paths 'import/no-unresolved': 'off', // Disable for now, can be noisy with monorepos/paths
}, },
}, }, {
{
// General overrides and rules for the project (TS/TSX files) // General overrides and rules for the project (TS/TSX files)
files: ['packages/*/src/**/*.{ts,tsx}'], // Target only TS/TSX in the cli package files: ['packages/*/src/**/*.{ts,tsx}'], // Target only TS/TSX in the cli package
plugins: { plugins: {
@ -157,8 +152,7 @@ export default tseslint.config(
radix: 'error', radix: 'error',
'default-case': 'error', 'default-case': 'error',
}, },
}, }, {
{
files: ['packages/*/src/**/*.test.{ts,tsx}', 'packages/**/test/**/*.test.{ts,tsx}'], files: ['packages/*/src/**/*.test.{ts,tsx}', 'packages/**/test/**/*.test.{ts,tsx}'],
plugins: { plugins: {
vitest, vitest,
@ -176,8 +170,7 @@ export default tseslint.config(
}, },
], ],
}, },
}, }, // extra settings for scripts that we run directly with node
// extra settings for scripts that we run directly with node
{ {
files: ['./scripts/**/*.js', 'esbuild.config.js', 'packages/*/scripts/**/*.js'], files: ['./scripts/**/*.js', 'esbuild.config.js', 'packages/*/scripts/**/*.js'],
languageOptions: { languageOptions: {
@ -197,8 +190,7 @@ export default tseslint.config(
}, },
], ],
}, },
}, }, {
{
files: ['packages/vscode-ide-companion/esbuild.js'], files: ['packages/vscode-ide-companion/esbuild.js'],
languageOptions: { languageOptions: {
globals: { globals: {
@ -211,8 +203,7 @@ export default tseslint.config(
'no-restricted-syntax': 'off', 'no-restricted-syntax': 'off',
'@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-require-imports': 'off',
}, },
}, }, // extra settings for scripts that we run directly with node
// extra settings for scripts that we run directly with node
{ {
files: ['packages/vscode-ide-companion/scripts/**/*.js'], files: ['packages/vscode-ide-companion/scripts/**/*.js'],
languageOptions: { languageOptions: {
@ -226,8 +217,7 @@ export default tseslint.config(
'no-restricted-syntax': 'off', 'no-restricted-syntax': 'off',
'@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-require-imports': 'off',
}, },
}, }, // extra settings for core package scripts
// extra settings for core package scripts
{ {
files: ['packages/core/scripts/**/*.js'], files: ['packages/core/scripts/**/*.js'],
languageOptions: { languageOptions: {
@ -241,10 +231,8 @@ export default tseslint.config(
'no-restricted-syntax': 'off', 'no-restricted-syntax': 'off',
'@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-require-imports': 'off',
}, },
}, }, // Prettier config must be last
// Prettier config must be last prettierConfig, // extra settings for scripts that we run directly with node
prettierConfig,
// extra settings for scripts that we run directly with node
{ {
files: ['./integration-tests/**/*.{js,ts,tsx}'], files: ['./integration-tests/**/*.{js,ts,tsx}'],
languageOptions: { languageOptions: {
@ -264,8 +252,7 @@ export default tseslint.config(
}, },
], ],
}, },
}, }, // Settings for docs-site directory
// Settings for docs-site directory
{ {
files: ['docs-site/**/*.{js,jsx}'], files: ['docs-site/**/*.{js,jsx}'],
languageOptions: { languageOptions: {
@ -285,5 +272,4 @@ export default tseslint.config(
'react/prop-types': 'off', 'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off', 'react/react-in-jsx-scope': 'off',
}, },
}, }, storybook.configs["flat/recommended"]);
);

2664
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -152,7 +152,7 @@
"vitest": "^3.2.4" "vitest": "^3.2.4"
}, },
"dependencies": { "dependencies": {
"@qwen-code/webui": "workspace:*", "@qwen-code/webui": "*",
"semver": "^7.7.2", "semver": "^7.7.2",
"@modelcontextprotocol/sdk": "^1.25.1", "@modelcontextprotocol/sdk": "^1.25.1",
"cors": "^2.8.5", "cors": "^2.8.5",

View file

@ -0,0 +1,25 @@
import type { StorybookConfig } from '@storybook/react-vite';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
/**
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
function getAbsolutePath(value: string): string {
return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
}
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
getAbsolutePath('@chromatic-com/storybook'),
getAbsolutePath('@storybook/addon-vitest'),
getAbsolutePath('@storybook/addon-a11y'),
getAbsolutePath('@storybook/addon-docs'),
getAbsolutePath('@storybook/addon-onboarding'),
],
framework: getAbsolutePath('@storybook/react-vite'),
};
export default config;

View file

@ -0,0 +1,11 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
@tailwind base;
@tailwind components;
@tailwind utilities;
@import '../src/styles/variables.css';

View file

@ -0,0 +1,15 @@
import type { Preview } from '@storybook/react-vite';
import './preview.css';
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View file

@ -0,0 +1,428 @@
# WebUI Component Library Extraction Plan
## 1. Background and Goals
### 1.1 Background
`packages/vscode-ide-companion` is a VSCode extension whose core content is a WebView page with UI components provided by React. As the product line expands, more scenarios require building products with Web UI:
- **Chrome Browser Extension** - Sidebar chat interface
- **Web Chat Page** - Pure web application
- **Conversation Share Page** - Render conversations as static HTML
For excellent software engineering architecture, we need to unify and reuse UI components across products.
### 1.2 Goals
1. Extract components from `vscode-ide-companion/src/webview/` into an independent `@qwen-code/webui` package
2. Establish a layered architecture: Pure UI components + Business UI components
3. Use Vite + Storybook for development and component showcase
4. Abstract platform capabilities through Platform Context for cross-platform reuse
5. Provide Tailwind CSS preset to ensure UI consistency across products
---
## 2. Current State Analysis
### 2.1 Current Code Structure
`packages/vscode-ide-companion/src/webview/` contains 77 files:
```
webview/
├── App.tsx # Main entry
├── components/
│ ├── icons/ # 8 icon components
│ ├── layout/ # 8 layout components
│ │ ├── ChatHeader.tsx
│ │ ├── InputForm.tsx
│ │ ├── SessionSelector.tsx
│ │ ├── EmptyState.tsx
│ │ ├── Onboarding.tsx
│ │ └── ...
│ ├── messages/ # Message display components
│ │ ├── UserMessage.tsx
│ │ ├── Assistant/
│ │ ├── MarkdownRenderer/
│ │ ├── ThinkingMessage.tsx
│ │ ├── Waiting/
│ │ └── toolcalls/ # 16 tool call components
│ ├── PermissionDrawer/ # Permission request drawer
│ └── Tooltip.tsx
├── hooks/ # Custom hooks
├── handlers/ # Message handlers
├── styles/ # CSS styles
└── utils/ # Utility functions
```
### 2.2 Key Dependency Analysis
**Platform Coupling Points:**
- `useVSCode` hook - Calls `acquireVsCodeApi()` for message communication
- `handlers/` - Handles VSCode message protocol
- Some type definitions come from `../types/` directory
```
┌─────────────────────────────────────────────────────────┐
│ App.tsx (Entry) │
├─────────────────────────────────────────────────────────┤
│ hooks/ │ handlers/ │ components/ │
│ ├─useVSCode ◄───┼──────────────────┼──────────────────┤
│ ├─useSession │ ├─MessageRouter │ ├─icons/ │
│ ├─useFileContext│ ├─AuthHandler │ ├─layout/ │
│ └─... │ └─... │ ├─messages/ │
│ │ │ └─PermDrawer/ │
├─────────────────────────────────────────────────────────┤
│ VSCode API (acquireVsCodeApi) │
└─────────────────────────────────────────────────────────┘
```
---
## 3. Target Architecture
### 3.1 Layered Architecture Design
```
┌─────────────────────────────────────────────────────────┐
│ Layer 3: Platform Adapters │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │VSCode Adapter│ │Chrome Adapter│ │ Web Adapter │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
├─────────┼────────────────┼────────────────┼────────────┤
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Platform Context Provider │ │
│ └─────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Layer 2: Chat Components │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ MessageList│ │ ChatHeader │ │ InputForm │ │
│ └────────────┘ └────────────┘ └────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Layer 1: Primitives (Pure UI) │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Button │ │ Input │ │ Icons │ │Tooltip │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 3.2 Platform Context Design
```typescript
// @qwen-code/webui/src/context/PlatformContext.ts
interface PlatformContext {
// Message communication
postMessage: (message: unknown) => void;
onMessage: (handler: (message: unknown) => void) => () => void;
// File operations
openFile?: (path: string) => void;
attachFile?: () => void;
// Authentication
login?: () => void;
// Platform info
platform: 'vscode' | 'chrome' | 'web' | 'share';
}
```
---
## 4. Technical Solution
### 4.1 Build Configuration (Vite Library Mode)
**Output formats:**
- ESM (`dist/index.js`) - Primary format
- CJS (`dist/index.cjs`) - Compatibility
- TypeScript declarations (`dist/index.d.ts`)
```javascript
// vite.config.ts
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
},
rollupOptions: {
external: ['react', 'react-dom'],
},
},
});
```
### 4.2 Tailwind Preset Solution
```javascript
// @qwen-code/webui/tailwind.preset.js
module.exports = {
theme: {
extend: {
colors: {
'app-primary': 'var(--app-primary)',
'app-background': 'var(--app-primary-background)',
'app-foreground': 'var(--app-primary-foreground)',
},
},
},
};
// Consumer's tailwind.config.js
module.exports = {
presets: [require('@qwen-code/webui/tailwind.preset')],
content: [
'./src/**/*.{ts,tsx}',
'./node_modules/@qwen-code/webui/dist/**/*.js',
],
};
```
### 4.3 Storybook Configuration
```
packages/webui/
├── .storybook/
│ ├── main.ts # Storybook config
│ ├── preview.ts # Global decorators
│ └── manager.ts # UI config
└── src/
└── stories/ # Story files
```
---
## 5. Component Migration Classification
### 5.1 Batch 1: No-dependency Components (Ready to migrate)
| Component | Source Path | Complexity | Notes |
| ------------------ | ------------------------ | ---------- | --------------------------- |
| Icons | `components/icons/` | Low | 8 icon components, pure SVG |
| Tooltip | `components/Tooltip.tsx` | Low | Pure UI |
| WaitingMessage | `messages/Waiting/` | Low | Loading state display |
| InterruptedMessage | `messages/Waiting/` | Low | Interrupted state display |
### 5.2 Batch 2: Light-dependency Components (Need props abstraction)
| Component | Source Path | Dependency | Refactoring |
| ---------------- | ------------------------------ | ----------- | ---------------- |
| UserMessage | `messages/UserMessage.tsx` | onFileClick | Props injection |
| AssistantMessage | `messages/Assistant/` | onFileClick | Props injection |
| ThinkingMessage | `messages/ThinkingMessage.tsx` | onFileClick | Props injection |
| MarkdownRenderer | `messages/MarkdownRenderer/` | None | Direct migration |
| EmptyState | `layout/EmptyState.tsx` | None | Direct migration |
| ChatHeader | `layout/ChatHeader.tsx` | callbacks | Props injection |
### 5.3 Batch 3: Medium-dependency Components (Need Context)
| Component | Source Path | Dependency | Refactoring |
| ------------------- | ---------------------------- | --------------------- | ----------------- |
| InputForm | `layout/InputForm.tsx` | Multiple callbacks | Context + Props |
| SessionSelector | `layout/SessionSelector.tsx` | session data | Props injection |
| CompletionMenu | `layout/CompletionMenu.tsx` | items data | Props injection |
| PermissionDrawer | `PermissionDrawer/` | callbacks | Context + Props |
| ToolCall components | `messages/toolcalls/` | Various tool displays | Modular migration |
### 5.4 Batch 4: Heavy-dependency (Keep in platform package)
| Component/Module | Notes |
| ---------------- | ------------------------------------------------- |
| App.tsx | Main entry, contains business orchestration logic |
| hooks/ | Most require platform adaptation |
| handlers/ | VSCode message handling |
| Onboarding | Authentication related, platform-specific |
---
## 6. Incremental Migration Strategy
### 6.1 Migration Principles
1. **Bidirectional compatibility**: During migration, vscode-ide-companion can import from both webui and local
2. **One-by-one replacement**: For each migrated component, replace import path in VSCode extension and verify
3. **No breaking changes**: Ensure the extension builds and runs normally after each migration
### 6.2 Migration Workflow
```
Developer ──► @qwen-code/webui ──► vscode-ide-companion
│ │ │
│ 1. Copy component to webui │
│ 2. Add Story for verification │
│ 3. Export from index.ts │
│ │ │
│ └──────────────────────┤
│ │
│ 4. Update import path
│ 5. Delete original component
│ 6. Build and test
```
### 6.3 Example: Migrating Icons
```typescript
// Before: vscode-ide-companion/src/webview/components/icons/index.ts
export { FileIcon } from './FileIcons.js';
// After: Update import
import { FileIcon } from '@qwen-code/webui';
// or import { FileIcon } from '@qwen-code/webui/icons';
```
---
## 7. Task Breakdown
### Phase 0: Infrastructure Setup (Prerequisites)
- [ ] **T0-1**: Vite build configuration
- [ ] **T0-2**: Storybook configuration
- [ ] **T0-3**: Tailwind preset creation
- [ ] **T0-4**: Platform Context definition
- [ ] **T0-5**: Shared types migration
### Phase 1: Pure UI Components Migration
- [ ] **T1-1**: Icons components migration (8 files)
- [ ] **T1-2**: Tooltip component migration
- [ ] **T1-3**: WaitingMessage / InterruptedMessage migration
- [ ] **T1-4**: Basic Button/Input components refinement
### Phase 2: Message Components Migration
- [ ] **T2-1**: MarkdownRenderer migration
- [ ] **T2-2**: UserMessage migration
- [ ] **T2-3**: AssistantMessage migration
- [ ] **T2-4**: ThinkingMessage migration
### Phase 3: Layout Components Migration
- [ ] **T3-1**: ChatHeader migration
- [ ] **T3-2**: EmptyState migration
- [ ] **T3-3**: InputForm migration (requires Context)
- [ ] **T3-4**: SessionSelector migration
- [ ] **T3-5**: CompletionMenu migration
### Phase 4: Complex Components Migration
- [ ] **T4-1**: PermissionDrawer migration
- [ ] **T4-2**: ToolCall series components migration (16 files)
### Phase 5: Platform Adapters
- [ ] **T5-1**: VSCode Adapter implementation
- [ ] **T5-2**: Chrome Extension Adapter
- [ ] **T5-3**: Web/Share Page Adapter
---
## 8. Risks and Considerations
### 8.1 Common Pitfalls
1. **Tailwind Class Name Tree Shaking**
- Problem: Tailwind class names may be removed after library bundling
- Solution: Consumer's `content` config needs to include `node_modules/@qwen-code/webui`
2. **CSS Variable Scope**
- Problem: Variables like `var(--app-primary)` need to be defined by consumers
- Solution: Provide default CSS variables file, or define fallbacks in Tailwind preset
3. **React Version Compatibility**
- Current vscode-ide-companion uses React 19, webui's peerDependencies is React 18
- Need to update peerDependencies to `"react": "^18.0.0 || ^19.0.0"`
4. **ESM/CJS Compatibility**
- VSCode extensions may require CJS format
- Vite needs to be configured for dual format output
### 8.2 Industry References
- **Radix UI**: Pure Headless components, styles completely controlled by consumers
- **shadcn/ui**: Copy components into project, rather than importing as dependency
- **Ant Design**: Complete component library, customization through ConfigProvider
### 8.3 Acceptance Criteria
Each migration task completion requires:
1. Component has corresponding Storybook Story
2. Import in vscode-ide-companion has been updated
3. Extension builds successfully (`npm run build:vscode`)
4. Extension functionality works (manual testing or existing tests pass)
---
## 9. Time Estimation
| Phase | Tasks | Estimated Days | Parallelizable |
| ------- | ----- | -------------- | -------------- |
| Phase 0 | 5 | 2-3 days | Partially |
| Phase 1 | 4 | 1-2 days | Fully |
| Phase 2 | 4 | 2-3 days | Fully |
| Phase 3 | 5 | 3-4 days | Partially |
| Phase 4 | 2 | 3-4 days | Yes |
| Phase 5 | 3 | 2-3 days | Yes |
**Total**: Approximately 13-19 person-days (sequential execution), can be reduced to 1-2 weeks with parallel work
---
## 10. Development and Debugging Workflow
### 10.1 Component Development Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ Development Workflow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Develop/Modify Component │
│ └── Edit files in @qwen-code/webui/src/ │
│ │
│ 2. Debug with Storybook │
│ └── npm run storybook (port 6006) │
│ └── View component in isolation │
│ └── Test different props/states │
│ │
│ 3. Build Library │
│ └── npm run build │
│ └── Outputs: dist/index.js, dist/index.cjs, dist/index.d.ts │
│ │
│ 4. Use in VSCode Extension │
│ └── import { Component } from '@qwen-code/webui' │
│ └── No UI code modifications in vscode-ide-companion │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 10.2 Debugging Commands
```bash
# Start Storybook for component development
cd packages/webui
npm run storybook
# Watch mode for library development
npm run dev
# Build library for production
npm run build
# Type checking
npm run typecheck
```
### 10.3 Key Principles
1. **Single Source of Truth**: All UI components live in `@qwen-code/webui`
2. **Storybook First**: Debug and validate components in Storybook before integration
3. **No UI Code in Consumers**: `vscode-ide-companion` only imports and uses components
4. **Platform Abstraction**: Use `PlatformContext` for platform-specific behaviors

View file

@ -0,0 +1,428 @@
# WebUI 组件库抽离计划
## 一、背景与目标
### 1.1 背景
`packages/vscode-ide-companion` 是一个 VSCode 插件,其核心内容是一个 WebView 页面,大量 UI 部分由 React 组件提供。随着产品线扩展,越来越多的场景需要构建包含 Web UI 的产品:
- **Chrome 浏览器扩展** - 侧边栏聊天界面
- **Web 端聊天页面** - 纯 Web 应用
- **对话分享页面** - 将对话渲染为静态 HTML
对于优秀的软件工程架构,我们需要让 UI 做到统一且可复用。
### 1.2 目标
1. 将 `vscode-ide-companion/src/webview/` 中的组件抽离到独立的 `@qwen-code/webui`
2. 建立分层架构:纯 UI 组件 + 业务 UI 组件
3. 使用 Vite + Storybook 进行开发和组件展示
4. 通过 Platform Context 抽象平台能力,实现跨平台复用
5. 提供 Tailwind CSS 预设,保证多产品 UI 一致性
---
## 二、现状分析
### 2.1 当前代码结构
`packages/vscode-ide-companion/src/webview/` 包含 77 个文件:
```
webview/
├── App.tsx # 主入口
├── components/
│ ├── icons/ # 8 个图标组件
│ ├── layout/ # 8 个布局组件
│ │ ├── ChatHeader.tsx
│ │ ├── InputForm.tsx
│ │ ├── SessionSelector.tsx
│ │ ├── EmptyState.tsx
│ │ ├── Onboarding.tsx
│ │ └── ...
│ ├── messages/ # 消息展示组件
│ │ ├── UserMessage.tsx
│ │ ├── Assistant/
│ │ ├── MarkdownRenderer/
│ │ ├── ThinkingMessage.tsx
│ │ ├── Waiting/
│ │ └── toolcalls/ # 16 个工具调用组件
│ ├── PermissionDrawer/ # 权限请求抽屉
│ └── Tooltip.tsx
├── hooks/ # 自定义 hooks
├── handlers/ # 消息处理器
├── styles/ # CSS 样式
└── utils/ # 工具函数
```
### 2.2 关键依赖分析
**平台耦合点:**
- `useVSCode` hook - 调用 `acquireVsCodeApi()` 进行消息通信
- `handlers/` - 处理 VSCode 消息协议
- 部分类型定义来自 `../types/` 目录
```
┌─────────────────────────────────────────────────────────┐
│ App.tsx (入口) │
├─────────────────────────────────────────────────────────┤
│ hooks/ │ handlers/ │ components/ │
│ ├─useVSCode ◄───┼──────────────────┼──────────────────┤
│ ├─useSession │ ├─MessageRouter │ ├─icons/ │
│ ├─useFileContext│ ├─AuthHandler │ ├─layout/ │
│ └─... │ └─... │ ├─messages/ │
│ │ │ └─PermDrawer/ │
├─────────────────────────────────────────────────────────┤
│ VSCode API (acquireVsCodeApi) │
└─────────────────────────────────────────────────────────┘
```
---
## 三、目标架构
### 3.1 分层架构设计
```
┌─────────────────────────────────────────────────────────┐
│ Layer 3: Platform Adapters │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │VSCode Adapter│ │Chrome Adapter│ │ Web Adapter │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
├─────────┼────────────────┼────────────────┼────────────┤
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Platform Context Provider │ │
│ └─────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Layer 2: Chat Components │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ MessageList│ │ ChatHeader │ │ InputForm │ │
│ └────────────┘ └────────────┘ └────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Layer 1: Primitives (纯 UI) │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Button │ │ Input │ │ Icons │ │Tooltip │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 3.2 Platform Context 设计
```typescript
// @qwen-code/webui/src/context/PlatformContext.ts
interface PlatformContext {
// 消息通信
postMessage: (message: unknown) => void;
onMessage: (handler: (message: unknown) => void) => () => void;
// 文件操作
openFile?: (path: string) => void;
attachFile?: () => void;
// 认证
login?: () => void;
// 平台信息
platform: 'vscode' | 'chrome' | 'web' | 'share';
}
```
---
## 四、技术方案
### 4.1 构建配置Vite Library Mode
**输出格式:**
- ESM (`dist/index.js`) - 主要格式
- CJS (`dist/index.cjs`) - 兼容性
- TypeScript 声明 (`dist/index.d.ts`)
```javascript
// vite.config.ts
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
},
rollupOptions: {
external: ['react', 'react-dom'],
},
},
});
```
### 4.2 Tailwind 预设方案
```javascript
// @qwen-code/webui/tailwind.preset.js
module.exports = {
theme: {
extend: {
colors: {
'app-primary': 'var(--app-primary)',
'app-background': 'var(--app-primary-background)',
'app-foreground': 'var(--app-primary-foreground)',
},
},
},
};
// 消费方 tailwind.config.js
module.exports = {
presets: [require('@qwen-code/webui/tailwind.preset')],
content: [
'./src/**/*.{ts,tsx}',
'./node_modules/@qwen-code/webui/dist/**/*.js',
],
};
```
### 4.3 Storybook 配置
```
packages/webui/
├── .storybook/
│ ├── main.ts # Storybook 配置
│ ├── preview.ts # 全局装饰器
│ └── manager.ts # UI 配置
└── src/
└── stories/ # Story 文件
```
---
## 五、组件迁移分类
### 5.1 第一批:无依赖组件(可立即迁移)
| 组件 | 来源路径 | 复杂度 | 说明 |
| ------------------ | ------------------------ | ------ | -------------------- |
| Icons | `components/icons/` | 低 | 8 个图标组件,纯 SVG |
| Tooltip | `components/Tooltip.tsx` | 低 | 纯 UI |
| WaitingMessage | `messages/Waiting/` | 低 | 加载状态展示 |
| InterruptedMessage | `messages/Waiting/` | 低 | 中断状态展示 |
### 5.2 第二批:轻度依赖组件(需要抽象 props
| 组件 | 来源路径 | 依赖 | 改造方式 |
| ---------------- | ------------------------------ | ----------- | --------------- |
| UserMessage | `messages/UserMessage.tsx` | onFileClick | 通过 props 注入 |
| AssistantMessage | `messages/Assistant/` | onFileClick | 通过 props 注入 |
| ThinkingMessage | `messages/ThinkingMessage.tsx` | onFileClick | 通过 props 注入 |
| MarkdownRenderer | `messages/MarkdownRenderer/` | 无 | 直接迁移 |
| EmptyState | `layout/EmptyState.tsx` | 无 | 直接迁移 |
| ChatHeader | `layout/ChatHeader.tsx` | callbacks | 通过 props 注入 |
### 5.3 第三批:中度依赖组件(需要 Context
| 组件 | 来源路径 | 依赖 | 改造方式 |
| ---------------- | ---------------------------- | -------------- | --------------- |
| InputForm | `layout/InputForm.tsx` | 多个 callbacks | Context + Props |
| SessionSelector | `layout/SessionSelector.tsx` | session 数据 | Props 注入 |
| CompletionMenu | `layout/CompletionMenu.tsx` | items 数据 | Props 注入 |
| PermissionDrawer | `PermissionDrawer/` | 回调函数 | Context + Props |
| ToolCall 组件 | `messages/toolcalls/` | 多种工具展示 | 分模块迁移 |
### 5.4 第四批:重度依赖(保留在平台包)
| 组件/模块 | 说明 |
| ---------- | ------------------------ |
| App.tsx | 总入口,包含业务编排逻辑 |
| hooks/ | 大部分需要平台适配 |
| handlers/ | VSCode 消息处理 |
| Onboarding | 认证相关,平台特定 |
---
## 六、渐进式迁移策略
### 6.1 迁移原则
1. **双向兼容**迁移期间vscode-ide-companion 可以同时从 webui 和本地导入
2. **逐个替换**:每迁移一个组件,在 VSCode 插件中替换导入路径并验证
3. **不破坏现有功能**:确保每次迁移后插件可正常构建和运行
### 6.2 迁移流程
```
开发者 ──► @qwen-code/webui ──► vscode-ide-companion
│ │ │
│ 1. 复制组件到 webui │
│ 2. 添加 Story 验证 │
│ 3. 从 index.ts 导出 │
│ │ │
│ └──────────────────────┤
│ │
│ 4. 更新 import 路径
│ 5. 删除原组件文件
│ 6. 构建测试验证
```
### 6.3 示例:迁移 Icons
```typescript
// Before: vscode-ide-companion/src/webview/components/icons/index.ts
export { FileIcon } from './FileIcons.js';
// After: 修改导入
import { FileIcon } from '@qwen-code/webui';
// 或 import { FileIcon } from '@qwen-code/webui/icons';
```
---
## 七、任务拆分
### Phase 0: 基础设施搭建(前置任务)
- [ ] **T0-1**: Vite 构建配置
- [ ] **T0-2**: Storybook 配置
- [ ] **T0-3**: Tailwind 预设创建
- [ ] **T0-4**: Platform Context 定义
- [ ] **T0-5**: 类型定义迁移(共享 types
### Phase 1: 纯 UI 组件迁移
- [ ] **T1-1**: Icons 组件迁移8 个文件)
- [ ] **T1-2**: Tooltip 组件迁移
- [ ] **T1-3**: WaitingMessage / InterruptedMessage 迁移
- [ ] **T1-4**: 基础 Button/Input 组件完善
### Phase 2: 消息组件迁移
- [ ] **T2-1**: MarkdownRenderer 迁移
- [ ] **T2-2**: UserMessage 迁移
- [ ] **T2-3**: AssistantMessage 迁移
- [ ] **T2-4**: ThinkingMessage 迁移
### Phase 3: 布局组件迁移
- [ ] **T3-1**: ChatHeader 迁移
- [ ] **T3-2**: EmptyState 迁移
- [ ] **T3-3**: InputForm 迁移(需要 Context
- [ ] **T3-4**: SessionSelector 迁移
- [ ] **T3-5**: CompletionMenu 迁移
### Phase 4: 复杂组件迁移
- [ ] **T4-1**: PermissionDrawer 迁移
- [ ] **T4-2**: ToolCall 系列组件迁移16 个文件)
### Phase 5: 平台适配器
- [ ] **T5-1**: VSCode Adapter 实现
- [ ] **T5-2**: Chrome Extension Adapter
- [ ] **T5-3**: Web/Share Page Adapter
---
## 八、风险与注意事项
### 8.1 常见坑点
1. **Tailwind 类名 Tree Shaking**
- 问题:组件库打包后 Tailwind 类名可能被移除
- 解决:消费方的 `content` 配置需要包含 `node_modules/@qwen-code/webui`
2. **CSS 变量作用域**
- 问题:`var(--app-primary)` 等变量需要在消费方定义
- 解决:提供默认 CSS 变量文件,或在 Tailwind 预设中定义 fallback
3. **React 版本兼容**
- 当前 vscode-ide-companion 使用 React 19webui 的 peerDependencies 是 React 18
- 需要更新 peerDependencies 为 `"react": "^18.0.0 || ^19.0.0"`
4. **ESM/CJS 兼容**
- VSCode 扩展可能需要 CJS 格式
- Vite 需要配置双格式输出
### 8.2 业界参考
- **Radix UI**: 纯 Headless 组件,样式完全由消费方控制
- **shadcn/ui**: 复制组件到项目中,而非作为依赖引入
- **Ant Design**: 完整的组件库,通过 ConfigProvider 进行定制
### 8.3 验收标准
每个迁移任务完成后需要:
1. 组件有对应的 Storybook Story
2. vscode-ide-companion 中的导入已更新
3. 插件可正常构建 (`npm run build:vscode`)
4. 插件功能正常(手动测试或已有测试通过)
---
## 九、预估时间
| 阶段 | 任务数 | 预估人天 | 可并行 |
| ------- | ------ | -------- | ---------- |
| Phase 0 | 5 | 2-3 天 | 部分可并行 |
| Phase 1 | 4 | 1-2 天 | 全部可并行 |
| Phase 2 | 4 | 2-3 天 | 全部可并行 |
| Phase 3 | 5 | 3-4 天 | 部分可并行 |
| Phase 4 | 2 | 3-4 天 | 可并行 |
| Phase 5 | 3 | 2-3 天 | 可并行 |
**总计**:约 13-19 人天(单人顺序执行),如果多人并行可缩短至 1-2 周
---
## 十、开发与调试流程
### 10.1 组件开发流程
```
┌─────────────────────────────────────────────────────────────────┐
│ 开发工作流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 开发/修改组件 │
│ └── 在 @qwen-code/webui/src/ 中编辑文件 │
│ │
│ 2. 使用 Storybook 调试 │
│ └── npm run storybook (端口 6006) │
│ └── 独立查看组件 │
│ └── 测试不同的 props/状态 │
│ │
│ 3. 构建组件库 │
│ └── npm run build │
│ └── 输出: dist/index.js, dist/index.cjs, dist/index.d.ts │
│ │
│ 4. 在 VSCode 插件中使用 │
│ └── import { Component } from '@qwen-code/webui' │
│ └── vscode-ide-companion 中不再修改 UI 代码 │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 10.2 调试命令
```bash
# 启动 Storybook 进行组件开发
cd packages/webui
npm run storybook
# 监听模式进行库开发
npm run dev
# 构建生产版本
npm run build
# 类型检查
npm run typecheck
```
### 10.3 核心原则
1. **单一数据源**: 所有 UI 组件都在 `@qwen-code/webui`
2. **Storybook 优先**: 在集成前先在 Storybook 中调试和验证组件
3. **消费方不修改 UI 代码**: `vscode-ide-companion` 只导入和使用组件
4. **平台抽象**: 使用 `PlatformContext` 处理平台特定行为

View file

@ -2,28 +2,60 @@
"name": "@qwen-code/webui", "name": "@qwen-code/webui",
"version": "0.1.0", "version": "0.1.0",
"description": "Shared UI components for Qwen Code packages", "description": "Shared UI components for Qwen Code packages",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"type": "module", "type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./tailwind.preset": "./tailwind.preset.cjs",
"./styles.css": "./dist/styles.css"
},
"files": [
"dist",
"tailwind.preset.cjs"
],
"sideEffects": [
"**/*.css"
],
"scripts": { "scripts": {
"build": "tsc && rollup -c", "dev": "vite build --watch",
"dev": "tsc --watch", "build": "vite build",
"typecheck": "tsc --noEmit",
"lint": "eslint src --ext .ts,.tsx", "lint": "eslint src --ext .ts,.tsx",
"lint:fix": "eslint src --ext .ts,.tsx --fix", "lint:fix": "eslint src --ext .ts,.tsx --fix",
"typecheck": "tsc --noEmit" "storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^18.0.0", "react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0" "react-dom": "^18.0.0 || ^19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.0", "@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0", "@types/react-dom": "^18.0.0",
"@vitejs/plugin-react": "^4.2.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.0",
"tailwindcss": "^3.4.0",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"@rollup/plugin-typescript": "^11.0.0", "vite": "^5.0.0",
"rollup": "^4.0.0", "vite-plugin-dts": "^3.7.0",
"rollup-plugin-dts": "^6.0.0" "storybook": "^10.1.11",
"@storybook/react-vite": "^10.1.11",
"@chromatic-com/storybook": "^5.0.0",
"@storybook/addon-vitest": "^10.1.11",
"@storybook/addon-a11y": "^10.1.11",
"@storybook/addon-docs": "^10.1.11",
"@storybook/addon-onboarding": "^10.1.11",
"eslint-plugin-storybook": "^10.1.11",
"playwright": "^1.57.0",
"@vitest/browser": "^3.2.4",
"@vitest/coverage-v8": "^3.2.4"
}, },
"keywords": [ "keywords": [
"qwen", "qwen",

View file

@ -0,0 +1,13 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/* eslint-env node */
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View file

@ -1,47 +0,0 @@
import typescript from '@rollup/plugin-typescript';
import { dts } from 'rollup-plugin-dts';
import pkg from './package.json' with { type: 'json' };
const name = pkg.name;
export default [
// Browser-friendly version
{
input: 'src/index.ts',
output: {
name,
file: 'dist/index.min.js',
format: 'iife',
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
external: ['react', 'react-dom'],
plugins: [
typescript({
tsconfig: './tsconfig.json',
}),
],
},
// ES module version
{
input: 'src/index.ts',
output: [
{ file: 'dist/index.esm.js', format: 'es' },
{ file: 'dist/index.cjs.js', format: 'cjs' },
],
external: ['react', 'react-dom'],
plugins: [
typescript({
tsconfig: './tsconfig.json',
}),
],
},
// Type declarations
{
input: 'dist/dts/src/index.d.ts',
output: [{ file: 'dist/index.d.ts', format: 'es' }],
plugins: [dts()],
},
];

View file

@ -0,0 +1,83 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import type { Meta, StoryObj } from '@storybook/react-vite';
import Button from './Button';
/**
* Button component for user interactions.
* Supports multiple variants and sizes.
*/
const meta: Meta<typeof Button> = {
title: 'UI/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
description: 'Visual style variant',
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
description: 'Button size',
},
disabled: {
control: 'boolean',
description: 'Disabled state',
},
onClick: { action: 'clicked' },
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
children: 'Primary Button',
variant: 'primary',
},
};
export const Secondary: Story = {
args: {
children: 'Secondary Button',
variant: 'secondary',
},
};
export const Danger: Story = {
args: {
children: 'Danger Button',
variant: 'danger',
},
};
export const Small: Story = {
args: {
children: 'Small Button',
size: 'sm',
},
};
export const Large: Story = {
args: {
children: 'Large Button',
size: 'lg',
},
};
export const Disabled: Story = {
args: {
children: 'Disabled Button',
disabled: true,
},
};

View file

@ -0,0 +1,68 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import type { Meta, StoryObj } from '@storybook/react-vite';
import Tooltip from './Tooltip';
import Button from './Button';
/**
* Tooltip component for displaying contextual information on hover.
* Supports four positions: top, right, bottom, left.
*/
const meta: Meta<typeof Tooltip> = {
title: 'UI/Tooltip',
component: Tooltip,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
position: {
control: 'select',
options: ['top', 'right', 'bottom', 'left'],
description: 'Tooltip position relative to trigger',
},
content: {
control: 'text',
description: 'Tooltip content text',
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Top: Story = {
args: {
content: 'Tooltip on top',
position: 'top',
children: <Button>Hover me</Button>,
},
};
export const Right: Story = {
args: {
content: 'Tooltip on right',
position: 'right',
children: <Button>Hover me</Button>,
},
};
export const Bottom: Story = {
args: {
content: 'Tooltip on bottom',
position: 'bottom',
children: <Button>Hover me</Button>,
},
};
export const Left: Story = {
args: {
content: 'Tooltip on left',
position: 'left',
children: <Button>Hover me</Button>,
},
};

View file

@ -0,0 +1,89 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { createContext, useContext } from 'react';
/**
* Platform types supported by the webui library
*/
export type PlatformType = 'vscode' | 'chrome' | 'web' | 'share';
/**
* Platform context interface for cross-platform component reuse.
* Each platform adapter implements this interface.
*/
export interface PlatformContextValue {
/** Current platform identifier */
platform: PlatformType;
/** Send message to platform host */
postMessage: (message: unknown) => void;
/** Subscribe to messages from platform host */
onMessage: (handler: (message: unknown) => void) => () => void;
/** Open a file in the platform's editor (optional) */
openFile?: (path: string) => void;
/** Trigger file attachment dialog (optional) */
attachFile?: () => void;
/** Trigger platform login flow (optional) */
login?: () => void;
/** Copy text to clipboard */
copyToClipboard?: (text: string) => Promise<void>;
/** Platform-specific feature flags */
features?: {
canOpenFile?: boolean;
canAttachFile?: boolean;
canLogin?: boolean;
canCopy?: boolean;
};
}
/**
* Default noop implementation for platforms without message support
*/
const defaultContext: PlatformContextValue = {
platform: 'web',
postMessage: () => {},
onMessage: () => () => {},
};
/**
* Platform context for accessing platform-specific capabilities
*/
export const PlatformContext =
createContext<PlatformContextValue>(defaultContext);
/**
* Hook to access platform context
*/
export function usePlatform(): PlatformContextValue {
return useContext(PlatformContext);
}
/**
* Provider component props
*/
export interface PlatformProviderProps {
children: React.ReactNode;
value: PlatformContextValue;
}
/**
* Platform context provider component
*/
export function PlatformProvider({ children, value }: PlatformProviderProps) {
return (
<PlatformContext.Provider value={value}>
{children}
</PlatformContext.Provider>
);
}

View file

@ -1,6 +1,18 @@
// Shared UI Components Export // Shared UI Components Export
// Export all shared components from this package // Export all shared components from this package
// Context
export {
PlatformContext,
PlatformProvider,
usePlatform,
} from './context/PlatformContext';
export type {
PlatformContextValue,
PlatformProviderProps,
PlatformType,
} from './context/PlatformContext';
// Layout components // Layout components
export { default as Container } from './components/layout/Container'; export { default as Container } from './components/layout/Container';
export { default as Header } from './components/layout/Header'; export { default as Header } from './components/layout/Header';
@ -33,3 +45,10 @@ export { useLocalStorage } from './hooks/useLocalStorage';
// Types // Types
export type { Theme } from './types/theme'; export type { Theme } from './types/theme';
export type { MessageProps } from './types/messages'; export type { MessageProps } from './types/messages';
export type { ChatMessage, MessageRole, PlanEntry } from './types/chat';
export type {
ToolCallStatus,
ToolCallLocation,
ToolCallContentItem,
ToolCallUpdate,
} from './types/toolCall';

View file

@ -0,0 +1,52 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Default CSS variables for @qwen-code/webui
* Consumers can override these variables to customize the theme.
*/
:root {
/* Primary colors */
--app-primary: #3b82f6;
--app-primary-hover: #2563eb;
--app-primary-foreground: #ffffff;
/* Background colors */
--app-background: #ffffff;
--app-background-secondary: #f3f4f6;
--app-background-tertiary: #e5e7eb;
/* Foreground/text colors */
--app-foreground: #111827;
--app-foreground-secondary: #6b7280;
--app-foreground-muted: #9ca3af;
/* Border colors */
--app-border: #e5e7eb;
--app-border-focus: #3b82f6;
/* Status colors */
--app-success: #10b981;
--app-warning: #f59e0b;
--app-error: #ef4444;
--app-info: #3b82f6;
/* Typography */
--app-font-sans: system-ui, -apple-system, sans-serif;
--app-font-mono: ui-monospace, monospace;
/* Border radius */
--app-radius-sm: 0.25rem;
--app-radius-md: 0.375rem;
--app-radius-lg: 0.5rem;
/* Spacing */
--app-spacing-xs: 0.25rem;
--app-spacing-sm: 0.5rem;
--app-spacing-md: 1rem;
--app-spacing-lg: 1.5rem;
--app-spacing-xl: 2rem;
}

View file

@ -0,0 +1,28 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Chat message role types
*/
export type MessageRole = 'user' | 'assistant' | 'system';
/**
* Basic chat message structure
*/
export interface ChatMessage {
role: MessageRole;
content: string;
timestamp: number;
}
/**
* Plan entry for task tracking
*/
export interface PlanEntry {
content: string;
priority?: 'high' | 'medium' | 'low';
status: 'pending' | 'in_progress' | 'completed';
}

View file

@ -0,0 +1,48 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Tool call status
*/
export type ToolCallStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
/**
* Tool call location reference
*/
export interface ToolCallLocation {
path: string;
line?: number | null;
}
/**
* Tool call content item
*/
export interface ToolCallContentItem {
type: 'content' | 'diff';
content?: {
type: string;
text?: string;
[key: string]: unknown;
};
path?: string;
oldText?: string | null;
newText?: string;
[key: string]: unknown;
}
/**
* Tool call update data
*/
export interface ToolCallUpdate {
toolCallId: string;
kind?: string;
title?: string;
status?: ToolCallStatus;
rawInput?: unknown;
content?: ToolCallContentItem[];
locations?: ToolCallLocation[];
timestamp?: number;
}

View file

@ -0,0 +1,11 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/* eslint-env node */
module.exports = {
presets: [require('./tailwind.preset.cjs')],
content: ['./src/**/*.{ts,tsx}'],
};

View file

@ -0,0 +1,71 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @qwen-code/webui Tailwind CSS Preset
*
* This preset provides shared theme configuration for all Qwen Code products.
* Consumers should include this preset in their tailwind.config.js:
*
* @example
* module.exports = {
* presets: [require('@qwen-code/webui/tailwind.preset')],
* content: [
* './src/**\/*.{ts,tsx}',
* './node_modules/@qwen-code/webui/dist/**\/*.js'
* ]
* }
*/
/* eslint-env node */
module.exports = {
theme: {
extend: {
colors: {
// Primary colors using CSS variables for runtime theming
'app-primary': 'var(--app-primary, #3b82f6)',
'app-primary-hover': 'var(--app-primary-hover, #2563eb)',
'app-primary-foreground': 'var(--app-primary-foreground, #ffffff)',
// Background colors
'app-background': 'var(--app-background, #ffffff)',
'app-background-secondary': 'var(--app-background-secondary, #f3f4f6)',
'app-background-tertiary': 'var(--app-background-tertiary, #e5e7eb)',
// Foreground/text colors
'app-foreground': 'var(--app-foreground, #111827)',
'app-foreground-secondary': 'var(--app-foreground-secondary, #6b7280)',
'app-foreground-muted': 'var(--app-foreground-muted, #9ca3af)',
// Border colors
'app-border': 'var(--app-border, #e5e7eb)',
'app-border-focus': 'var(--app-border-focus, #3b82f6)',
// Status colors
'app-success': 'var(--app-success, #10b981)',
'app-warning': 'var(--app-warning, #f59e0b)',
'app-error': 'var(--app-error, #ef4444)',
'app-info': 'var(--app-info, #3b82f6)',
},
fontFamily: {
sans: ['var(--app-font-sans, system-ui, sans-serif)'],
mono: ['var(--app-font-mono, ui-monospace, monospace)'],
},
borderRadius: {
'app-sm': 'var(--app-radius-sm, 0.25rem)',
'app-md': 'var(--app-radius-md, 0.375rem)',
'app-lg': 'var(--app-radius-lg, 0.5rem)',
},
spacing: {
'app-xs': 'var(--app-spacing-xs, 0.25rem)',
'app-sm': 'var(--app-spacing-sm, 0.5rem)',
'app-md': 'var(--app-spacing-md, 1rem)',
'app-lg': 'var(--app-spacing-lg, 1.5rem)',
'app-xl': 'var(--app-spacing-xl, 2rem)',
},
},
},
};

View file

@ -14,11 +14,8 @@
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true
"declaration": true,
"declarationDir": "./dist",
"emitDeclarationOnly": true
}, },
"include": ["src"], "include": ["src"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist", "**/*.stories.tsx"]
} }

View file

@ -0,0 +1,53 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import { resolve } from 'path';
/**
* Vite configuration for @qwen-code/webui library
*
* Build outputs:
* - ESM: dist/index.js (primary format)
* - CJS: dist/index.cjs (compatibility)
* - TypeScript declarations: dist/index.d.ts
* - CSS: dist/styles.css (optional styles)
*/
export default defineConfig({
plugins: [
react(),
dts({
include: ['src'],
outDir: 'dist',
rollupTypes: true,
insertTypesEntry: true,
}),
],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'QwenCodeWebUI',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
},
rollupOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
'react/jsx-runtime': 'jsxRuntime',
},
assetFileNames: 'styles.[ext]',
},
},
sourcemap: true,
minify: false,
cssCodeSplit: false,
},
});