@aparte/core 0.16.8 → 0.16.10
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/dist/client/aparte-client.d.ts +57 -2
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/lifecycle-events.d.ts +8 -1
- package/dist/client/lifecycle-events.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts +8 -0
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +6 -0
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-input.d.ts +1 -1
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/conversation-list/aparte-conversation-list.d.ts +5 -0
- package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
- package/dist/components/scroll-rail/aparte-scroll-rail.d.ts +91 -10
- package/dist/components/scroll-rail/aparte-scroll-rail.d.ts.map +1 -1
- package/dist/components/status/aparte-chat-status.d.ts +41 -3
- package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +8 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/conversations/conversation-manager.d.ts +29 -0
- package/dist/conversations/conversation-manager.d.ts.map +1 -1
- package/dist/conversations/index.d.ts +1 -1
- package/dist/conversations/index.d.ts.map +1 -1
- package/dist/custom-elements.json +10916 -10397
- package/dist/{dialog-triggers-D8wNICrT.js → dialog-triggers-cYGTlfZf.js} +128 -30
- package/dist/{dialog-triggers-D8wNICrT.js.map → dialog-triggers-cYGTlfZf.js.map} +1 -1
- package/dist/elicitation/panel.d.ts.map +1 -1
- package/dist/index.css +162 -40
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +447 -94
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +1 -1
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +1 -1
- package/dist/index.node.js.map +1 -1
- package/dist/interop/element-props.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts +25 -3
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/types/events.d.ts +4 -2
- package/dist/types/events.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -3151,9 +3151,24 @@ class AparteConversationManager {
|
|
|
3151
3151
|
_listeners = /* @__PURE__ */ new Set();
|
|
3152
3152
|
_initialized = false;
|
|
3153
3153
|
_retention = null;
|
|
3154
|
+
_titleProvider = null;
|
|
3154
3155
|
constructor(adapter, options) {
|
|
3155
3156
|
this._adapter = adapter;
|
|
3156
3157
|
this._retention = options?.retention ?? null;
|
|
3158
|
+
this._titleProvider = options?.titleProvider ?? null;
|
|
3159
|
+
}
|
|
3160
|
+
// ─── Title provider ─────────────────────────────────────────────────────
|
|
3161
|
+
/**
|
|
3162
|
+
* Replace how a new conversation is titled from its first user message.
|
|
3163
|
+
* `null` restores the default (the message's text). The provider is consulted
|
|
3164
|
+
* once per conversation, on that first message; `updateTitle` is untouched.
|
|
3165
|
+
*/
|
|
3166
|
+
setTitleProvider(provider) {
|
|
3167
|
+
this._titleProvider = provider;
|
|
3168
|
+
}
|
|
3169
|
+
/** The registered title provider, or `null` when the default applies. */
|
|
3170
|
+
getTitleProvider() {
|
|
3171
|
+
return this._titleProvider;
|
|
3157
3172
|
}
|
|
3158
3173
|
// ─── Initialisation ────────────────────────────────────────────────────
|
|
3159
3174
|
/** Load all conversations from the adapter. Call once at app startup. */
|
|
@@ -3229,7 +3244,7 @@ class AparteConversationManager {
|
|
|
3229
3244
|
...conv,
|
|
3230
3245
|
messages: [...conv.messages, msg],
|
|
3231
3246
|
updatedAt: Date.now(),
|
|
3232
|
-
title: isFirstUserMsg ? this.
|
|
3247
|
+
title: isFirstUserMsg ? await this._title(msg) : conv.title
|
|
3233
3248
|
};
|
|
3234
3249
|
this._replace(updated);
|
|
3235
3250
|
await this._adapter.save(updated);
|
|
@@ -3362,10 +3377,23 @@ class AparteConversationManager {
|
|
|
3362
3377
|
}
|
|
3363
3378
|
});
|
|
3364
3379
|
}
|
|
3380
|
+
/**
|
|
3381
|
+
* The title of a conversation, decided once, on its first user message: the
|
|
3382
|
+
* provider's answer when one is registered and answers, else the default.
|
|
3383
|
+
*/
|
|
3384
|
+
async _title(msg) {
|
|
3385
|
+
const fallback = this._autoTitle(msg);
|
|
3386
|
+
if (!this._titleProvider) return fallback;
|
|
3387
|
+
try {
|
|
3388
|
+
const title = (await this._titleProvider((msg.content ?? "").trim(), msg)).trim();
|
|
3389
|
+
return title || fallback;
|
|
3390
|
+
} catch (err) {
|
|
3391
|
+
console.warn("[AparteConversationManager] the title provider threw; the default title stands:", err);
|
|
3392
|
+
return fallback;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3365
3395
|
_autoTitle(msg) {
|
|
3366
|
-
|
|
3367
|
-
const text = typeof raw === "string" ? raw : (raw ?? "").toString();
|
|
3368
|
-
return text.trim() || "New Chat";
|
|
3396
|
+
return (msg.content ?? "").trim() || "New Chat";
|
|
3369
3397
|
}
|
|
3370
3398
|
}
|
|
3371
3399
|
function filesToAttachments(files) {
|
|
@@ -4385,13 +4413,13 @@ class AparteChatHost {
|
|
|
4385
4413
|
});
|
|
4386
4414
|
}
|
|
4387
4415
|
}
|
|
4388
|
-
function dispatchLifecycleEvent(target, name, detail) {
|
|
4389
|
-
const id = target.id || void 0;
|
|
4416
|
+
function dispatchLifecycleEvent(target, name, detail, targetId) {
|
|
4417
|
+
const id = targetId ?? (target.id || void 0);
|
|
4390
4418
|
const stamped = detail && typeof detail === "object" ? { targetId: id, ...detail } : detail;
|
|
4391
4419
|
target.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail: stamped }));
|
|
4392
4420
|
}
|
|
4393
4421
|
function createStreamAdapter(opts) {
|
|
4394
|
-
const { target, config, messageId } = opts;
|
|
4422
|
+
const { target, config, messageId, targetId } = opts;
|
|
4395
4423
|
let parser = new AparteStreamParser({ blocks: config.getStreamBlocks() });
|
|
4396
4424
|
let streaming = /* @__PURE__ */ new Set();
|
|
4397
4425
|
let thinkingId = null;
|
|
@@ -4479,7 +4507,7 @@ function createStreamAdapter(opts) {
|
|
|
4479
4507
|
}
|
|
4480
4508
|
case "tool-awaiting-approval":
|
|
4481
4509
|
target.updateSegment?.(`tool-${e.toolCallId}`, { status: "awaiting-approval" });
|
|
4482
|
-
dispatchLifecycleEvent(target, "aparte-tool-approval-request", { toolCallId: e.toolCallId, toolName: e.name, input: e.input });
|
|
4510
|
+
dispatchLifecycleEvent(target, "aparte-tool-approval-request", { toolCallId: e.toolCallId, toolName: e.name, input: e.input }, targetId);
|
|
4483
4511
|
break;
|
|
4484
4512
|
case "tool-approved":
|
|
4485
4513
|
target.updateSegment?.(`tool-${e.toolCallId}`, { status: "pending" });
|
|
@@ -4513,7 +4541,7 @@ function createStreamAdapter(opts) {
|
|
|
4513
4541
|
}
|
|
4514
4542
|
break;
|
|
4515
4543
|
case "run-aborted":
|
|
4516
|
-
dispatchLifecycleEvent(target, "aparte-message-aborted", { messageId });
|
|
4544
|
+
dispatchLifecycleEvent(target, "aparte-message-aborted", { messageId }, targetId);
|
|
4517
4545
|
break;
|
|
4518
4546
|
case "run-done":
|
|
4519
4547
|
target.updateMessage?.(messageId, { status: "completed" });
|
|
@@ -4690,12 +4718,20 @@ function buildEnumField(field, onChange, fallbackLabel, settle) {
|
|
|
4690
4718
|
wrap.appendChild(list);
|
|
4691
4719
|
const controls = () => Array.from(list.querySelectorAll(".aparte-elic-control"));
|
|
4692
4720
|
const other = () => list.querySelector('input[value="__other__"]');
|
|
4721
|
+
let arrowed = false;
|
|
4722
|
+
list.addEventListener("keydown", (e) => {
|
|
4723
|
+
arrowed = e.key.startsWith("Arrow");
|
|
4724
|
+
});
|
|
4725
|
+
list.addEventListener("click", () => {
|
|
4726
|
+
arrowed = false;
|
|
4727
|
+
});
|
|
4693
4728
|
list.addEventListener("change", () => {
|
|
4694
4729
|
const o = other();
|
|
4695
4730
|
if (o && otherText) {
|
|
4696
4731
|
otherText.style.display = o.checked ? "" : "none";
|
|
4697
|
-
if (o.checked) otherText.focus();
|
|
4732
|
+
if (o.checked && !arrowed) otherText.focus();
|
|
4698
4733
|
}
|
|
4734
|
+
arrowed = false;
|
|
4699
4735
|
onChange();
|
|
4700
4736
|
});
|
|
4701
4737
|
otherText?.addEventListener("input", onChange);
|
|
@@ -5313,6 +5349,7 @@ class AparteClient {
|
|
|
5313
5349
|
* sequence, so it can't drift between the three entry points.
|
|
5314
5350
|
*/
|
|
5315
5351
|
async _streamTurn(targetElement, messageId, provider, messages, modelId, authConfig, opts) {
|
|
5352
|
+
const chatId = this._targetIdOf(targetElement);
|
|
5316
5353
|
const registeredTools = this._toolsForCurrentModel();
|
|
5317
5354
|
let baseRequest = {
|
|
5318
5355
|
messages,
|
|
@@ -5328,22 +5365,22 @@ class AparteClient {
|
|
|
5328
5365
|
baseRequest = { ...baseRequest, tools: void 0 };
|
|
5329
5366
|
}
|
|
5330
5367
|
if (this._isAborted) {
|
|
5331
|
-
dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId });
|
|
5368
|
+
dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId }, chatId);
|
|
5332
5369
|
this._updateMessage(targetElement, messageId, { status: "completed" });
|
|
5333
5370
|
return;
|
|
5334
5371
|
}
|
|
5335
|
-
dispatchLifecycleEvent(targetElement, "aparte-message-start", { messageId, role: "assistant" });
|
|
5372
|
+
dispatchLifecycleEvent(targetElement, "aparte-message-start", { messageId, role: "assistant" }, chatId);
|
|
5336
5373
|
const streamController = new AbortController();
|
|
5337
5374
|
try {
|
|
5338
5375
|
const usage = await this._streamLoop(targetElement, messageId, provider, baseRequest, authConfig, streamController);
|
|
5339
5376
|
if (this._isAborted || streamController.signal.aborted) {
|
|
5340
5377
|
this._updateMessage(targetElement, messageId, { status: "completed" });
|
|
5341
5378
|
} else {
|
|
5342
|
-
dispatchLifecycleEvent(targetElement, "aparte-message-done", { messageId, role: "assistant", usage });
|
|
5379
|
+
dispatchLifecycleEvent(targetElement, "aparte-message-done", { messageId, role: "assistant", usage }, chatId);
|
|
5343
5380
|
}
|
|
5344
5381
|
} catch (error) {
|
|
5345
5382
|
if (this._isAborted || streamController.signal.aborted) {
|
|
5346
|
-
dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId });
|
|
5383
|
+
dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId }, chatId);
|
|
5347
5384
|
this._updateMessage(targetElement, messageId, { status: "completed" });
|
|
5348
5385
|
return;
|
|
5349
5386
|
}
|
|
@@ -5531,13 +5568,64 @@ class AparteClient {
|
|
|
5531
5568
|
return null;
|
|
5532
5569
|
}
|
|
5533
5570
|
/**
|
|
5534
|
-
*
|
|
5571
|
+
* Which chat a render target BELONGS to — the id the lifecycle events must carry.
|
|
5572
|
+
*
|
|
5573
|
+
* Not `target.id`: the target is whatever renders, and an `<aparte-chat>` shell
|
|
5574
|
+
* delegates that to its `.viewport`, which has no id. So every lifecycle event on
|
|
5575
|
+
* a shell-shaped chat went out unstamped, and the receive side reads a missing id
|
|
5576
|
+
* as "for every chat on the page" — deliberately, so a single-chat page needs no
|
|
5577
|
+
* wiring. On a two-chat page one chat's turn therefore drove every composer.
|
|
5578
|
+
*
|
|
5579
|
+
* The climb is the same rule `aparte-composer._ownTargetId` uses on the receive
|
|
5580
|
+
* side, and it has to be, or the two would disagree about which chat is which:
|
|
5581
|
+
* the nearest `aparte-chat` / `[data-aparte-chat]` ancestor (or the element
|
|
5582
|
+
* itself) that HAS an id. Deliberately not `chatBoundaryOf` — that returns the
|
|
5583
|
+
* boundary whether or not it is identified, and an id-less boundary must fall
|
|
5584
|
+
* through to the next one up rather than answer "no id".
|
|
5585
|
+
*/
|
|
5586
|
+
_targetIdOf(el2) {
|
|
5587
|
+
let node = el2 ?? null;
|
|
5588
|
+
while (node) {
|
|
5589
|
+
const tag = node.tagName?.toLowerCase();
|
|
5590
|
+
const isHost = tag === "aparte-chat" || node.hasAttribute?.("data-aparte-chat");
|
|
5591
|
+
if (isHost && node.id) return node.id;
|
|
5592
|
+
node = node.parentElement;
|
|
5593
|
+
}
|
|
5594
|
+
return el2?.id || void 0;
|
|
5595
|
+
}
|
|
5596
|
+
/**
|
|
5597
|
+
* Convert AparteMessage[] to AparteChatMessage[] for re-submission (retry, edit).
|
|
5598
|
+
*
|
|
5599
|
+
* The SLICE is each caller's own business — retry cuts before the reply it is
|
|
5600
|
+
* regenerating, edit cuts after the message being reworded, send cuts at the last
|
|
5601
|
+
* answered turn. What a message CONTRIBUTES to the wire is not: send, retry and
|
|
5602
|
+
* edit must not disagree about it, and they did. This one had no filter at all,
|
|
5603
|
+
* so a failed turn and a turn stopped before its first token both went out as
|
|
5604
|
+
* `{ role: 'assistant', content: '' }` — a claim the assistant said nothing, which
|
|
5605
|
+
* some providers reject outright and the rest read as an empty reply worth
|
|
5606
|
+
* imitating.
|
|
5535
5607
|
*/
|
|
5536
5608
|
_messagesToChatMessages(messages) {
|
|
5537
|
-
return
|
|
5609
|
+
return this._wireHistory(messages);
|
|
5610
|
+
}
|
|
5611
|
+
/**
|
|
5612
|
+
* The one rule for what a message contributes to the wire.
|
|
5613
|
+
*
|
|
5614
|
+
* Not the slice — the contribution: a role the wire has (`user`/`assistant`), a
|
|
5615
|
+
* turn that neither failed nor is still in flight, and text that is not empty.
|
|
5616
|
+
*
|
|
5617
|
+
* `_wireText` (not `m.content`): assistant replies stream their text into
|
|
5618
|
+
* `segments`, leaving `content` as '', so without flattening every reply would be
|
|
5619
|
+
* empty here and the drop below would swallow the whole transcript.
|
|
5620
|
+
*/
|
|
5621
|
+
_wireHistory(messages) {
|
|
5622
|
+
return messages.filter((m) => {
|
|
5623
|
+
if (m.role !== "user" && m.role !== "assistant") return false;
|
|
5624
|
+
return m.status !== "error" && !AparteClient._inFlight(m);
|
|
5625
|
+
}).map((m) => ({
|
|
5538
5626
|
role: m.role,
|
|
5539
5627
|
content: this._wireText(m)
|
|
5540
|
-
}));
|
|
5628
|
+
})).filter((m) => m.content.length > 0);
|
|
5541
5629
|
}
|
|
5542
5630
|
/** The "no model selected" warning is said once per client, not once per dropped send. */
|
|
5543
5631
|
_warnedNoModel = false;
|
|
@@ -5671,22 +5759,30 @@ class AparteClient {
|
|
|
5671
5759
|
messages.push({ role: "user", content: userMsg });
|
|
5672
5760
|
return messages;
|
|
5673
5761
|
}
|
|
5762
|
+
/**
|
|
5763
|
+
* A turn that has not finished yet. `status` is OPTIONAL on `AparteMessage`, so
|
|
5764
|
+
* the question the history gates can ask is "is this still in flight?", never
|
|
5765
|
+
* "is this completed?".
|
|
5766
|
+
*
|
|
5767
|
+
* The gates below used to ask the second one, and a host that SEEDS a transcript
|
|
5768
|
+
* — restoring a saved conversation, hydrating a server-rendered one — has no
|
|
5769
|
+
* reason to invent a status for turns that are already over. With none, the
|
|
5770
|
+
* cutoff never advanced past 0 and the whole transcript was sliced away: the
|
|
5771
|
+
* model got the new question with no context, and nothing in the UI said so,
|
|
5772
|
+
* because the viewport still showed every message.
|
|
5773
|
+
*/
|
|
5774
|
+
static _inFlight(m) {
|
|
5775
|
+
return m.status === "streaming" || m.status === "pending";
|
|
5776
|
+
}
|
|
5674
5777
|
_toHistoryMessages(messages) {
|
|
5675
5778
|
let cutoff = 0;
|
|
5676
5779
|
for (let i = 0; i < messages.length; i++) {
|
|
5677
5780
|
const m = messages[i];
|
|
5678
|
-
if (m.role === "assistant" && m.status
|
|
5781
|
+
if (m.role === "assistant" && m.status !== "error" && !AparteClient._inFlight(m)) {
|
|
5679
5782
|
cutoff = i + 1;
|
|
5680
5783
|
}
|
|
5681
5784
|
}
|
|
5682
|
-
return messages.slice(0, cutoff)
|
|
5683
|
-
if (m.role === "user") return m.status !== "error";
|
|
5684
|
-
if (m.role === "assistant") return m.status === "completed";
|
|
5685
|
-
return false;
|
|
5686
|
-
}).map((m) => ({
|
|
5687
|
-
role: m.role,
|
|
5688
|
-
content: this._wireText(m)
|
|
5689
|
-
})).filter((m) => m.content.length > 0);
|
|
5785
|
+
return this._wireHistory(messages.slice(0, cutoff));
|
|
5690
5786
|
}
|
|
5691
5787
|
/**
|
|
5692
5788
|
* A message's text as the MODEL should read it — the one mapper both history
|
|
@@ -5859,10 +5955,12 @@ ${content}
|
|
|
5859
5955
|
*/
|
|
5860
5956
|
async _runViaStreamRunner(streamRunner, targetElement, messageId, provider, baseRequest, authConfig, streamController) {
|
|
5861
5957
|
const signal = streamController.signal;
|
|
5958
|
+
const chatId = this._targetIdOf(targetElement);
|
|
5862
5959
|
const adapter = createStreamAdapter({
|
|
5863
5960
|
target: targetElement,
|
|
5864
5961
|
config: this._config,
|
|
5865
|
-
messageId
|
|
5962
|
+
messageId,
|
|
5963
|
+
...chatId ? { targetId: chatId } : {}
|
|
5866
5964
|
});
|
|
5867
5965
|
const prefixSegments = baseRequest._meta?.["prefixSegments"];
|
|
5868
5966
|
const emitter = (event) => {
|
|
@@ -5955,7 +6053,7 @@ ${content}
|
|
|
5955
6053
|
segments: [errorSegment]
|
|
5956
6054
|
});
|
|
5957
6055
|
}
|
|
5958
|
-
dispatchLifecycleEvent(target, "aparte-message-error", { messageId, error });
|
|
6056
|
+
dispatchLifecycleEvent(target, "aparte-message-error", { messageId, error }, this._targetIdOf(target));
|
|
5959
6057
|
}
|
|
5960
6058
|
}
|
|
5961
6059
|
function findHead(node) {
|
|
@@ -6269,7 +6367,7 @@ function applyElementProps(el2, props, transformValue = (value) => value) {
|
|
|
6269
6367
|
if (value === null || value === void 0) el2.style.removeProperty(key);
|
|
6270
6368
|
else if (typeof value === "object" || typeof value === "function") el2.style.removeProperty(key);
|
|
6271
6369
|
else el2.style.setProperty(key, String(value));
|
|
6272
|
-
} else if (key.startsWith("on")) {
|
|
6370
|
+
} else if (key.toLowerCase().startsWith("on")) {
|
|
6273
6371
|
el2.removeAttribute(key);
|
|
6274
6372
|
} else if (value === null || value === void 0 || value === false) {
|
|
6275
6373
|
el2.removeAttribute(key);
|
|
@@ -6407,4 +6505,4 @@ export {
|
|
|
6407
6505
|
collectRendererStyles as y,
|
|
6408
6506
|
contentToText as z
|
|
6409
6507
|
};
|
|
6410
|
-
//# sourceMappingURL=dialog-triggers-
|
|
6508
|
+
//# sourceMappingURL=dialog-triggers-cYGTlfZf.js.map
|