mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 19:52:02 +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
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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue