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 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
+ ```