@cnt-dev/chat-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/AGENTS.md +202 -0
- package/LICENSE +21 -0
- package/README.md +303 -0
- package/index.d.ts +23 -0
- package/package.json +41 -0
- package/resources/chatui/.library +14 -0
- package/resources/chatui/ChatBox-dbg.js +1099 -0
- package/resources/chatui/ChatBox-dbg.js.map +1 -0
- package/resources/chatui/ChatBox.d.ts +308 -0
- package/resources/chatui/ChatBox.d.ts.map +1 -0
- package/resources/chatui/ChatBox.gen.d.ts +545 -0
- package/resources/chatui/ChatBox.js +5 -0
- package/resources/chatui/ChatBox.js.map +1 -0
- package/resources/chatui/ChatBox.ts +1153 -0
- package/resources/chatui/library-dbg.js +28 -0
- package/resources/chatui/library-dbg.js.map +1 -0
- package/resources/chatui/library-preload.js +17 -0
- package/resources/chatui/library-preload.js.map +1 -0
- package/resources/chatui/library.d.ts +3 -0
- package/resources/chatui/library.d.ts.map +1 -0
- package/resources/chatui/library.js +5 -0
- package/resources/chatui/library.js.map +1 -0
- package/resources/chatui/library.ts +30 -0
- package/resources/chatui/manifest.json +52 -0
- package/resources/chatui/markdown-dbg.js +116 -0
- package/resources/chatui/markdown-dbg.js.map +1 -0
- package/resources/chatui/markdown.d.ts +21 -0
- package/resources/chatui/markdown.d.ts.map +1 -0
- package/resources/chatui/markdown.js +5 -0
- package/resources/chatui/markdown.js.map +1 -0
- package/resources/chatui/markdown.ts +140 -0
- package/resources/chatui/messagebundle.properties +20 -0
- package/resources/chatui/messagebundle_en.properties +20 -0
- package/resources/chatui/themes/base/ChatBox.less +702 -0
- package/resources/chatui/themes/base/library-RTL.css +3 -0
- package/resources/chatui/themes/base/library-parameters.json +1 -0
- package/resources/chatui/themes/base/library.css +3 -0
- package/resources/chatui/themes/base/library.source.less +4 -0
- package/resources/chatui/themes/sap_fiori_3/library-RTL.css +3 -0
- package/resources/chatui/themes/sap_fiori_3/library-parameters.json +1 -0
- package/resources/chatui/themes/sap_fiori_3/library.css +3 -0
- package/resources/chatui/themes/sap_fiori_3/library.source.less +3 -0
- package/resources/chatui/themes/sap_fiori_3_dark/library-RTL.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_dark/library-parameters.json +1 -0
- package/resources/chatui/themes/sap_fiori_3_dark/library.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_dark/library.source.less +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcb/library-RTL.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcb/library-parameters.json +1 -0
- package/resources/chatui/themes/sap_fiori_3_hcb/library.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcb/library.source.less +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcw/library-RTL.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcw/library-parameters.json +1 -0
- package/resources/chatui/themes/sap_fiori_3_hcw/library.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcw/library.source.less +3 -0
- package/ui5.yaml +15 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# @cnt-dev/chat-ui — Notes for AI coding agents
|
|
2
|
+
|
|
3
|
+
> This file is written for coding agents (Claude Code, Cursor, Aider, …)
|
|
4
|
+
> that work in a project that consumes `@cnt-dev/chat-ui`. It is deliberately
|
|
5
|
+
> short and focused on the shape of the API so an agent can wire the
|
|
6
|
+
> library correctly on the first try.
|
|
7
|
+
|
|
8
|
+
## What this package is
|
|
9
|
+
|
|
10
|
+
A **UI5 library** with a single control, `chatui.ChatBox`, that
|
|
11
|
+
you drop into any UI5 app to get an embedded AI-chat block: sidebar with
|
|
12
|
+
past conversations, streaming assistant replies, file attachments, rich
|
|
13
|
+
UI5-control bubbles.
|
|
14
|
+
|
|
15
|
+
The library is **transport-agnostic** — it fires events and expects the
|
|
16
|
+
embedding app to talk to the backend. Do **not** try to configure a URL,
|
|
17
|
+
API key, or model on the control itself; that lives in the consumer.
|
|
18
|
+
|
|
19
|
+
- **npm name:** `@cnt-dev/chat-ui`
|
|
20
|
+
- **UI5 namespace / module path:** `chatui` / `chatui/*`
|
|
21
|
+
- **Runtime deps:** only `sap.ui.core`
|
|
22
|
+
|
|
23
|
+
## Wiring it into a consumer app
|
|
24
|
+
|
|
25
|
+
1. Install: `npm i @cnt-dev/chat-ui`
|
|
26
|
+
2. In the consumer's `ui5.yaml`, add the library to `framework.libraries`:
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
framework:
|
|
30
|
+
libraries:
|
|
31
|
+
- name: sap.ui.core
|
|
32
|
+
- name: chatui
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. In `manifest.json`:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
"sap.ui5": {
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"libs": { "chatui": {} }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
4. Import and place:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import ChatBox from "chatui/ChatBox";
|
|
49
|
+
const chat = new ChatBox({ width: "100%", height: "100%" });
|
|
50
|
+
chat.placeAt("content");
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Minimal end-to-end example
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import ChatBox from "chatui/ChatBox";
|
|
57
|
+
import type {
|
|
58
|
+
ChatBox$SendEvent,
|
|
59
|
+
ChatBox$ConversationDeleteEvent,
|
|
60
|
+
Conversation,
|
|
61
|
+
Message
|
|
62
|
+
} from "chatui/ChatBox";
|
|
63
|
+
|
|
64
|
+
let conversations: Conversation[] = loadFromStorage();
|
|
65
|
+
|
|
66
|
+
const chat = new ChatBox({
|
|
67
|
+
height: "100%",
|
|
68
|
+
width: "100%",
|
|
69
|
+
send: async (e: ChatBox$SendEvent) => {
|
|
70
|
+
const message = e.getParameter("message") ?? "";
|
|
71
|
+
const files = (e.getParameter("files") as File[] | undefined) ?? [];
|
|
72
|
+
let id = e.getParameter("conversationId") ?? "";
|
|
73
|
+
if (!id) {
|
|
74
|
+
id = crypto.randomUUID();
|
|
75
|
+
chat.setActiveConversationId(id);
|
|
76
|
+
}
|
|
77
|
+
upsert(conversations, id, chat.getMessages()); // user turn saved
|
|
78
|
+
chat.setConversations(conversations);
|
|
79
|
+
|
|
80
|
+
chat.setBusy(true);
|
|
81
|
+
chat.addAssistantMessage(""); // pending bubble
|
|
82
|
+
for await (const chunk of stream(message, files, chat.getMessages())) {
|
|
83
|
+
chat.appendToLastAssistantMessage(chunk);
|
|
84
|
+
}
|
|
85
|
+
chat.setBusy(false);
|
|
86
|
+
|
|
87
|
+
upsert(conversations, id, chat.getMessages()); // assistant turn saved
|
|
88
|
+
chat.setConversations(conversations);
|
|
89
|
+
},
|
|
90
|
+
newChat: () => backend.resetSession(),
|
|
91
|
+
conversationDelete: (e: ChatBox$ConversationDeleteEvent) => {
|
|
92
|
+
const id = e.getParameter("id") ?? "";
|
|
93
|
+
conversations = conversations.filter(c => c.id !== id);
|
|
94
|
+
chat.setConversations(conversations);
|
|
95
|
+
if (chat.getActiveConversationId() === id) {
|
|
96
|
+
chat.setActiveConversationId(conversations[0]?.id ?? "");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
chat.setConversations(conversations); // library picks the first
|
|
102
|
+
chat.placeAt("content");
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API cheat sheet
|
|
106
|
+
|
|
107
|
+
### Properties
|
|
108
|
+
|
|
109
|
+
| Property | Type | Purpose |
|
|
110
|
+
|-------------------------|-----------------------|--------------------------------------------------------|
|
|
111
|
+
| `placeholder` | `string` | Textarea placeholder (empty → i18n default) |
|
|
112
|
+
| `busy` | `boolean` | Disables input while the assistant is streaming |
|
|
113
|
+
| `width` / `height` | `sap.ui.core.CSSSize` | Fill parent by default (`"100%"`) |
|
|
114
|
+
| `allowAttachments` | `boolean` | Hides paperclip + drop-zone when `false` |
|
|
115
|
+
| `attachmentAccept` | `string` | Passed as `<input type="file" accept="…">` |
|
|
116
|
+
| `conversations` | `Conversation[]` | Sidebar entries (owned by consumer, library only shows)|
|
|
117
|
+
| `activeConversationId` | `string` | Currently shown conversation; `""` = new-chat draft |
|
|
118
|
+
|
|
119
|
+
### Events
|
|
120
|
+
|
|
121
|
+
- **`send({message, files, conversationId})`** — user submitted. When
|
|
122
|
+
`conversationId === ""` the consumer allocates a fresh id and calls
|
|
123
|
+
`setActiveConversationId(newId)` + `setConversations(...)`.
|
|
124
|
+
- **`newChat`** — the "+ Neue Konversation" button was clicked. Transcript
|
|
125
|
+
and `activeConversationId` are already reset when this fires. Reset any
|
|
126
|
+
server-side session state here.
|
|
127
|
+
- **`conversationSelect({id})`** — sidebar entry picked (library already
|
|
128
|
+
switched the transcript view).
|
|
129
|
+
- **`conversationDelete({id})`** — sidebar × clicked. The library does
|
|
130
|
+
**not** mutate `conversations` itself — remove the entry from your store
|
|
131
|
+
and push back via `setConversations`.
|
|
132
|
+
|
|
133
|
+
### Methods
|
|
134
|
+
|
|
135
|
+
| Method | Purpose |
|
|
136
|
+
|---------------------------------------|----------------------------------------------------------------------------|
|
|
137
|
+
| `addAssistantMessage(text)` | Append assistant bubble; empty text = pending animation |
|
|
138
|
+
| `appendToLastAssistantMessage(chunk)` | Stream chunk into the last assistant bubble (opens a new one if needed) |
|
|
139
|
+
| `addAssistantControl(control)` | Attach any UI5 control (VizFrame, Card, …) as its own assistant bubble |
|
|
140
|
+
| `clear()` | Clear the transcript; does **not** fire `newChat` |
|
|
141
|
+
| `getMessages()` / `setMessages(msgs)` | Snapshot / replace transcript of the currently active conversation |
|
|
142
|
+
| `getConversations()` / `setConversations(convs)` | Snapshot / replace sidebar list |
|
|
143
|
+
| `getActiveConversationId()` / `setActiveConversationId(id)` | Switch view; `""` = new-chat draft |
|
|
144
|
+
|
|
145
|
+
### Types (re-exported from `chatui/ChatBox`)
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
interface Message {
|
|
149
|
+
role: "user" | "assistant";
|
|
150
|
+
content: string;
|
|
151
|
+
files?: File[];
|
|
152
|
+
control?: sap.ui.core.Control; // rich-content bubble
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface Conversation {
|
|
156
|
+
id: string; // required, stable
|
|
157
|
+
title?: string; // fallback: first user message, truncated
|
|
158
|
+
messages?: ReadonlyArray<Message>;
|
|
159
|
+
updatedAt?: number | string | Date;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Things to get right
|
|
164
|
+
|
|
165
|
+
- **`getMessages()` returns a snapshot** — do not mutate the array or the
|
|
166
|
+
entries. If you need to change the transcript, use `setMessages` or
|
|
167
|
+
`clear`.
|
|
168
|
+
- **Never insert the same control twice via `addAssistantControl`.** The
|
|
169
|
+
library owns the lifecycle from that call — the control is destroyed on
|
|
170
|
+
`clear`, `setMessages`, `newChat`, or ChatBox destroy.
|
|
171
|
+
- **On send with empty `conversationId`**: allocate the id *before* the
|
|
172
|
+
first `addAssistantMessage("")`, and call `setActiveConversationId(id)`
|
|
173
|
+
so the assistant chunks land in the right conversation.
|
|
174
|
+
- **Persistence: `File` and `sap.ui.core.Control` don't survive a JSON
|
|
175
|
+
round-trip.** Serialise only `role` + `content` (+ optional file names)
|
|
176
|
+
for storage; rich content must be re-attached at runtime.
|
|
177
|
+
- **The library never touches your storage or the network.** No `fetch`,
|
|
178
|
+
no `localStorage`, no session state.
|
|
179
|
+
|
|
180
|
+
## What is **not** in this library
|
|
181
|
+
|
|
182
|
+
- Backend/API calls, auth, session, retry
|
|
183
|
+
- Streaming transport (`fetch`/SSE) — the library only offers the
|
|
184
|
+
streaming API (`appendToLastAssistantMessage`)
|
|
185
|
+
- File-upload transport — files are collected and handed to `send`, the
|
|
186
|
+
consumer decides how they go to the backend
|
|
187
|
+
- Storage — `getMessages` / `setMessages` and `getConversations` /
|
|
188
|
+
`setConversations` bound the roundtrip
|
|
189
|
+
- `sap.m` / `sap.viz` dependencies — rich content is injected via
|
|
190
|
+
`addAssistantControl`; the library depends only on `sap.ui.core`
|
|
191
|
+
|
|
192
|
+
## Themes
|
|
193
|
+
|
|
194
|
+
Ships with `sap_fiori_3`, `sap_fiori_3_dark`, `sap_fiori_3_hcw`,
|
|
195
|
+
`sap_fiori_3_hcb`. CSS is built from UI5 theme parameters, so switching
|
|
196
|
+
themes at runtime just works.
|
|
197
|
+
|
|
198
|
+
## i18n
|
|
199
|
+
|
|
200
|
+
Default bundle is German (`chatbox.*` keys). English is included as
|
|
201
|
+
`_en`. Add your own bundle by convention or override individual keys via
|
|
202
|
+
the consumer's own `sap.ui.core.ResourceModel`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CONET
|
|
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,303 @@
|
|
|
1
|
+
# @cnt-dev/chat-ui
|
|
2
|
+
|
|
3
|
+
Eine schlanke, einbettbare **UI5-Chat-Library** mit genau einer Kontrolle:
|
|
4
|
+
`chatui.ChatBox`. Die Library ist bewusst **transport-agnostisch** —
|
|
5
|
+
sie kennt kein Backend, keine Session, keine Auth. Sie feuert `send` und
|
|
6
|
+
erwartet, dass die einbettende App die Antwort per API zurückschiebt.
|
|
7
|
+
|
|
8
|
+
- **Zero-Deps außer `sap.ui.core`** (kein `sap.m`, kein `sap.viz`).
|
|
9
|
+
- **Streaming-fähig** via Chunk-API.
|
|
10
|
+
- **Konversations-Sidebar, File-Uploads, Rich Content, Editing, Copy** out of the box.
|
|
11
|
+
- **npm-Name:** `@cnt-dev/chat-ui` — UI5-Namespace / Modulpfad: `chatui` / `chatui/…`.
|
|
12
|
+
|
|
13
|
+
## Feature-Übersicht
|
|
14
|
+
|
|
15
|
+
| Feature | API |
|
|
16
|
+
|--------------------------------|----------------------------------------------------------|
|
|
17
|
+
| Text-Nachricht senden | `send`-Event → `{message, files, conversationId}` |
|
|
18
|
+
| Streaming-Antwort | `addAssistantMessage("")` + `appendToLastAssistantMessage(chunk)` |
|
|
19
|
+
| Markdown-Rendering | automatisch bei Assistant-Text |
|
|
20
|
+
| Copy-to-Clipboard | Hover-Action pro Assistant-Bubble |
|
|
21
|
+
| Datei-/Bild-Uploads | Paperclip-Button + Drag&Drop → `files: File[]` im `send` |
|
|
22
|
+
| Nachrichten-Bearbeitung | Edit-Action pro User-Bubble, truncated + refire `send` |
|
|
23
|
+
| Rich Content (Chart, Card, …) | `addAssistantControl(control)` |
|
|
24
|
+
| Konversations-Sidebar | `conversations`-Property + `conversationSelect` / `conversationDelete`-Events |
|
|
25
|
+
| Restore aus Persistenz | `setConversations()` / `getConversations()` (und `setMessages` / `getMessages` pro Konversation) |
|
|
26
|
+
| Neue Konversation | Header-Button „+ Neue Konversation" → `newChat`-Event |
|
|
27
|
+
| i18n | DE (default) + EN (`chatbox.*`-Keys) |
|
|
28
|
+
| Themes | `sap_fiori_3`, `_dark`, `_hcw`, `_hcb` |
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm i @cnt-dev/chat-ui
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
In der Consumer-`ui5.yaml`:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
framework:
|
|
40
|
+
libraries:
|
|
41
|
+
- name: sap.ui.core
|
|
42
|
+
- name: chatui
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
In `manifest.json`:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
"sap.ui5": {
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"libs": {
|
|
51
|
+
"chatui": {}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Bei nicht-UI5-tooling-Bootstraps ggf. Ressourcen-Root explizit setzen:
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<script id="sap-ui-bootstrap"
|
|
61
|
+
src="…/sap-ui-core.js"
|
|
62
|
+
data-sap-ui-libs="chatui"
|
|
63
|
+
data-sap-ui-resourceroots='{"chatui": "node_modules/@cnt-dev/chat-ui/resources/chatui"}'>
|
|
64
|
+
</script>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Schnellstart
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import ChatBox from "chatui/ChatBox";
|
|
71
|
+
import type {
|
|
72
|
+
ChatBox$SendEvent,
|
|
73
|
+
ChatBox$ConversationDeleteEvent,
|
|
74
|
+
Conversation
|
|
75
|
+
} from "chatui/ChatBox";
|
|
76
|
+
|
|
77
|
+
let conversations: Conversation[] = loadFromStorage();
|
|
78
|
+
|
|
79
|
+
const chat = new ChatBox({
|
|
80
|
+
width: "100%",
|
|
81
|
+
height: "100%",
|
|
82
|
+
send: async (e: ChatBox$SendEvent) => {
|
|
83
|
+
const message = e.getParameter("message") ?? "";
|
|
84
|
+
const files = (e.getParameter("files") as File[] | undefined) ?? [];
|
|
85
|
+
let id = e.getParameter("conversationId") ?? "";
|
|
86
|
+
if (!id) { // neue Konversation
|
|
87
|
+
id = crypto.randomUUID();
|
|
88
|
+
chat.setActiveConversationId(id);
|
|
89
|
+
}
|
|
90
|
+
upsert(conversations, id, chat.getMessages());
|
|
91
|
+
chat.setConversations(conversations);
|
|
92
|
+
|
|
93
|
+
chat.setBusy(true);
|
|
94
|
+
chat.addAssistantMessage(""); // pending bubble
|
|
95
|
+
for await (const chunk of streamFromBackend(message, files, chat.getMessages())) {
|
|
96
|
+
chat.appendToLastAssistantMessage(chunk);
|
|
97
|
+
}
|
|
98
|
+
chat.setBusy(false);
|
|
99
|
+
|
|
100
|
+
upsert(conversations, id, chat.getMessages());
|
|
101
|
+
chat.setConversations(conversations);
|
|
102
|
+
},
|
|
103
|
+
newChat: () => backend.resetSession(),
|
|
104
|
+
conversationDelete: (e: ChatBox$ConversationDeleteEvent) => {
|
|
105
|
+
const id = e.getParameter("id") ?? "";
|
|
106
|
+
conversations = conversations.filter(c => c.id !== id);
|
|
107
|
+
chat.setConversations(conversations);
|
|
108
|
+
if (chat.getActiveConversationId() === id) {
|
|
109
|
+
chat.setActiveConversationId(conversations[0]?.id ?? "");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
chat.setConversations(conversations); // Library wählt automatisch die erste
|
|
114
|
+
chat.placeAt("content");
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Public API
|
|
118
|
+
|
|
119
|
+
### Properties
|
|
120
|
+
|
|
121
|
+
| Name | Typ | Default | Zweck |
|
|
122
|
+
|-------------------------|------------------------|---------|-------|
|
|
123
|
+
| `placeholder` | `string` | `""` | Platzhaltertext (leer → i18n-Default) |
|
|
124
|
+
| `busy` | `boolean` | `false` | Deaktiviert Input, Buttons, Edit |
|
|
125
|
+
| `width` | `sap.ui.core.CSSSize` | `100%` | Passt sich Parent an |
|
|
126
|
+
| `height` | `sap.ui.core.CSSSize` | `100%` | Passt sich Parent an |
|
|
127
|
+
| `allowAttachments` | `boolean` | `true` | Blendet Paperclip + Drop-Zone aus, wenn `false` |
|
|
128
|
+
| `attachmentAccept` | `string` | `""` | 1:1 an `<input type="file" accept="…">` (z.B. `"image/*"`) |
|
|
129
|
+
| `conversations` | `Conversation[]` | `[]` | Sidebar-Einträge (vom Consumer gehalten) |
|
|
130
|
+
| `activeConversationId` | `string` | `""` | Aktive Konversation. `""` = neuer Chat-Draft |
|
|
131
|
+
|
|
132
|
+
### Events
|
|
133
|
+
|
|
134
|
+
- **`send({message, files, conversationId})`**
|
|
135
|
+
User hat einen Prompt und/oder Anhänge abgeschickt. `conversationId`
|
|
136
|
+
ist die aktive Id oder `""` bei einem neuen Chat — dann vergibt der
|
|
137
|
+
Consumer eine frische Id und pusht eine aktualisierte Liste zurück.
|
|
138
|
+
- **`newChat`**
|
|
139
|
+
User hat „+ Neue Konversation" oben rechts gedrückt. Transcript und
|
|
140
|
+
`activeConversationId` sind bereits zurückgesetzt; Handler räumt
|
|
141
|
+
serverseitige Session-States auf.
|
|
142
|
+
- **`conversationSelect({id})`**
|
|
143
|
+
User hat eine Konversation in der Sidebar angeklickt. Die Library hat
|
|
144
|
+
die Ansicht bereits umgeschaltet.
|
|
145
|
+
- **`conversationDelete({id})`**
|
|
146
|
+
User hat auf das × einer Konversation geklickt. Die Library mutiert
|
|
147
|
+
`conversations` **nicht** selbst — der Consumer entfernt den Eintrag
|
|
148
|
+
aus seinem Store und pusht via `setConversations` zurück.
|
|
149
|
+
|
|
150
|
+
### Methoden
|
|
151
|
+
|
|
152
|
+
- `addAssistantMessage(text: string): this`
|
|
153
|
+
Hängt eine Assistant-Text-Bubble an. Leerer Text = „pending"-Bubble mit
|
|
154
|
+
Animation.
|
|
155
|
+
- `appendToLastAssistantMessage(chunk: string): this`
|
|
156
|
+
Streaming: hängt Token an die letzte Assistant-Text-Bubble.
|
|
157
|
+
- `addAssistantControl(control: sap.ui.core.Control): this`
|
|
158
|
+
Hängt ein beliebiges UI5-Control als eigene Assistant-Bubble an. Die
|
|
159
|
+
Library übernimmt den Lifecycle — Control wird bei `clear()`,
|
|
160
|
+
`newChat`, `setMessages()` oder ChatBox-Destroy mit `destroy()`
|
|
161
|
+
aufgeräumt.
|
|
162
|
+
- `clear(): this`
|
|
163
|
+
Leert Transcript **ohne** `newChat`-Event zu feuern.
|
|
164
|
+
- `getMessages()` / `setMessages(msgs)`
|
|
165
|
+
Snapshot / Ersetzen des Transcripts der aktiven Konversation. Control-
|
|
166
|
+
Refs werden von `setMessages` bewusst **nicht** übernommen.
|
|
167
|
+
- `getConversations()` / `setConversations(convs)`
|
|
168
|
+
Snapshot / Ersetzen der Sidebar-Liste. Beim ersten Setzen mit non-empty
|
|
169
|
+
Array wird automatisch die erste Konversation aktiv und deren
|
|
170
|
+
`messages` in den Transcript geladen.
|
|
171
|
+
- `getActiveConversationId()` / `setActiveConversationId(id)`
|
|
172
|
+
Programmatischer Wechsel. Unbekannte Id (inkl. `""`) → Transcript
|
|
173
|
+
geleert (neuer-Chat-Draft-State).
|
|
174
|
+
|
|
175
|
+
### Message- und Conversation-Struktur (exportiert)
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
interface Message {
|
|
179
|
+
role: "user" | "assistant";
|
|
180
|
+
content: string;
|
|
181
|
+
files?: File[];
|
|
182
|
+
control?: sap.ui.core.Control;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface Conversation {
|
|
186
|
+
id: string;
|
|
187
|
+
title?: string; // fallback: erste User-Message
|
|
188
|
+
messages?: ReadonlyArray<Message>;
|
|
189
|
+
updatedAt?: number | string | Date; // heute → HH:MM, gestern, Wochentag, Datum
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Consumer-Muster
|
|
194
|
+
|
|
195
|
+
### Persistenz per `localStorage`
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
interface Stored { id: string; title?: string; updatedAt?: number;
|
|
199
|
+
messages: { role: "user"|"assistant"; content: string }[]; }
|
|
200
|
+
|
|
201
|
+
function load(): Stored[] { try { return JSON.parse(localStorage.getItem("chats") ?? "[]"); } catch { return []; } }
|
|
202
|
+
function save(c: Stored[]) { localStorage.setItem("chats", JSON.stringify(c)); }
|
|
203
|
+
|
|
204
|
+
const stored = load();
|
|
205
|
+
chat.setConversations(stored); // Library wählt automatisch die erste
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Rich Content (Chart als Assistant-Bubble)
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import VizFrame from "sap/viz/ui5/controls/VizFrame";
|
|
212
|
+
|
|
213
|
+
// Die Deps für VizFrame liegen in der Consumer-App, nicht in der Library.
|
|
214
|
+
const chart = new VizFrame({ vizType: "column", /* bindings … */ });
|
|
215
|
+
chat.addAssistantControl(chart);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### App-Kontext ans Backend mitschicken
|
|
219
|
+
|
|
220
|
+
Die Library reicht Kontext bewusst **nicht** durch — jede App kennt ihren
|
|
221
|
+
Kontext selbst und packt ihn ins Request-Payload:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
send: async (e) => {
|
|
225
|
+
const message = e.getParameter("message");
|
|
226
|
+
const files = e.getParameter("files") as File[];
|
|
227
|
+
const context = {
|
|
228
|
+
route: window.location.hash,
|
|
229
|
+
selection: model.getProperty("/selection"),
|
|
230
|
+
clAudeMd: await fetch("./CLAUDE.md").then(r => r.text()),
|
|
231
|
+
};
|
|
232
|
+
const reply = await backend.chat({ message, files, context });
|
|
233
|
+
// …
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Design-Grenzen (bewusst außerhalb)
|
|
238
|
+
|
|
239
|
+
- **Kein Backend-Wissen**. Auth, Session, Transport, Retry — alles
|
|
240
|
+
Consumer.
|
|
241
|
+
- **Kein Streaming über `fetch`/SSE**. Die Library bietet nur die
|
|
242
|
+
Streaming-**API** (`appendToLastAssistantMessage`); die Netzwerk-Schicht
|
|
243
|
+
bringt der Consumer mit.
|
|
244
|
+
- **Kein Upload-Transport**. Files werden gesammelt und im `send`-Event
|
|
245
|
+
durchgereicht — wie sie zum Backend gehen, entscheidet der Consumer
|
|
246
|
+
(multipart, base64, S3-presigned, …).
|
|
247
|
+
- **Kein Storage**. `getMessages` / `setMessages` bilden den Roundtrip,
|
|
248
|
+
Storage bringt der Consumer.
|
|
249
|
+
- **Keine `sap.m`- / `sap.viz`-Dependencies**. Rich Content wird per
|
|
250
|
+
`addAssistantControl` reingereicht; die Library kennt weiterhin nur
|
|
251
|
+
`sap.ui.core.Control`.
|
|
252
|
+
|
|
253
|
+
## Entwicklung
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
npm run build-interface # regeneriert ChatBox.gen.d.ts aus dem Control-Code
|
|
257
|
+
npm run ts-typecheck # tsc --noEmit
|
|
258
|
+
npm run lint # eslint
|
|
259
|
+
npm test # karma + qunit, headless Chrome
|
|
260
|
+
npm start # startet fiori run mit test/ChatBox.html
|
|
261
|
+
npm run build-app # ui5 build → dist/
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Nach Änderungen an Properties/Events/Aggregations in `ChatBox.ts` muss
|
|
265
|
+
`build-interface` laufen, damit die `.gen.d.ts`-Metadata nicht driftet.
|
|
266
|
+
|
|
267
|
+
## Publish auf npm
|
|
268
|
+
|
|
269
|
+
Die Root-`package.json` ist `private: true` und trägt nur die Dev-
|
|
270
|
+
Metadaten. Publiziert wird ausschließlich der Build-Output unter `dist/`
|
|
271
|
+
mit einer generierten, schlanken `package.json`, so dass der Consumer
|
|
272
|
+
`node_modules/@cnt-dev/chat-ui/resources/chatui/…` direkt vorfindet.
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
npm run pack-dist # baut + prepped + erzeugt einen Tarball zum Prüfen
|
|
276
|
+
npm run publish-dist # baut + prepped + npm publish ./dist (npm login vorausgesetzt)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`scripts/prepare-publish.mjs` schreibt `dist/package.json`, `dist/ui5.yaml`
|
|
280
|
+
und kopiert `README.md`, `LICENSE`, `AGENTS.md` in `dist/` hinein.
|
|
281
|
+
Version anheben (`npm version patch|minor|major`) vor dem Publish.
|
|
282
|
+
|
|
283
|
+
## Verzeichnisstruktur
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
chatui/
|
|
287
|
+
├── src/chatui/
|
|
288
|
+
│ ├── ChatBox.ts # Hauptkontrolle (inline renderer)
|
|
289
|
+
│ ├── ChatBox.gen.d.ts # generierte Interface-Deklaration
|
|
290
|
+
│ ├── markdown.ts # kleiner Markdown→HTML-Konverter
|
|
291
|
+
│ ├── library.ts # Lib.init() — registriert ChatBox
|
|
292
|
+
│ ├── .library # Library-Metadaten
|
|
293
|
+
│ ├── messagebundle*.properties # i18n (DE default, EN)
|
|
294
|
+
│ └── themes/… # sap_fiori_3(_dark|_hcw|_hcb)
|
|
295
|
+
├── test/chatui/
|
|
296
|
+
│ ├── ChatBox.html # `npm start`-Einstiegspunkt
|
|
297
|
+
│ ├── ChatBox.ts # dummy Send-Handler (Echo-Stream)
|
|
298
|
+
│ └── qunit/*.qunit.ts # Unit-Tests
|
|
299
|
+
└── ui5.yaml, tsconfig.json, package.json, karma.conf.js
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Ausführliche interne Hintergründe (Design-Entscheidungen, ESLint-Ausnahmen,
|
|
303
|
+
Referenzmaterial) stehen in [`../CLAUDE.md`](../CLAUDE.md).
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Generated with TypeScript 5.9.3 / SAPUI5 1.136.0 using:
|
|
2
|
+
// - mdurl@2.1.0
|
|
3
|
+
// - linkify-it@5.0.2
|
|
4
|
+
// - typescript@5.9.3
|
|
5
|
+
// - string_decoder@1.1.1
|
|
6
|
+
// - punycode@1.4.1
|
|
7
|
+
// - undici-types@8.3.0
|
|
8
|
+
// - @types/ws@8.18.1
|
|
9
|
+
// - @types/three@0.125.3
|
|
10
|
+
// - @types/sizzle@2.3.10
|
|
11
|
+
// - @types/qunit@2.5.4
|
|
12
|
+
// - @types/offscreencanvas@2019.6.4
|
|
13
|
+
// - @types/normalize-package-data@2.4.4
|
|
14
|
+
// - @types/node@26.1.1
|
|
15
|
+
// - @types/mdurl@2.0.0
|
|
16
|
+
// - @types/markdown-it@14.2.0
|
|
17
|
+
// - @types/linkify-it@5.0.0
|
|
18
|
+
// - @types/jquery@3.5.13
|
|
19
|
+
// - @types/cors@2.8.19
|
|
20
|
+
/// <reference path="./resources/chatui/library.d.ts"/>
|
|
21
|
+
/// <reference path="./resources/chatui/ChatBox.gen.d.ts"/>
|
|
22
|
+
/// <reference path="./resources/chatui/ChatBox.d.ts"/>
|
|
23
|
+
/// <reference path="./resources/chatui/markdown.d.ts"/>
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cnt-dev/chat-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Embeddable AI chat UI control for UI5 applications — sidebar-based conversations, streaming answers, file attachments, rich UI5-control bubbles.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ui5",
|
|
7
|
+
"openui5",
|
|
8
|
+
"sapui5",
|
|
9
|
+
"chat",
|
|
10
|
+
"ai",
|
|
11
|
+
"assistant",
|
|
12
|
+
"chatbot",
|
|
13
|
+
"conet"
|
|
14
|
+
],
|
|
15
|
+
"author": "CONET",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"main": "resources/chatui/library.js",
|
|
18
|
+
"types": "index.d.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"resources",
|
|
21
|
+
"index.d.ts",
|
|
22
|
+
"ui5.yaml",
|
|
23
|
+
"AGENTS.md",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"resources/chatui/library.js",
|
|
29
|
+
"resources/chatui/library-preload.js",
|
|
30
|
+
"resources/**/*.css"
|
|
31
|
+
],
|
|
32
|
+
"ui5": {
|
|
33
|
+
"type": "library",
|
|
34
|
+
"metadata": {
|
|
35
|
+
"name": "chatui"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" ?>
|
|
2
|
+
<library xmlns="http://www.sap.com/sap.ui.library.xsd">
|
|
3
|
+
<name>chatui</name>
|
|
4
|
+
<vendor>CONET</vendor>
|
|
5
|
+
<version>0.1.0</version>
|
|
6
|
+
<copyright>${copyright}</copyright>
|
|
7
|
+
<title>chatui</title>
|
|
8
|
+
<documentation>Embeddable AI chat UI control for UI5 applications.</documentation>
|
|
9
|
+
<dependencies>
|
|
10
|
+
<dependency>
|
|
11
|
+
<libraryName>sap.ui.core</libraryName>
|
|
12
|
+
</dependency>
|
|
13
|
+
</dependencies>
|
|
14
|
+
</library>
|