feat(webui): support image uploads in composer and message bubbles
This commit is contained in:
@@ -20,7 +20,7 @@ class FakeSocket {
|
||||
onopen: (() => void) | null = null;
|
||||
onmessage: ((ev: MessageEvent) => void) | null = null;
|
||||
onerror: (() => void) | null = null;
|
||||
onclose: (() => void) | null = null;
|
||||
onclose: ((ev?: { code?: number }) => void) | null = null;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
@@ -36,6 +36,13 @@ class FakeSocket {
|
||||
this.onclose?.();
|
||||
}
|
||||
|
||||
/** Simulate a server-initiated drop with a specific wire-level close code
|
||||
* (e.g. ``1009`` for Message Too Big). */
|
||||
fakeCloseWithCode(code: number) {
|
||||
this.readyState = FakeSocket.CLOSED;
|
||||
this.onclose?.({ code });
|
||||
}
|
||||
|
||||
fakeOpen() {
|
||||
this.readyState = FakeSocket.OPEN;
|
||||
this.onopen?.();
|
||||
@@ -172,6 +179,95 @@ describe("NanobotClient", () => {
|
||||
expect(seen.at(-1)).toBe("closed");
|
||||
});
|
||||
|
||||
it("passes media through into the message envelope", () => {
|
||||
const client = new NanobotClient({
|
||||
url: "ws://test",
|
||||
reconnect: false,
|
||||
socketFactory: (url) => new FakeSocket(url) as unknown as WebSocket,
|
||||
});
|
||||
client.connect();
|
||||
lastSocket().fakeOpen();
|
||||
client.sendMessage("chat-x", "look", [
|
||||
{ data_url: "data:image/png;base64,AAAA", name: "shot.png" },
|
||||
]);
|
||||
const lastFrame = JSON.parse(lastSocket().sent.at(-1) as string);
|
||||
expect(lastFrame).toEqual({
|
||||
type: "message",
|
||||
chat_id: "chat-x",
|
||||
content: "look",
|
||||
media: [{ data_url: "data:image/png;base64,AAAA", name: "shot.png" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("omits media from the envelope when no images are attached", () => {
|
||||
const client = new NanobotClient({
|
||||
url: "ws://test",
|
||||
reconnect: false,
|
||||
socketFactory: (url) => new FakeSocket(url) as unknown as WebSocket,
|
||||
});
|
||||
client.connect();
|
||||
lastSocket().fakeOpen();
|
||||
client.sendMessage("chat-x", "hello");
|
||||
const lastFrame = JSON.parse(lastSocket().sent.at(-1) as string);
|
||||
expect(lastFrame).not.toHaveProperty("media");
|
||||
expect(lastFrame).toEqual({
|
||||
type: "message",
|
||||
chat_id: "chat-x",
|
||||
content: "hello",
|
||||
});
|
||||
});
|
||||
|
||||
it("emits a message_too_big error when the socket closes with code 1009", () => {
|
||||
const client = new NanobotClient({
|
||||
url: "ws://test",
|
||||
reconnect: false,
|
||||
socketFactory: (url) => new FakeSocket(url) as unknown as WebSocket,
|
||||
});
|
||||
const errors: Array<{ kind: string }> = [];
|
||||
client.onError((e) => errors.push(e));
|
||||
client.connect();
|
||||
lastSocket().fakeOpen();
|
||||
// Server rejected an outbound frame as too large.
|
||||
lastSocket().fakeCloseWithCode(1009);
|
||||
expect(errors).toEqual([{ kind: "message_too_big" }]);
|
||||
});
|
||||
|
||||
it("isolates throwing error handlers so reconnect bookkeeping still runs", async () => {
|
||||
const client = new NanobotClient({
|
||||
url: "ws://test",
|
||||
reconnect: true,
|
||||
maxBackoffMs: 5,
|
||||
socketFactory: (url) => new FakeSocket(url) as unknown as WebSocket,
|
||||
});
|
||||
// First handler explodes; subsequent reconnect state must be untouched.
|
||||
client.onError(() => {
|
||||
throw new Error("subscriber blew up");
|
||||
});
|
||||
const seenStatuses: string[] = [];
|
||||
client.onStatus((s) => seenStatuses.push(s));
|
||||
client.connect();
|
||||
lastSocket().fakeOpen();
|
||||
lastSocket().fakeCloseWithCode(1009);
|
||||
// Despite the throwing handler, the client must still schedule a reconnect.
|
||||
expect(seenStatuses).toContain("reconnecting");
|
||||
await vi.advanceTimersByTimeAsync(20);
|
||||
expect(FakeSocket.instances.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it("does not emit a stream error on a vanilla socket close", () => {
|
||||
const client = new NanobotClient({
|
||||
url: "ws://test",
|
||||
reconnect: false,
|
||||
socketFactory: (url) => new FakeSocket(url) as unknown as WebSocket,
|
||||
});
|
||||
const errors: Array<{ kind: string }> = [];
|
||||
client.onError((e) => errors.push(e));
|
||||
client.connect();
|
||||
lastSocket().fakeOpen();
|
||||
lastSocket().close();
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("surfaces 'reconnecting' only on an unexpected drop", async () => {
|
||||
const client = new NanobotClient({
|
||||
url: "ws://test",
|
||||
|
||||
Reference in New Issue
Block a user