@aparte/svelte 0.7.1 → 0.8.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/README.md +16 -3
- package/dist/AparteChat.svelte +245 -0
- package/dist/AparteUi.svelte +53 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1335
- package/dist/stores/aparteChat.d.ts +1 -6
- package/dist/stores/aparteChat.d.ts.map +1 -1
- package/dist/stores/aparteChat.js +38 -0
- package/dist/stores/aparteClient.js +13 -0
- package/dist/stores/conversationManager.d.ts +3 -3
- package/dist/stores/conversationManager.d.ts.map +1 -1
- package/dist/stores/conversationManager.js +58 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -0
- package/package.json +23 -6
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
# @aparte/svelte
|
|
2
2
|
|
|
3
|
-
Svelte
|
|
3
|
+
Svelte wrapper for [aparté](https://github.com/apartejs/aparte) — an ergonomic `<AparteChat>`
|
|
4
4
|
component plus stores (`createAparteChat`, `createAparteClient`, `createConversationManager`) over the
|
|
5
5
|
framework-agnostic web components in `@aparte/core`.
|
|
6
6
|
|
|
7
|
+
**Svelte 4 and Svelte 5**, from the same source. The package ships its `.svelte` files
|
|
8
|
+
under the `svelte` export condition, so **your** compiler builds them — how a Svelte
|
|
9
|
+
component library is meant to be published, and the only arrangement that can serve two
|
|
10
|
+
majors: Svelte-4 output imports `svelte/internal`, which Svelte 5 removed, and Svelte-5
|
|
11
|
+
output does not run on 4. No prebuilt artifact works for both.
|
|
12
|
+
|
|
13
|
+
Both are exercised in a browser on every run: `apps/examples/svelte4` builds these
|
|
14
|
+
sources with Svelte 4, `apps/examples/svelte5` with Svelte 5.
|
|
15
|
+
|
|
16
|
+
One thing differs between the two, and it is in YOUR entry point rather than here:
|
|
17
|
+
Svelte 5 removed the class-instantiation API, so you write `mount(App, …)` instead of
|
|
18
|
+
`new App(…)`.
|
|
19
|
+
|
|
7
20
|
```bash
|
|
8
21
|
npm install @aparte/svelte @aparte/core svelte
|
|
9
22
|
```
|
|
10
23
|
|
|
11
24
|
```svelte
|
|
12
25
|
<script lang="ts">
|
|
13
|
-
import { AparteChat, createAparteChat, type
|
|
26
|
+
import { AparteChat, createAparteChat, type AparteChatImperativeApi } from '@aparte/svelte';
|
|
14
27
|
import '@aparte/core/styles.css';
|
|
15
28
|
|
|
16
29
|
const chat = createAparteChat();
|
|
17
30
|
const { messages } = chat;
|
|
18
|
-
let comp:
|
|
31
|
+
let comp: AparteChatImperativeApi | null = null;
|
|
19
32
|
$: chat.connect(comp);
|
|
20
33
|
</script>
|
|
21
34
|
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
<script>import { onMount, onDestroy, tick, createEventDispatcher } from "svelte";
|
|
2
|
+
import { AparteChatHost, isAwaitingReply, uuid } from "@aparte/core";
|
|
3
|
+
export let id = void 0;
|
|
4
|
+
export let messages = [];
|
|
5
|
+
export let placeholder = "Type a message...";
|
|
6
|
+
export let disabled = false;
|
|
7
|
+
export let isTyping = false;
|
|
8
|
+
export let typingText = "Assistant is thinking...";
|
|
9
|
+
export let submitOnEnter = true;
|
|
10
|
+
export let layoutTransitionMs = 0;
|
|
11
|
+
export let centerWhenEmpty = false;
|
|
12
|
+
export let attachments = false;
|
|
13
|
+
export let conversationId = null;
|
|
14
|
+
export let config = void 0;
|
|
15
|
+
const dispatch = createEventDispatcher();
|
|
16
|
+
let hostId = "";
|
|
17
|
+
let rootRef;
|
|
18
|
+
let viewportRef;
|
|
19
|
+
let composerRef;
|
|
20
|
+
let internalMessages = [...messages];
|
|
21
|
+
let typingActive = isTyping;
|
|
22
|
+
let host = null;
|
|
23
|
+
let teardown = null;
|
|
24
|
+
let lastProp = messages;
|
|
25
|
+
$: if (messages !== lastProp) {
|
|
26
|
+
lastProp = messages;
|
|
27
|
+
internalMessages = [...messages];
|
|
28
|
+
if (messages.length === 0) host?.clearRenderCache();
|
|
29
|
+
}
|
|
30
|
+
let lastTyping = isTyping;
|
|
31
|
+
$: if (isTyping !== lastTyping) {
|
|
32
|
+
lastTyping = isTyping;
|
|
33
|
+
typingActive = isTyping;
|
|
34
|
+
}
|
|
35
|
+
let lastConv = conversationId;
|
|
36
|
+
$: if (conversationId !== lastConv) {
|
|
37
|
+
lastConv = conversationId;
|
|
38
|
+
void host?.setConversationId(conversationId ?? null);
|
|
39
|
+
}
|
|
40
|
+
$: if (host && internalMessages) {
|
|
41
|
+
void tick().then(() => host?.syncBubbles());
|
|
42
|
+
}
|
|
43
|
+
function toggleAttr(el, name, on, value) {
|
|
44
|
+
if (on) el.setAttribute(name, value);
|
|
45
|
+
else el.removeAttribute(name);
|
|
46
|
+
}
|
|
47
|
+
$: if (composerRef) {
|
|
48
|
+
composerRef.setAttribute("target", hostId);
|
|
49
|
+
composerRef.setAttribute("placeholder", placeholder);
|
|
50
|
+
toggleAttr(composerRef, "disabled", disabled, "");
|
|
51
|
+
toggleAttr(composerRef, "submit-on-enter", !submitOnEnter, "false");
|
|
52
|
+
}
|
|
53
|
+
function handleSend(event) {
|
|
54
|
+
viewportRef?.requestSmoothScroll?.();
|
|
55
|
+
dispatch("messageSent", event.detail);
|
|
56
|
+
}
|
|
57
|
+
function handleAction(event) {
|
|
58
|
+
dispatch("action", event.detail);
|
|
59
|
+
}
|
|
60
|
+
onMount(() => {
|
|
61
|
+
hostId = id ?? `aparte-chat-${uuid()}`;
|
|
62
|
+
if (rootRef) rootRef.id = hostId;
|
|
63
|
+
const binding = {
|
|
64
|
+
hostId,
|
|
65
|
+
host: rootRef,
|
|
66
|
+
viewport: viewportRef,
|
|
67
|
+
getMessages: () => internalMessages,
|
|
68
|
+
setMessages: (m) => {
|
|
69
|
+
internalMessages = m;
|
|
70
|
+
},
|
|
71
|
+
onMessagesChange: (m) => dispatch("messagesChange", m),
|
|
72
|
+
onMessageAppended: (m) => dispatch("messageAppended", m),
|
|
73
|
+
onTypingChange: (t) => {
|
|
74
|
+
typingActive = t;
|
|
75
|
+
dispatch("typingChange", t);
|
|
76
|
+
},
|
|
77
|
+
onStreamingChange: () => {
|
|
78
|
+
},
|
|
79
|
+
afterRender: (cb) => {
|
|
80
|
+
void tick().then(cb);
|
|
81
|
+
},
|
|
82
|
+
resetComposer: () => composerRef?.reset?.()
|
|
83
|
+
};
|
|
84
|
+
host = new AparteChatHost(binding, {
|
|
85
|
+
layoutTransitionMs,
|
|
86
|
+
conversationId: conversationId ?? null,
|
|
87
|
+
onConversationCreated: (id2) => dispatch("conversationCreated", id2),
|
|
88
|
+
config
|
|
89
|
+
});
|
|
90
|
+
teardown = host.bind();
|
|
91
|
+
host.syncBubbles();
|
|
92
|
+
rootRef?.addEventListener("aparte-action", handleAction);
|
|
93
|
+
});
|
|
94
|
+
onDestroy(() => {
|
|
95
|
+
rootRef?.removeEventListener("aparte-action", handleAction);
|
|
96
|
+
teardown?.();
|
|
97
|
+
teardown = null;
|
|
98
|
+
host = null;
|
|
99
|
+
});
|
|
100
|
+
export function appendMessage(m) {
|
|
101
|
+
host?.appendMessage(m);
|
|
102
|
+
}
|
|
103
|
+
export function updateMessage(id2, u) {
|
|
104
|
+
host?.updateMessage(id2, u);
|
|
105
|
+
}
|
|
106
|
+
export function updateLastMessage(content, options) {
|
|
107
|
+
host?.updateLastMessage(content, options);
|
|
108
|
+
}
|
|
109
|
+
export function addSegment(segment) {
|
|
110
|
+
host?.addSegment(segment);
|
|
111
|
+
}
|
|
112
|
+
export function updateSegment(segmentId, updates) {
|
|
113
|
+
host?.updateSegment(segmentId, updates);
|
|
114
|
+
}
|
|
115
|
+
export function removeSegment(segmentId) {
|
|
116
|
+
host?.removeSegment(segmentId);
|
|
117
|
+
}
|
|
118
|
+
export function appendToSegment(segmentId, content) {
|
|
119
|
+
host?.appendToSegment(segmentId, content);
|
|
120
|
+
}
|
|
121
|
+
export function getMessages() {
|
|
122
|
+
return host?.getMessages() ?? internalMessages;
|
|
123
|
+
}
|
|
124
|
+
export function clearMessages() {
|
|
125
|
+
host?.clearMessages();
|
|
126
|
+
}
|
|
127
|
+
export function addBranch(messageId) {
|
|
128
|
+
return host?.addBranch(messageId) ?? 0;
|
|
129
|
+
}
|
|
130
|
+
export function addSiblingOf(existingId, message) {
|
|
131
|
+
return host?.addSiblingOf(existingId, message) ?? null;
|
|
132
|
+
}
|
|
133
|
+
export function truncateFrom(messageId) {
|
|
134
|
+
host?.truncateFrom(messageId);
|
|
135
|
+
}
|
|
136
|
+
export function truncateResponsesAfter(userMessageId) {
|
|
137
|
+
host?.truncateResponsesAfter(userMessageId);
|
|
138
|
+
}
|
|
139
|
+
export function injectTokenStream(messageId, tokens) {
|
|
140
|
+
return host?.streamTokens(messageId, tokens) ?? Promise.resolve();
|
|
141
|
+
}
|
|
142
|
+
export function stopTokenStream() {
|
|
143
|
+
host?.stopTokenStream();
|
|
144
|
+
}
|
|
145
|
+
export function setConversationId(id2) {
|
|
146
|
+
return host?.setConversationId(id2) ?? Promise.resolve();
|
|
147
|
+
}
|
|
148
|
+
export function scrollToBottom() {
|
|
149
|
+
viewportRef?.scrollToBottom?.();
|
|
150
|
+
}
|
|
151
|
+
export function getViewport() {
|
|
152
|
+
return viewportRef ?? null;
|
|
153
|
+
}
|
|
154
|
+
export function focusInput() {
|
|
155
|
+
composerRef?.focus?.();
|
|
156
|
+
}
|
|
157
|
+
export function isStreaming() {
|
|
158
|
+
return host?.isStreaming ?? false;
|
|
159
|
+
}
|
|
160
|
+
function _assertImperativeParity() {
|
|
161
|
+
return {
|
|
162
|
+
appendMessage,
|
|
163
|
+
updateMessage,
|
|
164
|
+
updateLastMessage,
|
|
165
|
+
addSegment,
|
|
166
|
+
updateSegment,
|
|
167
|
+
removeSegment,
|
|
168
|
+
appendToSegment,
|
|
169
|
+
getMessages,
|
|
170
|
+
clearMessages,
|
|
171
|
+
addBranch,
|
|
172
|
+
addSiblingOf,
|
|
173
|
+
truncateFrom,
|
|
174
|
+
truncateResponsesAfter,
|
|
175
|
+
injectTokenStream,
|
|
176
|
+
stopTokenStream,
|
|
177
|
+
setConversationId,
|
|
178
|
+
scrollToBottom,
|
|
179
|
+
focusInput,
|
|
180
|
+
isStreaming,
|
|
181
|
+
getViewport
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
</script>
|
|
185
|
+
|
|
186
|
+
<div
|
|
187
|
+
class="aparte-chat-container"
|
|
188
|
+
class:aparte-chat-container--auto-center={centerWhenEmpty}
|
|
189
|
+
data-aparte-chat
|
|
190
|
+
data-aparte-empty={centerWhenEmpty && internalMessages.length === 0 ? '' : null}
|
|
191
|
+
id={hostId}
|
|
192
|
+
bind:this={rootRef}
|
|
193
|
+
>
|
|
194
|
+
<aparte-chat-viewport bind:this={viewportRef} framework-managed="">
|
|
195
|
+
<!-- Welcome / placeholder shown inside the viewport while empty. -->
|
|
196
|
+
{#if internalMessages.length === 0}
|
|
197
|
+
<slot name="empty-state" />
|
|
198
|
+
{/if}
|
|
199
|
+
<!-- `bubble` slot renders your OWN element per message in place of
|
|
200
|
+
<aparte-chat-bubble>; driven by the reactive list so it streams live. -->
|
|
201
|
+
{#each internalMessages as m (m.id)}
|
|
202
|
+
<slot name="bubble" message={m}>
|
|
203
|
+
<aparte-chat-bubble
|
|
204
|
+
message-id={m.id}
|
|
205
|
+
data-role={m.role}
|
|
206
|
+
timestamp={m.timestamp}
|
|
207
|
+
content={m.content}
|
|
208
|
+
streaming={isAwaitingReply(m) ? '' : null}></aparte-chat-bubble>
|
|
209
|
+
</slot>
|
|
210
|
+
{/each}
|
|
211
|
+
<aparte-chat-status visible={typingActive ? '' : null} text={typingText}></aparte-chat-status>
|
|
212
|
+
</aparte-chat-viewport>
|
|
213
|
+
|
|
214
|
+
<!-- Content above the composer (banner, disclaimer, context chip). -->
|
|
215
|
+
<slot name="above-composer" />
|
|
216
|
+
|
|
217
|
+
<aparte-composer
|
|
218
|
+
bind:this={composerRef}
|
|
219
|
+
on:aparte-send={handleSend}
|
|
220
|
+
>
|
|
221
|
+
<!-- Custom composer via the `composer` slot; falls back to the default
|
|
222
|
+
shell (input · send, plus the attachment primitives when `attachments` is
|
|
223
|
+
set). Compose the headless aparte-composer-* primitives freely for a
|
|
224
|
+
skin-specific layout. -->
|
|
225
|
+
<slot name="composer">
|
|
226
|
+
<div class="aparte-composer-shell">
|
|
227
|
+
{#if attachments}<aparte-composer-attachments></aparte-composer-attachments>{/if}
|
|
228
|
+
<div class="aparte-composer-row">
|
|
229
|
+
{#if attachments}<aparte-composer-add-attachment></aparte-composer-add-attachment>{/if}
|
|
230
|
+
<aparte-composer-input></aparte-composer-input>
|
|
231
|
+
<aparte-composer-send></aparte-composer-send>
|
|
232
|
+
</div>
|
|
233
|
+
<!-- The composer's bottom row. ONE slot: placement is the DOM order, and
|
|
234
|
+
`margin-inline-start: auto` pushes a control to the end (a logical
|
|
235
|
+
property, so it reads correctly in RTL). Nothing is rendered at all when
|
|
236
|
+
the slot is unused. -->
|
|
237
|
+
{#if $$slots.toolbar}
|
|
238
|
+
<aparte-composer-toolbar>
|
|
239
|
+
<slot name="toolbar" />
|
|
240
|
+
</aparte-composer-toolbar>
|
|
241
|
+
{/if}
|
|
242
|
+
</div>
|
|
243
|
+
</slot>
|
|
244
|
+
</aparte-composer>
|
|
245
|
+
</div>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script>import { onMount, onDestroy, createEventDispatcher } from "svelte";
|
|
2
|
+
import { applyElementProps, APARTE_DEFAULT_UI_EVENTS } from "@aparte/core";
|
|
3
|
+
export let name;
|
|
4
|
+
export let props = {};
|
|
5
|
+
export let events = void 0;
|
|
6
|
+
const dispatch = createEventDispatcher();
|
|
7
|
+
let host;
|
|
8
|
+
let el = null;
|
|
9
|
+
let cleanups = [];
|
|
10
|
+
function applyProps() {
|
|
11
|
+
if (el) applyElementProps(el, props);
|
|
12
|
+
}
|
|
13
|
+
function create() {
|
|
14
|
+
if (!host) return;
|
|
15
|
+
el = document.createElement(name);
|
|
16
|
+
applyProps();
|
|
17
|
+
for (const ev of events ?? APARTE_DEFAULT_UI_EVENTS) {
|
|
18
|
+
const listener = (e) => dispatch("elementEvent", e);
|
|
19
|
+
el.addEventListener(ev, listener);
|
|
20
|
+
cleanups.push(() => el?.removeEventListener(ev, listener));
|
|
21
|
+
}
|
|
22
|
+
host.appendChild(el);
|
|
23
|
+
}
|
|
24
|
+
function destroy() {
|
|
25
|
+
for (const c of cleanups) c();
|
|
26
|
+
cleanups = [];
|
|
27
|
+
el?.remove();
|
|
28
|
+
el = null;
|
|
29
|
+
}
|
|
30
|
+
onMount(create);
|
|
31
|
+
onDestroy(destroy);
|
|
32
|
+
let lastName = name;
|
|
33
|
+
let lastEvtsKey = (events ?? APARTE_DEFAULT_UI_EVENTS).join("|");
|
|
34
|
+
$: {
|
|
35
|
+
const evtsKey = (events ?? APARTE_DEFAULT_UI_EVENTS).join("|");
|
|
36
|
+
if (el && (name !== lastName || evtsKey !== lastEvtsKey)) {
|
|
37
|
+
lastName = name;
|
|
38
|
+
lastEvtsKey = evtsKey;
|
|
39
|
+
destroy();
|
|
40
|
+
create();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
$: if (el && props) applyProps();
|
|
44
|
+
export function getElement() {
|
|
45
|
+
return el;
|
|
46
|
+
}
|
|
47
|
+
export function callMethod(methodName, ...args) {
|
|
48
|
+
const fn = el?.[methodName];
|
|
49
|
+
return typeof fn === "function" ? fn.apply(el, args) : void 0;
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<span bind:this={host} style="display: contents"></span>
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* aparté Svelte wrapper
|
|
3
|
-
*
|
|
2
|
+
* aparté Svelte wrapper — Svelte 4 AND 5, from one shipped source.
|
|
3
|
+
*
|
|
4
|
+
* The package publishes its `.svelte` files rather than a prebuilt bundle, so the
|
|
5
|
+
* consumer's own compiler handles them: no prebuilt artifact can serve both majors.
|
|
6
|
+
* Both are proven in a browser (`apps/examples/svelte4` and `svelte5`).
|
|
4
7
|
*/
|
|
5
8
|
export { default as AparteChat } from './AparteChat.svelte';
|
|
6
9
|
export { createAparteChat } from './stores/aparteChat.js';
|
|
7
|
-
export type { AparteChatStore
|
|
10
|
+
export type { AparteChatStore } from './stores/aparteChat.js';
|
|
11
|
+
export type { AparteChatImperativeApi } from '@aparte/core';
|
|
8
12
|
export { createAparteClient } from './stores/aparteClient.js';
|
|
9
13
|
export { createConversationManager } from './stores/conversationManager.js';
|
|
10
14
|
export { default as AparteUi } from './AparteUi.svelte';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAG5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAK9D,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAG5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEhE,YAAY,EACR,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACxB,MAAM,YAAY,CAAC"}
|