Retry, edit and switch between versions of an answer
Let people retry an answer without losing the first one, edit what they sent, and move between the versions with the built-in picker.
<!-- The model here is scripted, and each call takes the next of three replies, so a
retry really answers differently. Retry the reply, then walk the versions with the
arrows; edit the question to ask it again. -->
<aparte-chat placeholder="Ask anything" style="height: 24rem"></aparte-chat>
<script>
const { aparteGlobalConfig } = window.aparte;
const { createScenarioProvider } = window.apartePlugins.scenario;
aparteGlobalConfig.registerAIProvider(createScenarioProvider({
id: 'versions',
name: 'Versions',
turns: [
'Cache it: the query is the same on every load, so keep its result for a minute.',
'Batch it: the panel sends ten lookups one after the other. Send them as one request.',
'Index it: the lookup reads the whole table. An index on the date column makes it instant.',
],
models: [{ id: 'versions', name: 'Versions' }],
}));
aparteGlobalConfig.setModelConfig({ defaultProvider: 'versions', defaultModel: 'versions' });
aparteGlobalConfig.setBubbleActions({ retry: true, edit: true });
</script>Turn on retry and edit
Section titled “Turn on retry and edit”import { aparteGlobalConfig } from '@aparte/core';
aparteGlobalConfig.setBubbleActions({ retry: true, edit: true });Both buttons are off until you turn them on: retry shows on replies, edit on the person’s own
messages. With AparteClient running — useAparteClient in React and Vue,
createAparteClient in Svelte and provideAparte() in Angular start one — that line is all.
Buttons lists the others and their order.
Retry keeps the old answer
Section titled “Retry keeps the old answer”-
YouMy dashboard query is slow. What should I do?
- Assistant Cache it: the query is the same on every load… version 1, kept
- Assistant Batch it: send the ten lookups as one request… ‹ 2 / 2 ›, on screen
A retry asks the model again from the same point and puts the new answer beside the old one, not
over it. As soon as a message has two versions, its footer shows the ‹ 2 / 2 › picker, and its
arrows switch the version on screen with nothing for you to wire. AparteClient asks again at a
sampling temperature of 0.4, whatever temperature the app set, so the new answer usually differs.
Offered on the person’s own messages too —
setBubbleActions({ user: ['edit', 'retry', 'copy'] }) — a retry answers that message afresh.
Edit a sent message
Section titled “Edit a sent message”The edit button opens the message in an editor: Enter saves,
Shift+Enter starts a new line and Escape cancels, whatever the
composer’s submit-on-enter. Saving a changed text sends aparte-edit with
{ messageId, content, targetId } — an unchanged or empty one just closes the editor — and
AparteClient changes the message in place, drops the replies under it and asks again. An edit
does not branch: the old text and its answers are gone. To keep them, handle aparte-edit yourself
and keep the old text in your own data; the built-in picker shows only on replies, so a switch
between versions of a question is yours to draw.
With your own loop
Section titled “With your own loop”import type { AparteChatViewport, AparteMessage } from '@aparte/core';
declare const viewport: AparteChatViewport;
declare function streamAnswer(history: AparteMessage[]): AsyncIterable<string>; // your model
async function answerInto(id: string): Promise<void> {
// The path on screen, ending in the new empty reply: everything before it is the history.
const history = viewport.getMessages().slice(0, -1);
for await (const chunk of streamAnswer(history)) viewport.appendToken(id, chunk);
viewport.completeMessage(id);
}
document.addEventListener('aparte-retry', (event) => {
const id = viewport.addSiblingOf(event.detail.messageId, {
id: crypto.randomUUID(), role: 'assistant', content: '', timestamp: Date.now(),
});
if (id) void answerInto(id);
});
document.addEventListener('aparte-edit', (event) => {
const { messageId, content } = event.detail;
viewport.updateMessage(messageId, { content });
viewport.truncateResponsesAfter(messageId);
const id = viewport.addSiblingOf(messageId, {
id: crypto.randomUUID(), role: 'assistant', content: '', timestamp: Date.now(),
});
if (id) void answerInto(id);
});The buttons only send their event; without a handler, an edit puts the original text back.
addSiblingOf reads the role of the message you give it: beside a reply it adds a version of that
reply, under one of the person’s messages it adds an answer to it — which is how the edit handler
gets its fresh reply. truncateResponsesAfter drops every version of the replies under the edited
message, so the new one starts alone. In React, Vue, Svelte or Angular, the framework package owns
the messages: call addSiblingOf, updateMessage and truncateResponsesAfter on the chat’s ref,
and stream with its injectTokenStream(id, tokens) in place of answerInto.
While a reply streams
Section titled “While a reply streams”The version arrows, and retry and edit on every message, are disabled until the reply is done;
Stop stays live. <aparte-chat-viewport> carries data-busy meanwhile, for controls of your own:
aparte-chat:has(aparte-chat-viewport[data-busy]) .my-button { opacity: 0.5; }.
Restyle it or take it back
Section titled “Restyle it or take it back”import { aparteGlobalConfig } from '@aparte/core';
aparteGlobalConfig.setBubbleActions({ retry: false }); // keep edit, drop retry
aparteGlobalConfig.setBubbleActions({ user: ['copy'] }); // your messages: copy onlyA user or assistant list replaces the flags for that role: to drop retry there, leave it out of
the list. The picker needs no switch of its own: it shows only on a message that has versions. Its counter
can be drawn your way, dots for instance, with
setSiblingNavRenderer.