A React chat application with fully custom UI components built on top of BotFramework WebChat's hooks layer. Supports Microsoft Copilot Studio agents via Direct Line or the M365 Agents SDK.
When building a custom chat UI for Copilot Studio, there are three main options:
| Approach | Pros | Cons |
|---|---|---|
| StyleOptions | Easy to configure | Limited to colors, fonts, sizes. Can't change layout or add custom components. |
| Fully Custom UI | Complete control | Must reimplement connection handling, activity management, typing indicators, streaming, etc. |
| Composer + Hooks | Full styling control + WebChat handles the hard parts | Requires understanding the hooks API |
This project uses Composer + Hooks - we get complete control over the UI while WebChat manages connections (Direct Line or M365 Agents SDK), activity state, and the messaging protocol. Best of both worlds.
See it in action without any agent configuration:
npm install
npm run devOpen http://localhost:5173/?demo
This runs an interactive HR assistant demo that showcases streaming text, adaptive cards, markdown tables, and suggested actions - all client-side.
http://localhost:5173/?demo
- No agent connection required - runs entirely client-side
- Interactive guided walkthrough of an HR assistant scenario
- Demonstrates all UI features: streaming, adaptive cards, markdown tables, suggested actions
http://localhost:5173/
- Connects to Copilot Studio via Direct Line token endpoint
- Messages appear when complete (no streaming)
- Requires
VITE_TOKEN_ENDPOINTin.env - Note: Currently only supports unauthenticated traffic (no user sign-in)
http://localhost:5173/?m365
- Uses
@microsoft/agents-copilotstudio-client - Real-time streaming - text appears as it's generated
- Supports authenticated users via MSAL popup
- Requires Azure app registration (see setup below)
- In Copilot Studio, go to Settings > Security > Web channel security
- Copy the Token endpoint URL
- Add to
.env:VITE_TOKEN_ENDPOINT=https://your-environment.api.powerplatform.com/.../directline/token
Limitation: This implementation currently only supports unauthenticated Direct Line connections. User authentication would require additional token exchange logic.
The M365 SDK mode requires an Azure app registration to authenticate users and call the Copilot Studio API.
- Go to Azure Portal > Microsoft Entra ID > App registrations
- Click New registration
- Configure:
- Name:
Copilot Studio Web Client(or your preferred name) - Supported account types: Accounts in this organizational directory only
- Redirect URI: Select Single-page application (SPA) and enter
http://localhost:5173
- Name:
- Click Register
- Copy the Application (client) ID and Directory (tenant) ID
- In your app registration, go to API permissions
- Click Add a permission > APIs my organization uses
- Search for
CopilotStudioand select CopilotStudio - Select Delegated permissions
- Check
CopilotStudio.Copilots.Invoke - Click Add permissions
- Click Grant admin consent (requires admin)
- In Copilot Studio, open your agent
- Go to Settings > Advanced > Metadata
- Copy:
- Environment ID (GUID from the environment URL)
- Schema name (e.g.,
cr123_myAgent)
Create .env file:
VITE_ENVIRONMENT_ID=12345678-1234-1234-1234-123456789012
VITE_AGENT_IDENTIFIER=cr123_myAgent
VITE_TENANT_ID=your-azure-tenant-id
VITE_APP_CLIENT_ID=your-azure-app-client-idnpm run devOpen http://localhost:5173/?m365 - you'll be prompted to sign in via Microsoft popup.
Instead of using WebChat's pre-built <ReactWebChat> component, this project uses the Composer + Hooks pattern:
┌─────────────────────────────────────────┐
│ Custom UI Layer │
│ (ChatTranscript, SendBox, Typing...) │
├─────────────────────────────────────────┤
│ WebChat Hooks Layer │
│ (useActivities, useSendMessage, etc.) │
├─────────────────────────────────────────┤
│ Composer Context │
│ (Provides hooks to descendants) │
├─────────────────────────────────────────┤
│ DirectLine / Mock Layer │
│ (Connection to agent or simulation) │
└─────────────────────────────────────────┘
The <Composer> component wraps WebChat's state machine in a React Context. Custom components access chat functionality via hooks like useActivities() and useSendMessage(), giving us full styling control without fighting WebChat's dynamically generated class names.
For detailed documentation, see ARCHITECTURE.md.
| File | Purpose |
|---|---|
src/App.jsx |
Direct Line mode entry point |
src/AppM365.jsx |
M365 Agents SDK mode entry point |
src/AppDemo.jsx |
Demo mode entry point |
src/components/ChatTranscript.jsx |
Message display with streaming support |
src/components/SendBox.jsx |
Text input and send button |
src/components/MessageAttachments.jsx |
Adaptive card rendering |
src/demo/ |
Mock DirectLine and demo scenario |
