@assistant-ui/react 0.15.10 → 0.15.12
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/ExternalThread.d.ts.map +1 -1
- package/dist/client/ExternalThread.js +330 -338
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/client/InMemoryThreadList.d.ts.map +1 -1
- package/dist/client/InMemoryThreadList.js +82 -86
- package/dist/client/InMemoryThreadList.js.map +1 -1
- package/dist/context/react/ThreadViewportContext.js +2 -1
- package/dist/context/react/ThreadViewportContext.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +2 -1
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js +2 -1
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +4 -2
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportFooter.js +3 -1
- package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js +2 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/package.json +13 -14
- package/src/client/ExternalThread.ts +26 -43
- package/src/client/InMemoryThreadList.ts +5 -4
- package/src/tests/external-thread-parity.test.tsx +95 -0
- package/src/tests/in-memory-thread-list.test.tsx +55 -0
|
@@ -322,6 +322,101 @@ describe("ExternalThread composer", () => {
|
|
|
322
322
|
expect(steer).not.toHaveBeenCalled();
|
|
323
323
|
});
|
|
324
324
|
|
|
325
|
+
it("dispatches a same-tick beginEdit + setText + send sequence", async () => {
|
|
326
|
+
const onEdit = vi.fn();
|
|
327
|
+
const { aui } = renderThread({
|
|
328
|
+
messages: [
|
|
329
|
+
{
|
|
330
|
+
id: "u1",
|
|
331
|
+
role: "user",
|
|
332
|
+
content: [{ type: "text", text: "hi" }],
|
|
333
|
+
createdAt: new Date(0),
|
|
334
|
+
attachments: [],
|
|
335
|
+
metadata: { custom: {} },
|
|
336
|
+
} as unknown as ExternalThreadMessage,
|
|
337
|
+
],
|
|
338
|
+
isRunning: false,
|
|
339
|
+
onEdit,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const composer = () => aui().thread.message({ id: "u1" }).composer();
|
|
343
|
+
composer().beginEdit();
|
|
344
|
+
composer().setText("edited");
|
|
345
|
+
composer().send();
|
|
346
|
+
|
|
347
|
+
await waitFor(() => expect(onEdit).toHaveBeenCalledTimes(1));
|
|
348
|
+
expect(onEdit.mock.calls[0]![0]).toMatchObject({
|
|
349
|
+
sourceId: "u1",
|
|
350
|
+
content: [{ type: "text", text: "edited" }],
|
|
351
|
+
});
|
|
352
|
+
await waitFor(() => expect(composer().getState().isEditing).toBe(false));
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("prefills the edit composer from the message on beginEdit", async () => {
|
|
356
|
+
const onEdit = vi.fn();
|
|
357
|
+
const { aui } = renderThread({
|
|
358
|
+
messages: [
|
|
359
|
+
{
|
|
360
|
+
id: "a1",
|
|
361
|
+
role: "assistant",
|
|
362
|
+
content: [{ type: "text", text: "original answer" }],
|
|
363
|
+
createdAt: new Date(0),
|
|
364
|
+
attachments: [
|
|
365
|
+
{
|
|
366
|
+
id: "att1",
|
|
367
|
+
type: "file",
|
|
368
|
+
name: "a.txt",
|
|
369
|
+
contentType: "text/plain",
|
|
370
|
+
status: { type: "complete" },
|
|
371
|
+
content: [],
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
metadata: { custom: {} },
|
|
375
|
+
} as unknown as ExternalThreadMessage,
|
|
376
|
+
],
|
|
377
|
+
isRunning: false,
|
|
378
|
+
onEdit,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const composer = () => aui().thread.message({ id: "a1" }).composer();
|
|
382
|
+
composer().beginEdit();
|
|
383
|
+
await waitFor(() => {
|
|
384
|
+
const state = composer().getState();
|
|
385
|
+
expect(state.text).toBe("original answer");
|
|
386
|
+
expect(state.role).toBe("assistant");
|
|
387
|
+
expect(state.attachments).toHaveLength(1);
|
|
388
|
+
});
|
|
389
|
+
expect(() => composer().beginEdit()).toThrow("Edit already in progress");
|
|
390
|
+
|
|
391
|
+
composer().send();
|
|
392
|
+
await waitFor(() => expect(onEdit).toHaveBeenCalledTimes(1));
|
|
393
|
+
expect(onEdit.mock.calls[0]![0]).toMatchObject({
|
|
394
|
+
sourceId: "a1",
|
|
395
|
+
content: [{ type: "text", text: "original answer" }],
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("throws on edit-composer send before beginEdit", () => {
|
|
400
|
+
const { aui } = renderThread({
|
|
401
|
+
messages: [
|
|
402
|
+
{
|
|
403
|
+
id: "u1",
|
|
404
|
+
role: "user",
|
|
405
|
+
content: [{ type: "text", text: "hi" }],
|
|
406
|
+
createdAt: new Date(0),
|
|
407
|
+
attachments: [],
|
|
408
|
+
metadata: { custom: {} },
|
|
409
|
+
} as unknown as ExternalThreadMessage,
|
|
410
|
+
],
|
|
411
|
+
isRunning: false,
|
|
412
|
+
onEdit: vi.fn(),
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
expect(() => aui().thread.message({ id: "u1" }).composer().send()).toThrow(
|
|
416
|
+
"Composer is not available",
|
|
417
|
+
);
|
|
418
|
+
});
|
|
419
|
+
|
|
325
420
|
it("throws on beginEdit when the runtime has no edit handler", () => {
|
|
326
421
|
const { aui } = renderThread({
|
|
327
422
|
messages: [
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { render, waitFor } from "@testing-library/react";
|
|
4
|
+
import type { FC } from "react";
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
import { useAui, AuiProvider } from "@assistant-ui/store";
|
|
7
|
+
import { InMemoryThreadList } from "../client/InMemoryThreadList";
|
|
8
|
+
import { ExternalThread } from "../client/ExternalThread";
|
|
9
|
+
|
|
10
|
+
const renderThreads = () => {
|
|
11
|
+
const captured: { aui?: ReturnType<typeof useAui> } = {};
|
|
12
|
+
const Capture: FC = () => {
|
|
13
|
+
captured.aui = useAui();
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
const App: FC = () => {
|
|
17
|
+
const aui = useAui({
|
|
18
|
+
threads: InMemoryThreadList({
|
|
19
|
+
thread: () => ExternalThread({ messages: [] }),
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
return (
|
|
23
|
+
<AuiProvider value={aui}>
|
|
24
|
+
<Capture />
|
|
25
|
+
</AuiProvider>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
render(<App />);
|
|
29
|
+
return { aui: () => captured.aui! };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe("InMemoryThreadList delete", () => {
|
|
33
|
+
it("falls back to a live thread when the switch target is deleted in the same tick", async () => {
|
|
34
|
+
const { aui } = renderThreads();
|
|
35
|
+
|
|
36
|
+
aui().threads.switchToNewThread();
|
|
37
|
+
await waitFor(() =>
|
|
38
|
+
expect(aui().threads.getState().mainThreadId).not.toBe("main"),
|
|
39
|
+
);
|
|
40
|
+
const newId = aui().threads.getState().mainThreadId;
|
|
41
|
+
aui().threads.switchToThread("main");
|
|
42
|
+
await waitFor(() =>
|
|
43
|
+
expect(aui().threads.getState().mainThreadId).toBe("main"),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
aui().threads.switchToThread(newId);
|
|
47
|
+
aui().threads.item({ id: newId }).delete();
|
|
48
|
+
|
|
49
|
+
await waitFor(() => {
|
|
50
|
+
const state = aui().threads.getState();
|
|
51
|
+
expect(state.mainThreadId).toBe("main");
|
|
52
|
+
expect(state.threadIds).not.toContain(newId);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|