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]', },