mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
refactor(ui/memory): wrap write helpers with react-query useMutation
Previously the Memory view read via `useQuery` but called the raw create/update/delete fetch helpers directly in handlers, tracking loading state with a local `submitting` flag and invalidating state via `refetch()`. That mixes two concerns: - it skips react-query's mutation state (isPending / isError / isSuccess) - `refetch()` only retouches the currently-mounted query instance, not other cached pages, so navigating back to an older page could show stale rows Switch the three write paths to `useMutation`: - `createMutation`, `updateMutation`, `deleteMutation` — each owns the mutation fn, success toast, and error toast. - Success handlers invalidate the whole `["memoryList", ...]` prefix via `queryClient.invalidateQueries`, so every cached page refetches (pagination + filter-aware). - Refresh button now invalidates instead of `refetch()`, keeping all behavior consistent. - handleSave/handleDelete become thin adapters that call `.mutateAsync`; their errors are swallowed locally since the mutation's onError has already surfaced the toast. Also tightened the edit modal's key-field tooltip to reflect the actual global-unique semantics (was "Unique per user/team scope"). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c5fafe56bb
commit
4a4b1123fb
2 changed files with 77 additions and 23 deletions
|
|
@ -80,7 +80,7 @@ export const MemoryEditModal: React.FC<MemoryEditModalProps> = ({
|
|||
label="Key"
|
||||
name="key"
|
||||
rules={[{ required: true, message: "Key is required" }]}
|
||||
tooltip="Unique per user/team scope. Acts as the namespace in /v1/memory/{key}."
|
||||
tooltip="Globally unique — two memories cannot share a key. Namespace your own keys if you need per-user isolation (e.g. user:123:notes)."
|
||||
>
|
||||
<Input
|
||||
placeholder="e.g. user_role"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
|
|
@ -73,13 +73,18 @@ export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
|
|||
setCurrentPage(1);
|
||||
}, [appliedSearch]);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
// React Query key prefix for all memory-list variants (paged + filtered).
|
||||
// Mutations invalidate the whole prefix so the next render refetches the
|
||||
// currently-visible page without us needing a manual refetch().
|
||||
const MEMORY_LIST_KEY = "memoryList" as const;
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ["memoryList", appliedSearch, currentPage],
|
||||
queryKey: [MEMORY_LIST_KEY, appliedSearch, currentPage],
|
||||
queryFn: () => {
|
||||
if (!accessToken) throw new Error("Access token required");
|
||||
// Prefix search matches the Redis-style mental model (namespace scan):
|
||||
|
|
@ -96,7 +101,67 @@ export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
|
|||
const rows = useMemo(() => data?.memories ?? [], [data]);
|
||||
const total = data?.total ?? 0;
|
||||
|
||||
const handleDelete = async (row: MemoryRow) => {
|
||||
// -- Mutations --------------------------------------------------------
|
||||
// All three write endpoints share the same success/error plumbing:
|
||||
// - on success: invalidate the list query so every cached page
|
||||
// refetches from scratch (pagination + filter-aware).
|
||||
// - on error: surface the message via antd `message.error`.
|
||||
|
||||
const invalidateList = () =>
|
||||
queryClient.invalidateQueries({ queryKey: [MEMORY_LIST_KEY] });
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (args: {
|
||||
key: string;
|
||||
value: string;
|
||||
metadata: unknown;
|
||||
}) => {
|
||||
if (!accessToken) throw new Error("Access token required");
|
||||
return createMemory(accessToken, args);
|
||||
},
|
||||
onSuccess: (row) => {
|
||||
message.success(`Created ${row.key}`);
|
||||
invalidateList();
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
message.error(`Save failed: ${err.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (args: {
|
||||
key: string;
|
||||
value?: string;
|
||||
metadata: unknown;
|
||||
}) => {
|
||||
if (!accessToken) throw new Error("Access token required");
|
||||
const { key, ...payload } = args;
|
||||
return updateMemory(accessToken, key, payload);
|
||||
},
|
||||
onSuccess: (row) => {
|
||||
message.success(`Updated ${row.key}`);
|
||||
invalidateList();
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
message.error(`Save failed: ${err.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (key: string) => {
|
||||
if (!accessToken) throw new Error("Access token required");
|
||||
return deleteMemory(accessToken, key).then(() => key);
|
||||
},
|
||||
onSuccess: (key) => {
|
||||
message.success(`Deleted ${key}`);
|
||||
invalidateList();
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
message.error(`Delete failed: ${err.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const handleDelete = (row: MemoryRow) => {
|
||||
Modal.confirm({
|
||||
title: "Delete memory",
|
||||
content: (
|
||||
|
|
@ -107,16 +172,7 @@ export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
|
|||
okText: "Delete",
|
||||
okType: "danger",
|
||||
cancelText: "Cancel",
|
||||
onOk: async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
await deleteMemory(accessToken, row.key);
|
||||
message.success(`Deleted ${row.key}`);
|
||||
refetch();
|
||||
} catch (err: any) {
|
||||
message.error(`Delete failed: ${err?.message ?? err}`);
|
||||
}
|
||||
},
|
||||
onOk: () => deleteMutation.mutateAsync(row.key).catch(() => {}),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -150,23 +206,21 @@ export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
|
|||
|
||||
try {
|
||||
if (isCreate) {
|
||||
await createMemory(accessToken, {
|
||||
await createMutation.mutateAsync({
|
||||
key,
|
||||
value,
|
||||
metadata: metadataPayload,
|
||||
});
|
||||
message.success(`Created ${key}`);
|
||||
} else {
|
||||
await updateMemory(accessToken, key, {
|
||||
await updateMutation.mutateAsync({
|
||||
key,
|
||||
value,
|
||||
metadata: metadataPayload,
|
||||
});
|
||||
message.success(`Updated ${key}`);
|
||||
}
|
||||
refetch();
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
message.error(`Save failed: ${err?.message ?? err}`);
|
||||
} catch {
|
||||
// error already surfaced by mutation's onError handler
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
@ -327,7 +381,7 @@ export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
|
|||
</Button>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => refetch()}
|
||||
onClick={() => invalidateList()}
|
||||
loading={isFetching && !isLoading}
|
||||
>
|
||||
Refresh
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue