feat(webui): Add UMD build format and CDN usage support

This commit is contained in:
yiliang114 2026-01-22 15:47:56 +08:00
parent 1f9311d3ca
commit 8d2d29a422
13 changed files with 796 additions and 189 deletions

View file

@ -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'], files: ['packages/vscode-ide-companion/esbuild.js'],
languageOptions: { languageOptions: {

55
packages/webui/.npmignore Normal file
View file

@ -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/**

View file

@ -9,6 +9,8 @@ A shared React component library for Qwen Code applications, providing cross-pla
- **Tailwind CSS**: Shared styling preset for consistent design - **Tailwind CSS**: Shared styling preset for consistent design
- **TypeScript**: Full type definitions for all components - **TypeScript**: Full type definitions for all components
- **Storybook**: Interactive component documentation and development - **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 ## Installation
@ -16,6 +18,141 @@ A shared React component library for Qwen Code applications, providing cross-pla
npm install @qwen-code/webui 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
<!DOCTYPE html>
<html>
<head>
<!-- Load React -->
<script
crossorigin
src="https://unpkg.com/react@18/umd/react.production.min.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
></script>
<!-- Load Babel Standalone for JSX processing -->
<script src="https://unpkg.com/@babel/standalone@7.23.6/babel.min.js"></script>
<!-- Manually create the jsxRuntime object to satisfy the dependency -->
<script>
// Provide a minimal JSX runtime for builds that expect react/jsx-runtime globals.
const withKey = (props, key) =>
key == null ? props : Object.assign({}, props, { key });
const jsx = (type, props, key) =>
React.createElement(type, withKey(props, key));
const jsxRuntime = {
Fragment: React.Fragment,
jsx,
jsxs: jsx,
jsxDEV: jsx,
};
window.ReactJSXRuntime = jsxRuntime;
window['react/jsx-runtime'] = jsxRuntime;
window['react/jsx-dev-runtime'] = jsxRuntime;
</script>
<!-- Load the webui library -->
<script src="https://unpkg.com/@qwen-code/webui@0.1.0-beta.2/dist/index.umd.js"></script>
<!-- Load the CSS -->
<link
rel="stylesheet"
href="https://unpkg.com/@qwen-code/webui@0.1.0-beta.2/dist/styles.css"
/>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
// Access components from the global QwenCodeWebUI object
const { ChatViewer } = QwenCodeWebUI;
// Use the components with JSX support
const App = () => (
<ChatViewer messages={/* your messages */} />
);
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
</html>
```
### Option 2: Without JSX (using React.createElement directly)
```html
<!DOCTYPE html>
<html>
<head>
<!-- Load React -->
<script
crossorigin
src="https://unpkg.com/react@18/umd/react.production.min.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
></script>
<!-- Manually create the jsxRuntime object to satisfy the dependency -->
<script>
// Provide a minimal JSX runtime for builds that expect react/jsx-runtime globals.
const withKey = (props, key) =>
key == null ? props : Object.assign({}, props, { key });
const jsx = (type, props, key) =>
React.createElement(type, withKey(props, key));
const jsxRuntime = {
Fragment: React.Fragment,
jsx,
jsxs: jsx,
jsxDEV: jsx,
};
window.ReactJSXRuntime = jsxRuntime;
window['react/jsx-runtime'] = jsxRuntime;
window['react/jsx-dev-runtime'] = jsxRuntime;
</script>
<!-- Load the webui library -->
<script src="https://unpkg.com/@qwen-code/webui@0.1.0-beta.2/dist/index.umd.js"></script>
<!-- Load the CSS -->
<link
rel="stylesheet"
href="https://unpkg.com/@qwen-code/webui@0.1.0-beta.2/dist/styles.css"
/>
</head>
<body>
<div id="root"></div>
<script>
// Access components from the global QwenCodeWebUI object
const { ChatViewer } = QwenCodeWebUI;
// Use the components with React.createElement (no JSX)
const App = React.createElement(ChatViewer, {
messages: [
/* your messages */
],
});
ReactDOM.render(App, document.getElementById('root'));
</script>
</body>
</html>
```
For a complete working example, see [examples/cdn-usage-demo.html](./examples/cdn-usage-demo.html).
## Quick Start ## Quick Start
```tsx ```tsx

View file

@ -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

View file

@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@qwen-code/webui CDN Usage Example</title>
<!-- Load React and ReactDOM from CDN -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<!-- Manually create the jsxRuntime object to satisfy the dependency -->
<script>
// Provide a minimal JSX runtime for builds that expect react/jsx-runtime globals.
const withKey = (props, key) =>
key == null ? props : Object.assign({}, props, { key });
const jsx = (type, props, key) => React.createElement(type, withKey(props, key));
const jsxRuntime = {
Fragment: React.Fragment,
jsx,
jsxs: jsx,
jsxDEV: jsx
};
window.ReactJSXRuntime = jsxRuntime;
window['react/jsx-runtime'] = jsxRuntime;
window['react/jsx-dev-runtime'] = jsxRuntime;
</script>
<!-- Load the webui library from CDN -->
<script src="https://unpkg.com/@qwen-code/webui@0.1.0-beta.2/dist/index.umd.js"></script>
<!-- Load the CSS -->
<link rel="stylesheet" href="https://unpkg.com/@qwen-code/webui@0.1.0-beta.2/dist/styles.css">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.chat-container {
height: 500px;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
</style>
</head>
<body>
<div class="container">
<h1>@qwen-code/webui CDN Usage Example</h1>
<h2>ChatViewer Component Demo</h2>
<div id="chat-root-no-babel" class="chat-container"></div>
</div>
<script>
// Same sample messages
const sampleMessages = [
{
uuid: '1',
type: 'user',
timestamp: new Date().toISOString(),
message: {
role: 'user',
parts: [{ text: 'Hello, how can I use the ChatViewer component without Babel?' }]
}
},
{
uuid: '2',
type: 'assistant',
timestamp: new Date(Date.now() + 1000).toISOString(),
message: {
role: 'assistant',
parts: [{ text: 'You can use React.createElement directly, without needing Babel.' }]
}
}
];
// Get the ChatViewer and Platform components from the global object
const { ChatViewer, PlatformProvider } = QwenCodeWebUI;
// Define a minimal platform context for web usage
const platformContext = {
platform: 'web',
postMessage: (message) => {
// In a web context, you might want to handle messages differently
console.log('Posted message:', message);
},
onMessage: (handler) => {
// In a web context, you might listen for custom events
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
},
openFile: (path) => {
console.log('Opening file:', path);
},
getResourceUrl: (resource) => {
// Return URLs for platform-specific resources
return null; // Use default resources
},
features: {
canOpenFile: false,
canCopy: true
}
};
// Render the ChatViewer component without Babel
const rootElementNoBabel = document.getElementById('chat-root-no-babel');
// Create the ChatViewer element wrapped with PlatformProvider using React.createElement (no JSX)
const ChatAppNoBabel = React.createElement(PlatformProvider, { value: platformContext },
React.createElement(ChatViewer, {
messages: sampleMessages,
autoScroll: true,
theme: 'light'
})
);
ReactDOM.render(ChatAppNoBabel, rootElementNoBabel);
</script>
</body>
</html>

View file

@ -0,0 +1,413 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@qwen-code/webui Complex Chat Demo</title>
<!-- Load React and ReactDOM from CDN -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<!-- Load the webui library from CDN -->
<script src="https://unpkg.com/@qwen-code/webui@0.1.0-beta.2/dist/index.umd.js"></script>
<!-- Load the CSS -->
<link rel="stylesheet" href="https://unpkg.com/@qwen-code/webui@0.1.0-beta.2/dist/styles.css">
<!-- Load Babel Standalone for JSX processing -->
<script src="https://unpkg.com/@babel/standalone@7.23.6/babel.min.js"></script>
<!-- Manually create the jsxRuntime object to satisfy the dependency -->
<script>
// Provide a minimal JSX runtime for builds that expect react/jsx-runtime globals.
const withKey = (props, key) =>
key == null ? props : Object.assign({}, props, { key });
const jsx = (type, props, key) => React.createElement(type, withKey(props, key));
const jsxRuntime = {
Fragment: React.Fragment,
jsx,
jsxs: jsx,
jsxDEV: jsx
};
window.ReactJSXRuntime = jsxRuntime;
window['react/jsx-runtime'] = jsxRuntime;
window['react/jsx-dev-runtime'] = jsxRuntime;
</script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
p {
margin: 0;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.chat-container {
height: 700px;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
</style>
</head>
<body>
<div class="container">
<h1>@qwen-code/webui Complex Chat Demo</h1>
<h2>Real conversation example with tool calls</h2>
<div id="complex-chat-root" class="chat-container"></div>
<h2>Alternative: With Full Tailwind Support</h2>
<p>For full Tailwind utility class support (like gap-1.5, button classes, etc.), also include:</p>
<pre>&lt;script src="https://cdn.tailwindcss.com"&gt;&lt;/script&gt;</pre>
</div>
<script type="text/babel">
// Complex real-world conversation example from Storybook
const complexChatMessages = [
{
uuid: '20fb9426-40ca-4e85-92a1-7add59082167',
parentUuid: null,
sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844',
timestamp: '2026-01-09T02:58:32.415Z',
type: 'user',
message: {
role: 'user',
content: `Message received: Object from: Object
service-worker.js:137 Attempting to connect to Native Host: com.qwen.cli.bridge
service-worker.js:202 Sending handshake...
service-worker.js:175 Native host disconnected
service-worker.js:177 Disconnect error: Object
service-worker.js:178 Disconnect error message: Native host has exited.
service-worker.js:207 Handshake timeout - no response from Native Host
接收不到 host 的信息。正常应该是:
service-worker.js:150 [Native Event] host_info Object
service-worker.js:552 [Qwen] host_info Object
service-worker.js:617 [Host] Info Object
service-worker.js:164 [Native Message] handshake_response Object
service-worker.js:231 Handshake successful: Object`,
},
},
{
uuid: '86f72c75-4626-4292-a26f-13db6f80d4be',
parentUuid: '20fb9426-40ca-4e85-92a1-7add59082167',
sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844',
timestamp: '2026-01-09T02:58:45.239Z',
type: 'assistant',
model: 'claude-opus-4-5-20251101',
message: {
role: 'assistant',
content: `I can see you're experiencing an issue with the Chrome extension's native host connection. The error shows that the native host is disconnecting with "Native host has exited" and there's a handshake timeout. However, the logs also show that sometimes the handshake is successful when the host_info is received properly.
Let me help you troubleshoot this issue. First, I'll explore the codebase to understand how the native host communication works.`,
},
},
{
uuid: '6ffff579-8020-4ccb-9b79-7c1da76bbf82',
parentUuid: '86f72c75-4626-4292-a26f-13db6f80d4be',
sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844',
timestamp: '2026-01-09T02:58:52.409Z',
type: 'assistant',
model: 'claude-opus-4-5-20251101',
message: {
role: 'assistant',
content: `Let me create a todo list to track the troubleshooting steps:
1. **Explore the codebase** to understand the native host implementation
2. **Check the native host configuration** and setup
3. **Analyze the service worker code** that handles native messaging
4. **Identify why the native host exits** unexpectedly
5. **Propose fixes** for the native host connection issue`,
},
},
{
uuid: '8fd3dc96-1d34-4dae-845a-4827f435b890',
parentUuid: '6ffff579-8020-4ccb-9b79-7c1da76bbf82',
sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844',
timestamp: '2026-01-09T02:58:57.366Z',
type: 'assistant',
model: 'claude-opus-4-5-20251101',
message: {
role: 'assistant',
content: `Let me start by exploring the codebase to understand the native host implementation.
Looking at the project structure, I found the following relevant files:
\`\`\`
packages/chrome-extension/
├── src/
│ ├── service-worker.ts # Main service worker
│ ├── native-messaging/
│ │ ├── host.ts # Native host communication
│ │ └── protocol.ts # Message protocol
│ └── manifest.json # Extension manifest
└── native-host/
├── index.js # Native host entry
└── com.qwen.cli.bridge.json # Host manifest
\`\`\`
The issue appears to be in the handshake timing. The native host is exiting before the handshake completes.`,
},
},
{
uuid: 'user-follow-up-1',
parentUuid: '8fd3dc96-1d34-4dae-845a-4827f435b890',
sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844',
timestamp: '2026-01-09T03:00:15.000Z',
type: 'user',
message: {
role: 'user',
content: '那应该怎么修复这个问题?',
},
},
{
uuid: 'assistant-fix-1',
parentUuid: 'user-follow-up-1',
sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844',
timestamp: '2026-01-09T03:00:45.000Z',
type: 'assistant',
model: 'claude-opus-4-5-20251101',
message: {
role: 'assistant',
content: `Based on my analysis, here's how to fix the native host connection issue:
## Root Cause
The native host process is exiting prematurely because it's not keeping the stdin stream open for communication.
## Solution
Update the native host's \`index.js\`:
\`\`\`javascript
// native-host/index.js
const { stdin, stdout } = process;
// Keep the process alive by not closing stdin
stdin.resume();
// Handle incoming messages
stdin.on('data', (data) => {
try {
const length = data.readUInt32LE(0);
const message = JSON.parse(data.slice(4, 4 + length).toString());
if (message.type === 'handshake') {
sendMessage({ type: 'handshake_response', status: 'ok' });
}
// Handle other messages...
} catch (err) {
console.error('Error parsing message:', err);
}
});
function sendMessage(msg) {
const json = JSON.stringify(msg);
const length = Buffer.alloc(4);
length.writeUInt32LE(json.length, 0);
stdout.write(Buffer.concat([length, Buffer.from(json)]));
}
// Send host info on startup
sendMessage({
type: 'host_info',
pid: process.pid,
node: process.execPath
});
\`\`\`
## Key Changes
1. Added \`stdin.resume()\` to prevent the process from exiting
2. Properly handle the native messaging protocol (4-byte length prefix)
3. Send \`host_info\` immediately on startup
Would you like me to apply these changes to your codebase?`,
},
},
];
// Tool call conversation example
const toolCallMessages = [
{
uuid: 'tc-1',
timestamp: '2026-01-15T14:00:00.000Z',
type: 'user',
message: {
role: 'user',
parts: [
{ text: '帮我看看 package.json 文件,然后添加一个 lodash 依赖' },
],
},
},
{
uuid: 'tc-2',
timestamp: '2026-01-15T14:00:05.000Z',
type: 'assistant',
message: {
role: 'assistant',
content: '好的,我先读取 package.json 文件来查看当前的依赖配置。',
},
},
{
uuid: 'tc-3',
timestamp: '2026-01-15T14:00:06.000Z',
type: 'tool_call',
toolCall: {
toolCallId: 'read-1',
kind: 'read',
title: 'package.json',
status: 'completed',
content: [
{
type: 'content',
content: {
type: 'text',
text: `{
"name": "my-project",
"version": "1.0.0",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"vite": "^5.0.0"
}
}`,
},
},
],
locations: [{ path: 'package.json', line: 1 }],
},
},
{
uuid: 'tc-4',
timestamp: '2026-01-15T14:00:10.000Z',
type: 'assistant',
message: {
role: 'assistant',
content: '我看到当前项目使用 React 18 和 TypeScript。现在我来添加 lodash 依赖。',
},
},
{
uuid: 'tc-5',
timestamp: '2026-01-15T14:00:11.000Z',
type: 'tool_call',
toolCall: {
toolCallId: 'edit-1',
kind: 'edit',
title: 'package.json',
status: 'completed',
content: [
{
type: 'diff',
path: 'package.json',
oldText: ` "dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},`,
newText: ` "dependencies": {
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},`,
},
],
locations: [{ path: 'package.json', line: 4 }],
},
},
{
uuid: 'tc-6',
timestamp: '2026-01-15T14:00:15.000Z',
type: 'assistant',
message: {
role: 'assistant',
content: `已经成功添加 lodash 依赖到 package.json。
你需要运行以下命令来安装新的依赖:
\`\`\`bash
npm install
\`\`\`
或者如果你使用 pnpm:
\`\`\`bash
pnpm install
\`\`\``,
},
},
];
// Combine all messages for a comprehensive example
const combinedMessages = [
...complexChatMessages,
...toolCallMessages
];
// Get the ChatViewer and Platform components from the global object
const { ChatViewer, PlatformProvider } = QwenCodeWebUI;
// Define a minimal platform context for web usage
const platformContext = {
platform: 'web',
postMessage: (message) => {
// In a web context, you might want to handle messages differently
console.log('Posted message:', message);
},
onMessage: (handler) => {
// In a web context, you might listen for custom events
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
},
openFile: (path) => {
console.log('Opening file:', path);
},
getResourceUrl: (resource) => {
// Return URLs for platform-specific resources
return null; // Use default resources
},
features: {
canOpenFile: false,
canCopy: true
}
};
// Render the ChatViewer component
const rootElement = document.getElementById('complex-chat-root');
// Create the ChatViewer element wrapped with PlatformProvider with complex data
const ChatApp = React.createElement(PlatformProvider, { value: platformContext },
React.createElement(ChatViewer, {
messages: combinedMessages,
autoScroll: true,
theme: 'light',
emptyMessage: 'Loading conversation...'
})
);
ReactDOM.render(ChatApp, rootElement);
</script>
</body>
</html>

