@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.
Files changed (55) hide show
  1. package/AGENTS.md +202 -0
  2. package/LICENSE +21 -0
  3. package/README.md +303 -0
  4. package/index.d.ts +23 -0
  5. package/package.json +41 -0
  6. package/resources/chatui/.library +14 -0
  7. package/resources/chatui/ChatBox-dbg.js +1099 -0
  8. package/resources/chatui/ChatBox-dbg.js.map +1 -0
  9. package/resources/chatui/ChatBox.d.ts +308 -0
  10. package/resources/chatui/ChatBox.d.ts.map +1 -0
  11. package/resources/chatui/ChatBox.gen.d.ts +545 -0
  12. package/resources/chatui/ChatBox.js +5 -0
  13. package/resources/chatui/ChatBox.js.map +1 -0
  14. package/resources/chatui/ChatBox.ts +1153 -0
  15. package/resources/chatui/library-dbg.js +28 -0
  16. package/resources/chatui/library-dbg.js.map +1 -0
  17. package/resources/chatui/library-preload.js +17 -0
  18. package/resources/chatui/library-preload.js.map +1 -0
  19. package/resources/chatui/library.d.ts +3 -0
  20. package/resources/chatui/library.d.ts.map +1 -0
  21. package/resources/chatui/library.js +5 -0
  22. package/resources/chatui/library.js.map +1 -0
  23. package/resources/chatui/library.ts +30 -0
  24. package/resources/chatui/manifest.json +52 -0
  25. package/resources/chatui/markdown-dbg.js +116 -0
  26. package/resources/chatui/markdown-dbg.js.map +1 -0
  27. package/resources/chatui/markdown.d.ts +21 -0
  28. package/resources/chatui/markdown.d.ts.map +1 -0
  29. package/resources/chatui/markdown.js +5 -0
  30. package/resources/chatui/markdown.js.map +1 -0
  31. package/resources/chatui/markdown.ts +140 -0
  32. package/resources/chatui/messagebundle.properties +20 -0
  33. package/resources/chatui/messagebundle_en.properties +20 -0
  34. package/resources/chatui/themes/base/ChatBox.less +702 -0
  35. package/resources/chatui/themes/base/library-RTL.css +3 -0
  36. package/resources/chatui/themes/base/library-parameters.json +1 -0
  37. package/resources/chatui/themes/base/library.css +3 -0
  38. package/resources/chatui/themes/base/library.source.less +4 -0
  39. package/resources/chatui/themes/sap_fiori_3/library-RTL.css +3 -0
  40. package/resources/chatui/themes/sap_fiori_3/library-parameters.json +1 -0
  41. package/resources/chatui/themes/sap_fiori_3/library.css +3 -0
  42. package/resources/chatui/themes/sap_fiori_3/library.source.less +3 -0
  43. package/resources/chatui/themes/sap_fiori_3_dark/library-RTL.css +3 -0
  44. package/resources/chatui/themes/sap_fiori_3_dark/library-parameters.json +1 -0
  45. package/resources/chatui/themes/sap_fiori_3_dark/library.css +3 -0
  46. package/resources/chatui/themes/sap_fiori_3_dark/library.source.less +3 -0
  47. package/resources/chatui/themes/sap_fiori_3_hcb/library-RTL.css +3 -0
  48. package/resources/chatui/themes/sap_fiori_3_hcb/library-parameters.json +1 -0
  49. package/resources/chatui/themes/sap_fiori_3_hcb/library.css +3 -0
  50. package/resources/chatui/themes/sap_fiori_3_hcb/library.source.less +3 -0
  51. package/resources/chatui/themes/sap_fiori_3_hcw/library-RTL.css +3 -0
  52. package/resources/chatui/themes/sap_fiori_3_hcw/library-parameters.json +1 -0
  53. package/resources/chatui/themes/sap_fiori_3_hcw/library.css +3 -0
  54. package/resources/chatui/themes/sap_fiori_3_hcw/library.source.less +3 -0
  55. 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>