packages/webui/README.md
A shared React component library for Qwen Code applications, providing cross-platform UI components with consistent styling and behavior.
npm install @qwen-code/webui
You can also use this library directly in the browser via CDN:
<!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/[email protected]/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/[email protected]/dist/index.umd.js"></script>
<!-- Load the CSS -->
<link
rel="stylesheet"
href="https://unpkg.com/@qwen-code/[email protected]/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= />
);
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
</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/[email protected]/dist/index.umd.js"></script>
<!-- Load the CSS -->
<link
rel="stylesheet"
href="https://unpkg.com/@qwen-code/[email protected]/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.
import { Button, Input, Tooltip } from '@qwen-code/webui';
import { PlatformProvider } from '@qwen-code/webui/context';
function App() {
return (
<PlatformProvider value={platformContext}>
<Button variant="primary" onClick={handleClick}>
Click me
</Button>
</PlatformProvider>
);
}
@qwen-code/webui/daemon-react-sdk)All daemon-related React bindings (Providers, hooks, types) are published under the daemon-react-sdk sub-path. The main entry (@qwen-code/webui) is purely UI components with zero daemon dependency.
import {
DaemonSessionProvider,
DaemonWorkspaceProvider,
useTranscriptBlocks,
useConnection,
useActions,
useStreamingState,
} from '@qwen-code/webui/daemon-react-sdk';
Two providers, split by lifecycle axis:
DaemonSessionProvider — per-conversation: SSE connection, transcript store, prompt/cancel/model/approval-mode/permission actions.DaemonWorkspaceProvider — per-workspace (outlives sessions): MCP, skills, tools, memory, agents, files.<DaemonWorkspaceProvider> ← owns DaemonClient + capabilities
useMcp / useAgents / useMemory / useTools / ...
├── <DaemonSessionProvider> ← owns session + SSE + transcript store
│ useTranscriptBlocks / useActions / useConnection / useStreamingState / ...
│ ├── <ChatPanel />
│ └── <TerminalPanel />
import {
DaemonSessionProvider,
DaemonWorkspaceProvider,
useTranscriptBlocks,
useActions,
useConnection,
} from '@qwen-code/webui/daemon-react-sdk';
function App() {
return (
<DaemonWorkspaceProvider baseUrl="http://127.0.0.1:4170" token={token}>
<DaemonSessionProvider autoReconnect>
<ChatView />
</DaemonSessionProvider>
</DaemonWorkspaceProvider>
);
}
function ChatView() {
const blocks = useTranscriptBlocks();
const { sendPrompt, cancel } = useActions();
const { status, sessionId, currentModel } = useConnection();
// render blocks, handle input...
}
Wrap both views with a single <DaemonSessionProvider>. Both panels share one SSE connection and one transcript store.
<DaemonWorkspaceProvider baseUrl={baseUrl} token={token}>
<DaemonSessionProvider autoReconnect>
<ChatPanel />
<TerminalPanel />
</DaemonSessionProvider>
</DaemonWorkspaceProvider>
Do NOT nest multiple <DaemonSessionProvider> for the same session — that creates two SSE connections and potential state divergence.
| Hook | Returns |
|---|---|
useTranscriptBlocks() | readonly DaemonTranscriptBlock[] (raw blocks) |
useTranscriptState() | Full DaemonTranscriptState (blocks + metadata) |
useActions() | { sendPrompt, cancel, setModel, setApprovalMode, respondToPermission, loadSession, newSession, ... } |
useConnection() | { status, sessionId, currentModel, currentMode, commands, skills, models, tokenCount, contextWindow } |
useStreamingState() | 'idle' | 'waiting' | 'responding' | 'thinking' |
usePromptStatus() | 'idle' | 'waiting' | 'streaming' |
usePendingPermissions() | Unresolved permission blocks |
useActiveTodoList() | Latest todo list, only when it still has active items |
Require an ancestor <DaemonWorkspaceProvider>:
| Hook | Description |
|---|---|
useMcp(options?) | MCP server list + restart + tools |
useSkills(options?) | Available skills (read-only) |
useTools(options?) | Workspace tools + enable/disable |
useMemory(options?) | Memory files + read/write |
useAgents(options?) | Agent CRUD |
useSessions(options?) | Session list (switch/new/release require nested DaemonSessionProvider) |
useFiles() | File operations: glob, read, write, edit, stat |
useGlob() | globWorkspace(pattern, opts) |
useWorkspace() | Full workspace context value |
useWorkspaceActions() | All workspace-level actions |
All resource hooks accept { autoLoad?: boolean, enabled?: boolean } and return { data, loading, error, reload }. When nested under an active DaemonSessionProvider, resource hooks also refresh from daemon workspace events that are already broadcast on the session stream (memory_changed, agent_changed, tool_toggled, MCP restart events, and workspace init events). Without an active session, hooks remain pull-based.
DaemonSessionProviderProps:
| Prop | Type | Default | Description |
|---|---|---|---|
baseUrl | string? | inherited | Daemon HTTP base URL (inherited from DaemonWorkspaceProvider when nested; required in standalone mode) |
token | string? | inherited | Bearer token (inherited from DaemonWorkspaceProvider when nested) |
workspaceCwd | string? | — | Override workspace path (uses capabilities if omitted) |
initialSessionId | string? | — | Restore a specific session on mount |
clientId | string? | — | Override stable client ID (auto-generated if omitted) |
autoConnect | boolean | true | Connect on mount |
autoReconnect | boolean | true | Auto-reconnect on disconnect |
reconnectDelayMs | number | 1000 | Initial reconnect backoff |
maxReconnectDelayMs | number | 10000 | Max reconnect backoff |
suppressOwnUserEcho | boolean | true | Suppress own user message echoes |
DaemonWorkspaceProviderProps:
| Prop | Type | Default | Description |
|---|---|---|---|
baseUrl | string | required | Daemon HTTP base URL |
token | string? | — | Bearer token |
workspaceCwd | string? | — | Override workspace path |
autoConnect | boolean | true | Connect and fetch capabilities on mount |
import { Button } from '@qwen-code/webui';
<Button variant="primary" size="md" loading={false}>
Submit
</Button>;
Props:
variant: 'primary' | 'secondary' | 'danger' | 'ghost' | 'outline'size: 'sm' | 'md' | 'lg'loading: booleanleftIcon: ReactNoderightIcon: ReactNodefullWidth: booleanimport { Input } from '@qwen-code/webui';
<Input
label="Email"
placeholder="Enter email"
error={hasError}
errorMessage="Invalid email"
/>;
Props:
size: 'sm' | 'md' | 'lg'error: booleanerrorMessage: stringlabel: stringhelperText: stringleftElement: ReactNoderightElement: ReactNodeimport { Tooltip } from '@qwen-code/webui';
<Tooltip content="Helpful tip">
<span>Hover me</span>
</Tooltip>;
import { FileIcon, FolderIcon, CheckIcon } from '@qwen-code/webui/icons';
<FileIcon size={16} className="text-gray-500" />;
Available icon categories:
Container: Main layout wrapperHeader: Application headerFooter: Application footerSidebar: Side navigationMain: Main content areaMessage: Chat message displayMessageList: List of messagesMessageInput: Message input fieldWaitingMessage: Loading/waiting stateInterruptedMessage: Interrupted state displayThe Platform Context provides an abstraction layer for platform-specific capabilities:
import { PlatformProvider, usePlatform } from '@qwen-code/webui/context';
const platformContext = {
postMessage: (message) => vscode.postMessage(message),
onMessage: (handler) => {
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
},
openFile: (path) => {
/* platform-specific */
},
platform: 'vscode',
};
function App() {
return (
<PlatformProvider value={platformContext}>
<YourApp />
</PlatformProvider>
);
}
function Component() {
const { postMessage, platform } = usePlatform();
// Use platform capabilities
}
Use the shared Tailwind preset for consistent styling:
// tailwind.config.js
module.exports = {
presets: [require('@qwen-code/webui/tailwind.preset.cjs')],
// your customizations
};
cd packages/webui
npm run storybook
npm run build
npm run typecheck
packages/webui/
├── src/
│ ├── components/
│ │ ├── icons/ # Icon components
│ │ ├── layout/ # Layout components
│ │ ├── messages/ # Message components
│ │ └── ui/ # UI primitives
│ ├── context/ # Platform context
│ ├── hooks/ # Custom hooks
│ └── types/ # Type definitions
├── .storybook/ # Storybook config
├── tailwind.preset.cjs # Shared Tailwind preset
└── vite.config.ts # Build configuration
Apache-2.0