View file

@ -1,6 +1,6 @@
{ {
"name": "@qwen-code/webui", "name": "@qwen-code/webui",
"version": "0.1.0", "version": "0.1.0-beta.2",
"description": "Shared UI components for Qwen Code packages", "description": "Shared UI components for Qwen Code packages",
"type": "module", "type": "module",
"main": "./dist/index.cjs", "main": "./dist/index.cjs",
@ -18,8 +18,7 @@
"require": "./dist/components/icons/index.cjs" "require": "./dist/components/icons/index.cjs"
}, },
"./tailwind.preset": "./tailwind.preset.cjs", "./tailwind.preset": "./tailwind.preset.cjs",
"./styles.css": "./dist/styles.css", "./styles.css": "./dist/styles.css"
"./webview.css": "./dist/webview.css"
}, },
"files": [ "files": [
"dist", "dist",
@ -28,6 +27,9 @@
"sideEffects": [ "sideEffects": [
"**/*.css" "**/*.css"
], ],
"publishConfig": {
"access": "public"
},
"scripts": { "scripts": {
"dev": "vite build --watch", "dev": "vite build --watch",
"build": "vite build", "build": "vite build",

View file

@ -50,7 +50,6 @@
.markdown-content p { .markdown-content p {
margin-top: 0; margin-top: 0;
/* margin-bottom: 1em; */
} }
.markdown-content ul, .markdown-content ul,

View file

@ -82,7 +82,8 @@ const parsePlanEntries = (textOutputs: string[]): PlanEntry[] => {
const entries: PlanEntry[] = []; const entries: PlanEntry[] = [];
// Accept [ ], [x]/[X] and in-progress markers [-] or [*] // 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) { for (const line of lines) {
const m = line.match(todoRe); const m = line.match(todoRe);
if (m) { if (m) {

View file

@ -43,7 +43,9 @@ export const extractCommandOutput = (text: string): string => {
} }
// Second try: Extract from structured text format // 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]) { if (outputMatch && outputMatch[1]) {
const output = outputMatch[1].trim(); const output = outputMatch[1].trim();
if (output && output !== '(none)' && output.length > 0) { if (output && output !== '(none)' && output.length > 0) {

View file

@ -4,6 +4,10 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@tailwind base;
@tailwind components;
@tailwind utilities;
/** /**
* Default CSS variables for @qwen-code/webui * Default CSS variables for @qwen-code/webui
* These provide fallback values when running outside of VSCode. * These provide fallback values when running outside of VSCode.

View file

@ -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;
}

View file

@ -15,12 +15,16 @@ import { resolve } from 'path';
* Build outputs: * Build outputs:
* - ESM: dist/index.js (primary format) * - ESM: dist/index.js (primary format)
* - CJS: dist/index.cjs (compatibility) * - CJS: dist/index.cjs (compatibility)
* - UMD: dist/index.umd.js (for CDN usage)
* - TypeScript declarations: dist/index.d.ts * - TypeScript declarations: dist/index.d.ts
* - CSS: dist/styles.css (optional styles) * - CSS: dist/styles.css (optional styles)
*/ */
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
react(), // Use the plugin with development mode settings for UMD builds
react({
jsxRuntime: 'classic', // Use classic JSX runtime for better CDN compatibility
}),
dts({ dts({
include: ['src'], include: ['src'],
outDir: 'dist', outDir: 'dist',
@ -32,16 +36,20 @@ export default defineConfig({
lib: { lib: {
entry: resolve(__dirname, 'src/index.ts'), entry: resolve(__dirname, 'src/index.ts'),
name: 'QwenCodeWebUI', name: 'QwenCodeWebUI',
formats: ['es', 'cjs'], formats: ['es', 'cjs', 'umd'],
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`, 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: { rollupOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime'], external: ['react', 'react-dom'],
output: { output: {
globals: { globals: {
react: 'React', react: 'React',
'react-dom': 'ReactDOM', 'react-dom': 'ReactDOM',
'react/jsx-runtime': 'jsxRuntime',
}, },
assetFileNames: 'styles.[ext]', assetFileNames: 'styles.[ext]',
}, },