mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 11:41:04 +00:00
feat(webui): Add UMD build format and CDN usage support
This commit is contained in:
parent
1f9311d3ca
commit
8d2d29a422
13 changed files with 796 additions and 189 deletions
|
|
@ -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
55
packages/webui/.npmignore
Normal 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/**
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
8
packages/webui/examples/README.md
Normal file
8
packages/webui/examples/README.md
Normal 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
|
||||||
142
packages/webui/examples/cdn-usage-demo.html
Normal file
142
packages/webui/examples/cdn-usage-demo.html
Normal 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>
|
||||||
413
packages/webui/examples/complex-chat-demo.html
Normal file
413
packages/webui/examples/complex-chat-demo.html
Normal 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><script src="https://cdn.tailwindcss.com"></script></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>
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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]',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue