diff --git a/eslint.config.js b/eslint.config.js
index 67d09f2c1..37eec9ecb 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -192,6 +192,20 @@ export default tseslint.config(
},
],
},
+ },
+ {
+ files: ['**/*.cjs'],
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ module: 'readonly',
+ require: 'readonly',
+ },
+ },
+ rules: {
+ '@typescript-eslint/no-require-imports': 'off',
+ 'no-undef': 'off',
+ },
}, {
files: ['packages/vscode-ide-companion/esbuild.js'],
languageOptions: {
diff --git a/packages/webui/.npmignore b/packages/webui/.npmignore
new file mode 100644
index 000000000..c0355e378
--- /dev/null
+++ b/packages/webui/.npmignore
@@ -0,0 +1,55 @@
+# Dependencies
+node_modules/
+
+# Build outputs (already included in files array)
+# dist is included via package.json files array
+
+# Development files
+*.test.*
+*.spec.*
+.storybook/
+.storybook-static/
+.storybook-build/
+storybook-static/
+docs/
+
+# IDE files
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# Editor files
+.DS_Store
+Thumbs.db
+
+# Git files
+.git/
+.gitignore
+
+# Temporary files
+tmp/
+temp/
+.nyc_output/
+coverage/
+
+# Logs
+*.log
+
+# Configs
+.vite/
+.eslintrc*
+.prettierrc*
+.editorconfig
+
+# Examples (not needed in npm package)
+examples/
+
+# Scripts
+scripts/
+
+# Local development
+demo/
+dist-dev/
+dist-dev/**
+!dist/**
\ No newline at end of file
diff --git a/packages/webui/README.md b/packages/webui/README.md
index d69937b42..4a358d4e8 100644
--- a/packages/webui/README.md
+++ b/packages/webui/README.md
@@ -9,6 +9,8 @@ A shared React component library for Qwen Code applications, providing cross-pla
- **Tailwind CSS**: Shared styling preset for consistent design
- **TypeScript**: Full type definitions for all components
- **Storybook**: Interactive component documentation and development
+- **Multiple Build Formats**: Supports ESM, CJS, and UMD formats for different environments
+- **CDN Usage**: Can be loaded directly in browsers via CDN
## Installation
@@ -16,6 +18,141 @@ A shared React component library for Qwen Code applications, providing cross-pla
npm install @qwen-code/webui
```
+## CDN Usage
+
+You can also use this library directly in the browser via CDN:
+
+### Option 1: With JSX Support (using Babel)
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Option 2: Without JSX (using React.createElement directly)
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+For a complete working example, see [examples/cdn-usage-demo.html](./examples/cdn-usage-demo.html).
+
## Quick Start
```tsx
diff --git a/packages/webui/examples/README.md b/packages/webui/examples/README.md
new file mode 100644
index 000000000..dedd708b7
--- /dev/null
+++ b/packages/webui/examples/README.md
@@ -0,0 +1,8 @@
+# Examples
+
+This directory contains example implementations demonstrating various ways to use the `@qwen-code/webui` library.
+
+## Available Examples
+
+- [`cdn-usage-demo.html`](./cdn-usage-demo.html) - Shows how to use the library directly via CDN in a browser environment, featuring the ChatViewer component
+- [`complex-chat-demo.html`](./complex-chat-demo.html) - Demonstrates a complex chat conversation with tool calls, code blocks, and mixed-language interactions
diff --git a/packages/webui/examples/cdn-usage-demo.html b/packages/webui/examples/cdn-usage-demo.html
new file mode 100644
index 000000000..c013e9078
--- /dev/null
+++ b/packages/webui/examples/cdn-usage-demo.html
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+ @qwen-code/webui CDN Usage Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@qwen-code/webui CDN Usage Example
+
ChatViewer Component Demo
+
+
+
+
+
+
+
diff --git a/packages/webui/examples/complex-chat-demo.html b/packages/webui/examples/complex-chat-demo.html
new file mode 100644
index 000000000..5f4f32b25
--- /dev/null
+++ b/packages/webui/examples/complex-chat-demo.html
@@ -0,0 +1,413 @@
+
+
+
+
+
+
+ @qwen-code/webui Complex Chat Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@qwen-code/webui Complex Chat Demo
+
Real conversation example with tool calls
+
+
+
Alternative: With Full Tailwind Support
+
For full Tailwind utility class support (like gap-1.5, button classes, etc.), also include:
+
<script src="https://cdn.tailwindcss.com"></script>
+
+
+
+
+
+
diff --git a/packages/webui/package.json b/packages/webui/package.json
index 06fda4881..dd19b055d 100644
--- a/packages/webui/package.json
+++ b/packages/webui/package.json
@@ -1,6 +1,6 @@
{
"name": "@qwen-code/webui",
- "version": "0.1.0",
+ "version": "0.1.0-beta.2",
"description": "Shared UI components for Qwen Code packages",
"type": "module",
"main": "./dist/index.cjs",
@@ -18,8 +18,7 @@
"require": "./dist/components/icons/index.cjs"
},
"./tailwind.preset": "./tailwind.preset.cjs",
- "./styles.css": "./dist/styles.css",
- "./webview.css": "./dist/webview.css"
+ "./styles.css": "./dist/styles.css"
},
"files": [
"dist",
@@ -28,6 +27,9 @@
"sideEffects": [
"**/*.css"
],
+ "publishConfig": {
+ "access": "public"
+ },
"scripts": {
"dev": "vite build --watch",
"build": "vite build",
diff --git a/packages/webui/src/components/messages/MarkdownRenderer/MarkdownRenderer.css b/packages/webui/src/components/messages/MarkdownRenderer/MarkdownRenderer.css
index 37a3485a3..c53725e49 100644
--- a/packages/webui/src/components/messages/MarkdownRenderer/MarkdownRenderer.css
+++ b/packages/webui/src/components/messages/MarkdownRenderer/MarkdownRenderer.css
@@ -50,7 +50,6 @@
.markdown-content p {
margin-top: 0;
- /* margin-bottom: 1em; */
}
.markdown-content ul,
diff --git a/packages/webui/src/components/toolcalls/UpdatedPlanToolCall.tsx b/packages/webui/src/components/toolcalls/UpdatedPlanToolCall.tsx
index 46dae47a1..414ce6723 100644
--- a/packages/webui/src/components/toolcalls/UpdatedPlanToolCall.tsx
+++ b/packages/webui/src/components/toolcalls/UpdatedPlanToolCall.tsx
@@ -82,7 +82,8 @@ const parsePlanEntries = (textOutputs: string[]): PlanEntry[] => {
const entries: PlanEntry[] = [];
// Accept [ ], [x]/[X] and in-progress markers [-] or [*]
- const todoRe = /^(?:\s*(?:[-*]|\d+[.)])\s*)?\[( |x|X|-|\*)\]\s+(.*)$/;
+ const todoRe =
+ /^(?:\s{0,10}(?:[-*]|\d{1,3}[.)])\s{0,10})?\[( |x|X|-|\*)\]\s+(.{0,500})$/;
for (const line of lines) {
const m = line.match(todoRe);
if (m) {
diff --git a/packages/webui/src/components/toolcalls/shared/utils.ts b/packages/webui/src/components/toolcalls/shared/utils.ts
index 832c7e39a..af96e0eb5 100644
--- a/packages/webui/src/components/toolcalls/shared/utils.ts
+++ b/packages/webui/src/components/toolcalls/shared/utils.ts
@@ -43,7 +43,9 @@ export const extractCommandOutput = (text: string): string => {
}
// Second try: Extract from structured text format
- const outputMatch = text.match(/Output:[ \t]*(.+?)(?=\nError:|$)/i);
+ const outputMatch = text.match(
+ /Output:[ \t]{0,20}(.{0,1000}?)(?=\nError:|$)/i,
+ );
if (outputMatch && outputMatch[1]) {
const output = outputMatch[1].trim();
if (output && output !== '(none)' && output.length > 0) {
diff --git a/packages/webui/src/styles/variables.css b/packages/webui/src/styles/variables.css
index d22814aea..ddecb31e5 100644
--- a/packages/webui/src/styles/variables.css
+++ b/packages/webui/src/styles/variables.css
@@ -4,6 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
/**
* Default CSS variables for @qwen-code/webui
* These provide fallback values when running outside of VSCode.
diff --git a/packages/webui/src/styles/webview.css b/packages/webui/src/styles/webview.css
deleted file mode 100644
index a3a3a6d13..000000000
--- a/packages/webui/src/styles/webview.css
+++ /dev/null
@@ -1,178 +0,0 @@
-/**
- * @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;
-}
-
diff --git a/packages/webui/vite.config.ts b/packages/webui/vite.config.ts
index 9edb5b28c..59837c8d8 100644
--- a/packages/webui/vite.config.ts
+++ b/packages/webui/vite.config.ts
@@ -15,12 +15,16 @@ import { resolve } from 'path';
* Build outputs:
* - ESM: dist/index.js (primary format)
* - CJS: dist/index.cjs (compatibility)
+ * - UMD: dist/index.umd.js (for CDN usage)
* - TypeScript declarations: dist/index.d.ts
* - CSS: dist/styles.css (optional styles)
*/
export default defineConfig({
plugins: [
- react(),
+ // Use the plugin with development mode settings for UMD builds
+ react({
+ jsxRuntime: 'classic', // Use classic JSX runtime for better CDN compatibility
+ }),
dts({
include: ['src'],
outDir: 'dist',
@@ -32,16 +36,20 @@ export default defineConfig({
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'QwenCodeWebUI',
- formats: ['es', 'cjs'],
- fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
+ formats: ['es', 'cjs', 'umd'],
+ fileName: (format) => {
+ if (format === 'es') return 'index.js';
+ if (format === 'cjs') return 'index.cjs';
+ if (format === 'umd') return 'index.umd.js';
+ return 'index.js';
+ },
},
rollupOptions: {
- external: ['react', 'react-dom', 'react/jsx-runtime'],
+ external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
- 'react/jsx-runtime': 'jsxRuntime',
},
assetFileNames: 'styles.[ext]',
},