besser-agentic-framework-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/dist/besser-agentic-framework-ui.js +1645 -0
- package/dist/components/ChatArea.d.ts +10 -0
- package/dist/components/GUIRenderer.d.ts +6 -0
- package/dist/components/MessageBubble.d.ts +8 -0
- package/dist/components/ReasoningTrace.d.ts +19 -0
- package/dist/hooks/useWebSocket.d.ts +14 -0
- package/dist/lib.d.ts +15 -0
- package/dist/style.css +2 -0
- package/dist/types/agent.d.ts +12 -0
- package/dist/types/payload.d.ts +30 -0
- package/dist/types/reasoningStep.d.ts +79 -0
- package/dist/utils/contentDetect.d.ts +36 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BESSER-PEARL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# BESSER Agentic Framework UI
|
|
2
|
+
|
|
3
|
+
A ChatGPT-like web frontend for interacting with agents built on the [BESSER Agentic Framework (BAF)](https://github.com/BESSER-PEARL/BESSER-Agentic-Framework). It supports all BAF payload types, session persistence via PostgreSQL, and real-time communication over WebSockets.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Tech stack
|
|
8
|
+
|
|
9
|
+
| Layer | Technology |
|
|
10
|
+
|---|---|
|
|
11
|
+
| UI framework | React 19 + TypeScript |
|
|
12
|
+
| Build tool | Vite 8 |
|
|
13
|
+
| Styling | Pure CSS (custom properties, light/dark via `prefers-color-scheme`) |
|
|
14
|
+
| WebSocket | Native browser `WebSocket` API |
|
|
15
|
+
| DB access | `pg` (node-postgres), run server-side inside a Vite plugin |
|
|
16
|
+
| Markdown rendering | `react-markdown` + `remark-gfm` |
|
|
17
|
+
| HTML sanitisation | `DOMPurify` |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Repository structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
src/
|
|
25
|
+
├── App.tsx # Root component — routing between AgentSelector and ChatLayout
|
|
26
|
+
├── App.css # All component styles (no CSS framework)
|
|
27
|
+
├── index.css # CSS variables + global reset
|
|
28
|
+
├── main.tsx # React entry point
|
|
29
|
+
├── config.ts # (reserved for future config)
|
|
30
|
+
│
|
|
31
|
+
├── types/
|
|
32
|
+
│ ├── payload.ts # PayloadAction constants + Payload interface
|
|
33
|
+
│ └── agent.ts # Agent and ChatMessage interfaces
|
|
34
|
+
│
|
|
35
|
+
├── hooks/
|
|
36
|
+
│ └── useWebSocket.ts # WebSocket lifecycle hook
|
|
37
|
+
│
|
|
38
|
+
├── services/
|
|
39
|
+
│ └── db.ts # PostgreSQL query service (server-side only)
|
|
40
|
+
│
|
|
41
|
+
└── components/
|
|
42
|
+
├── AgentSelector.tsx # Landing page: agent list + add/remove agents + username input
|
|
43
|
+
├── ChatLayout.tsx # Main chat shell: header, sidebar, chat area
|
|
44
|
+
├── SessionSidebar.tsx # Left sidebar: session list, new session controls
|
|
45
|
+
├── ChatArea.tsx # Message list + text input
|
|
46
|
+
└── MessageBubble.tsx # Renders a single message for every payload type
|
|
47
|
+
|
|
48
|
+
vite-plugin-baf.ts # Vite dev-server plugin: exposes /api/sessions REST endpoint
|
|
49
|
+
vite.config.ts # Vite config — registers bafPlugin()
|
|
50
|
+
.env.example # Template for DB credentials
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Module descriptions
|
|
56
|
+
|
|
57
|
+
### `App.tsx` — Root router
|
|
58
|
+
|
|
59
|
+
Holds two pieces of global state persisted in `localStorage`:
|
|
60
|
+
|
|
61
|
+
- `baf_agents` — the list of configured agents (name, WebSocket URL)
|
|
62
|
+
- `baf_username` — the currently entered username
|
|
63
|
+
|
|
64
|
+
Renders either `<AgentSelector>` (no agent selected) or `<ChatLayout>` (agent selected). The `username` flows down as a prop to both.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### `types/payload.ts` — Payload contract
|
|
69
|
+
|
|
70
|
+
Defines the shared message format used by BAF's WebSocket protocol:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
interface Payload {
|
|
74
|
+
action: string // one of PayloadAction values
|
|
75
|
+
message: unknown // content varies by action
|
|
76
|
+
history?: boolean // true when the message is fetched from history, not a live event
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`PayloadAction` is a `const` object (not an enum, due to `erasableSyntaxOnly: true` in tsconfig) with all supported action strings. User-originated actions start with `user_`; agent replies start with `agent_reply_`.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### `hooks/useWebSocket.ts` — WebSocket lifecycle
|
|
85
|
+
|
|
86
|
+
Manages a single WebSocket connection. Accepts a URL (`null` = disconnected) and an options object:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
{
|
|
90
|
+
onMessage(payload) // called for all live messages (history === false)
|
|
91
|
+
onHistoryMessage(payload) // called for fetched history messages (history === true)
|
|
92
|
+
onOpen() // called when the connection is established
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Key design decisions:
|
|
97
|
+
- The URL is the only dependency of the effect — changing it closes the old connection and opens a new one.
|
|
98
|
+
- Callbacks are stored in a `ref` so they never cause the effect to re-run, yet always execute the latest closure.
|
|
99
|
+
- `send()` and `disconnect()` are stable `useCallback` references.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### `services/db.ts` — PostgreSQL access
|
|
104
|
+
|
|
105
|
+
**This module is Node.js-only and cannot run in the browser.** It is imported exclusively by `vite-plugin-baf.ts`, which runs on the Vite server process.
|
|
106
|
+
|
|
107
|
+
`createDb(credentials)` returns a query object with `getSessions(agentName, username?)`, which queries the BAF monitoring database:
|
|
108
|
+
|
|
109
|
+
```sql
|
|
110
|
+
SELECT id, session_id, session_name, platform_name, timestamp
|
|
111
|
+
FROM session
|
|
112
|
+
WHERE agent_name = $1 [AND username = $2]
|
|
113
|
+
ORDER BY timestamp DESC
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The `session` table is created by BAF and contains one row per agent session.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### `vite-plugin-baf.ts` — Dev-server API
|
|
121
|
+
|
|
122
|
+
A Vite plugin that registers a middleware on the same port as the dev server. This avoids CORS issues and the need for a separate backend process.
|
|
123
|
+
|
|
124
|
+
Exposes one endpoint:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
GET /api/sessions?agent_name=<name>&username=<user>
|
|
128
|
+
→ SessionRecord[]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
On startup it reads `.env` for DB credentials, creates a `pg` connection pool, and keeps it alive for the duration of the dev session.
|
|
132
|
+
|
|
133
|
+
> In production builds, a real backend server must serve this endpoint.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### `components/AgentSelector.tsx` — Landing page
|
|
138
|
+
|
|
139
|
+
Shows a card grid of configured agents. Each card displays the agent name and WebSocket URL. Agents are added via a modal (name + WebSocket URL, e.g. `ws://localhost:8765`) and are persisted to `localStorage`.
|
|
140
|
+
|
|
141
|
+
A **Username** input at the top of the page sets the global username used for session filtering. It is also persisted to `localStorage`.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### `components/ChatLayout.tsx` — Chat shell
|
|
146
|
+
|
|
147
|
+
The central orchestrator once an agent is selected. Responsibilities:
|
|
148
|
+
|
|
149
|
+
- Builds the WebSocket URL with query parameters (`user_id`, and optionally `session_id` or `session_name`)
|
|
150
|
+
- Owns the `messages` array and the `selectedSession` state
|
|
151
|
+
- Drives `useWebSocket` — passing `fetchOnOpen: true` when connecting to an existing session causes `FETCH_USER_MESSAGES` to be sent immediately on connection open
|
|
152
|
+
- Routes history payloads (arriving right after `FETCH_USER_MESSAGES`) to prepend existing messages, distinguishing user vs agent by checking the action type
|
|
153
|
+
- Delegates to `SessionSidebar` (left) and `ChatArea` (right)
|
|
154
|
+
|
|
155
|
+
**WebSocket URL construction:**
|
|
156
|
+
|
|
157
|
+
| Scenario | URL parameters sent |
|
|
158
|
+
|---|---|
|
|
159
|
+
| New session, no name | `user_id` only |
|
|
160
|
+
| New session, with name | `user_id` + `session_name` |
|
|
161
|
+
| Existing session selected | `user_id` + `session_id` |
|
|
162
|
+
|
|
163
|
+
> The browser `WebSocket` API cannot send HTTP headers. `user_id` is passed as a query parameter instead of a header.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### `components/SessionSidebar.tsx` — Session list
|
|
168
|
+
|
|
169
|
+
Polls `/api/sessions` every **5 seconds** to keep the session list up to date. At the top of the sidebar, a text input and "+ New session" button allow creating or resuming sessions without leaving the chat view.
|
|
170
|
+
|
|
171
|
+
Each session item shows:
|
|
172
|
+
- Session name (if set) or session ID
|
|
173
|
+
- Raw session ID (shown below the name when a name exists)
|
|
174
|
+
- Formatted timestamp
|
|
175
|
+
|
|
176
|
+
Clicking a session item calls `onSelectSession`, which in `ChatLayout` either sends `FETCH_USER_MESSAGES` directly (if already connected to that session) or opens a new WebSocket connection that fetches history on open.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### `components/ChatArea.tsx` — Message list + input
|
|
181
|
+
|
|
182
|
+
Renders the scrollable message list and the text input bar. The textarea auto-resizes, supports Enter-to-send (Shift+Enter for newline), and is disabled when the WebSocket is not connected.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### `components/MessageBubble.tsx` — Message renderer
|
|
187
|
+
|
|
188
|
+
Renders a single `ChatMessage`. User messages appear as plain-text right-aligned bubbles. Agent messages are routed by `action` to a specific renderer:
|
|
189
|
+
|
|
190
|
+
| Action | Renderer |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `agent_reply_str` | Plain text |
|
|
193
|
+
| `agent_reply_markdown` | `react-markdown` with GFM |
|
|
194
|
+
| `agent_reply_html` | `DOMPurify`-sanitised `dangerouslySetInnerHTML` |
|
|
195
|
+
| `agent_reply_file` | Download link |
|
|
196
|
+
| `agent_reply_image` | `<img>` from base64 |
|
|
197
|
+
| `agent_reply_dataframe` | HTML table |
|
|
198
|
+
| `agent_reply_plotly` | Plotly chart (via `dangerouslySetInnerHTML`) |
|
|
199
|
+
| `agent_reply_options` | Clickable option buttons |
|
|
200
|
+
| `agent_reply_location` | Google Maps link |
|
|
201
|
+
| `agent_reply_rag` | Answer + collapsible source documents |
|
|
202
|
+
| `agent_reply_audio` | `<audio>` player from base64 |
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Setup
|
|
207
|
+
|
|
208
|
+
1. Copy `.env.example` to `.env` and fill in your PostgreSQL credentials:
|
|
209
|
+
```
|
|
210
|
+
DB_HOST=localhost
|
|
211
|
+
DB_PORT=5432
|
|
212
|
+
DB_NAME=your_db
|
|
213
|
+
DB_USER=your_user
|
|
214
|
+
DB_PASSWORD=your_password
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
2. Install dependencies:
|
|
218
|
+
```bash
|
|
219
|
+
npm install
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
3. Start the dev server:
|
|
223
|
+
```bash
|
|
224
|
+
npm run dev
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The app and the `/api/sessions` endpoint both run on the same Vite port (default `5173`).
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
export default defineConfig([
|
|
232
|
+
globalIgnores(['dist']),
|
|
233
|
+
{
|
|
234
|
+
files: ['**/*.{ts,tsx}'],
|
|
235
|
+
extends: [
|
|
236
|
+
// Other configs...
|
|
237
|
+
|
|
238
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
239
|
+
tseslint.configs.recommendedTypeChecked,
|
|
240
|
+
// Alternatively, use this for stricter rules
|
|
241
|
+
tseslint.configs.strictTypeChecked,
|
|
242
|
+
// Optionally, add this for stylistic rules
|
|
243
|
+
tseslint.configs.stylisticTypeChecked,
|
|
244
|
+
|
|
245
|
+
// Other configs...
|
|
246
|
+
],
|
|
247
|
+
languageOptions: {
|
|
248
|
+
parserOptions: {
|
|
249
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
250
|
+
tsconfigRootDir: import.meta.dirname,
|
|
251
|
+
},
|
|
252
|
+
// other options...
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
])
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
259
|
+
|
|
260
|
+
```js
|
|
261
|
+
// eslint.config.js
|
|
262
|
+
import reactX from 'eslint-plugin-react-x'
|
|
263
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
264
|
+
|
|
265
|
+
export default defineConfig([
|
|
266
|
+
globalIgnores(['dist']),
|
|
267
|
+
{
|
|
268
|
+
files: ['**/*.{ts,tsx}'],
|
|
269
|
+
extends: [
|
|
270
|
+
// Other configs...
|
|
271
|
+
// Enable lint rules for React
|
|
272
|
+
reactX.configs['recommended-typescript'],
|
|
273
|
+
// Enable lint rules for React DOM
|
|
274
|
+
reactDom.configs.recommended,
|
|
275
|
+
],
|
|
276
|
+
languageOptions: {
|
|
277
|
+
parserOptions: {
|
|
278
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
279
|
+
tsconfigRootDir: import.meta.dirname,
|
|
280
|
+
},
|
|
281
|
+
// other options...
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
])
|
|
285
|
+
```
|