mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-22 17:42:24 +00:00
fix(webui): keep new chat during session refresh
This commit is contained in:
parent
3d3ebf1110
commit
de0a8f5e41
@ -27,13 +27,25 @@ export function useSessions(): {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const tokenRef = useRef(token);
|
const tokenRef = useRef(token);
|
||||||
|
const optimisticKeysRef = useRef<Set<string>>(new Set());
|
||||||
tokenRef.current = token;
|
tokenRef.current = token;
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const rows = await listSessions(tokenRef.current);
|
const rows = await listSessions(tokenRef.current);
|
||||||
setSessions(rows);
|
const serverKeys = new Set(rows.map((row) => row.key));
|
||||||
|
setSessions((prev) => [
|
||||||
|
...rows,
|
||||||
|
...prev.filter(
|
||||||
|
(session) =>
|
||||||
|
optimisticKeysRef.current.has(session.key) &&
|
||||||
|
!serverKeys.has(session.key),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
for (const key of Array.from(optimisticKeysRef.current)) {
|
||||||
|
if (serverKeys.has(key)) optimisticKeysRef.current.delete(key);
|
||||||
|
}
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg =
|
const msg =
|
||||||
@ -57,6 +69,7 @@ export function useSessions(): {
|
|||||||
const createChat = useCallback(async (): Promise<string> => {
|
const createChat = useCallback(async (): Promise<string> => {
|
||||||
const chatId = await client.newChat();
|
const chatId = await client.newChat();
|
||||||
const key = `websocket:${chatId}`;
|
const key = `websocket:${chatId}`;
|
||||||
|
optimisticKeysRef.current.add(key);
|
||||||
// Optimistic insert; a subsequent refresh will replace it with the
|
// Optimistic insert; a subsequent refresh will replace it with the
|
||||||
// authoritative row once the server persists the session.
|
// authoritative row once the server persists the session.
|
||||||
setSessions((prev) => [
|
setSessions((prev) => [
|
||||||
@ -77,6 +90,7 @@ export function useSessions(): {
|
|||||||
const deleteChat = useCallback(
|
const deleteChat = useCallback(
|
||||||
async (key: string) => {
|
async (key: string) => {
|
||||||
await apiDeleteSession(tokenRef.current, key);
|
await apiDeleteSession(tokenRef.current, key);
|
||||||
|
optimisticKeysRef.current.delete(key);
|
||||||
setSessions((prev) => prev.filter((s) => s.key !== key));
|
setSessions((prev) => prev.filter((s) => s.key !== key));
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
|
|||||||
@ -157,6 +157,53 @@ describe("useSessions", () => {
|
|||||||
expect(api.listSessions).toHaveBeenCalledTimes(2);
|
expect(api.listSessions).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps a newly created chat visible until the server session list catches up", async () => {
|
||||||
|
vi.mocked(api.listSessions)
|
||||||
|
.mockResolvedValueOnce([])
|
||||||
|
.mockResolvedValueOnce([])
|
||||||
|
.mockResolvedValueOnce([
|
||||||
|
{
|
||||||
|
key: "websocket:chat-new",
|
||||||
|
channel: "websocket",
|
||||||
|
chatId: "chat-new",
|
||||||
|
createdAt: "2026-05-20T10:00:00Z",
|
||||||
|
updatedAt: "2026-05-20T10:01:00Z",
|
||||||
|
title: "Generated title",
|
||||||
|
preview: "First message",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const client = fakeClient();
|
||||||
|
client.newChat.mockResolvedValue("chat-new");
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useSessions(), {
|
||||||
|
wrapper: wrap(client),
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.loading).toBe(false));
|
||||||
|
expect(result.current.sessions).toEqual([]);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await result.current.createChat();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.sessions.map((s) => s.key)).toEqual(["websocket:chat-new"]);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await result.current.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.sessions.map((s) => s.key)).toEqual(["websocket:chat-new"]);
|
||||||
|
expect(result.current.sessions[0]?.preview).toBe("");
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await result.current.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.sessions.map((s) => s.key)).toEqual(["websocket:chat-new"]);
|
||||||
|
expect(result.current.sessions[0]?.preview).toBe("First message");
|
||||||
|
expect(result.current.sessions[0]?.title).toBe("Generated title");
|
||||||
|
});
|
||||||
|
|
||||||
it("passes through WebUI transcript user media as images and media", async () => {
|
it("passes through WebUI transcript user media as images and media", async () => {
|
||||||
vi.mocked(api.fetchWebuiThread).mockResolvedValue({
|
vi.mocked(api.fetchWebuiThread).mockResolvedValue({
|
||||||
schemaVersion: 3,
|
schemaVersion: 3,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user