feat(memories): make preprocessing instructions required with tooltip hint (#13250)

* feat(memory): improve memory UI with empty states, toasts, and model filtering

* [autofix.ci] apply automated fixes

* fix: capture memory id/name before setTimeout to prevent stale closure null access

* error improvement and testcase addition

* fix: surface refresh errors, restore API error messages, flatten sidebar ternary, and add keyboard access to batch-size tooltip

* [autofix.ci] apply automated fixes

* biome fix

* fix types

* [autofix.ci] apply automated fixes

* fix: UI polish for Memories and Traces sidebar sections

* [autofix.ci] apply automated fixes

* feat(memories): surface memory config in summary card with popover details

* fix: replace magic index, template literals, and inline noops in Memories/Traces sidebar

* [autofix.ci] apply automated fixes

* ruff fix

* fix testcases

* improve testcase cov import clean up

* [autofix.ci] apply automated fixes

* fix import order

* accessibility fix

* [autofix.ci] apply automated fixes

* feat(memories): make preprocessing instructions required with tooltip hint

* ruff fix

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
This commit is contained in:
olayinkaadelakun
2026-05-22 12:01:09 -07:00
committed by GitHub
parent 68d4e8488e
commit 831c69f9b6
12 changed files with 110 additions and 33 deletions

View File

@ -951,7 +951,6 @@
"memory.noChunksYet": "Noch keine Brocken",
"memory.noId": "(keine ID)",
"memory.preprocessing": "Vorverarbeitung:",
"memory.preprocessingPromptPlaceholder": "Erstellen Sie eine prägnante Zusammenfassung, die die wichtigsten Fakten und den Kontext erfasst.",
"memory.processedMessages": "Verarbeitete Nachrichten",
"memory.providerLabel": "Leistungserbringer:",
"memory.reloadSessions": "Sitzungen und Nachrichten neu laden",

View File

@ -1563,6 +1563,7 @@
"memory.selectPreprocessingModel": "Select preprocessing model",
"memory.preprocessingModel": "Preprocessing model:",
"memory.preprocessingInstructions": "Preprocessing instructions:",
"memory.preprocessingInstructionsHint": "Summarize the key context, facts, and user preferences from this chat to remember for future conversations, skipping the casual chitchat.",
"memory.senderLabel": "Sender:",
"memory.sessionFilter": "Session filter",
"memory.sessionLabel": "Session:",

View File

@ -951,7 +951,6 @@
"memory.noChunksYet": "Todavía no hay fragmentos",
"memory.noId": "(sin ID)",
"memory.preprocessing": "Preprocesamiento:",
"memory.preprocessingPromptPlaceholder": "Elabora un resumen conciso que recoja los datos clave y el contexto.",
"memory.processedMessages": "Mensajes procesados",
"memory.providerLabel": "Proveedor:",
"memory.reloadSessions": "Actualizar sesiones y mensajes",

View File

@ -951,7 +951,6 @@
"memory.noChunksYet": "Pas encore de morceaux",
"memory.noId": "(sans identifiant)",
"memory.preprocessing": "Prétraitement :",
"memory.preprocessingPromptPlaceholder": "Rédigez un résumé concis qui résume les faits essentiels et le contexte.",
"memory.processedMessages": "Messages traités",
"memory.providerLabel": "Fournisseur :",
"memory.reloadSessions": "Actualiser les sessions et les messages",

View File

@ -951,7 +951,6 @@
"memory.noChunksYet": "まだチャンクはありません",
"memory.noId": "(IDなし)",
"memory.preprocessing": "前処理:",
"memory.preprocessingPromptPlaceholder": "重要な事実と背景を捉えた簡潔な要約を作成してください。",
"memory.processedMessages": "処理済みのメッセージ",
"memory.providerLabel": "プロバイダー:",
"memory.reloadSessions": "セッションとメッセージを再読み込みする",

View File

@ -951,7 +951,6 @@
"memory.noChunksYet": "Ainda não há trechos",
"memory.noId": "(sem ID)",
"memory.preprocessing": "Pré-processamento:",
"memory.preprocessingPromptPlaceholder": "Elabore um resumo conciso que resuma os principais fatos e o contexto.",
"memory.processedMessages": "Mensagens processadas",
"memory.providerLabel": "Provedor:",
"memory.reloadSessions": "Atualizar sessões e mensagens",

View File

@ -951,7 +951,6 @@
"memory.noChunksYet": "目前还没有块",
"memory.noId": "(无ID)",
"memory.preprocessing": "预处理:",
"memory.preprocessingPromptPlaceholder": "撰写一份简明扼要的摘要,概括关键事实和背景。",
"memory.processedMessages": "已处理的消息",
"memory.providerLabel": "提供商:",
"memory.reloadSessions": "重新加载会话和消息",

View File

@ -1,4 +1,5 @@
import { act, renderHook } from "@testing-library/react";
import type { ModelOption } from "@/components/core/parameterRenderComponent/components/modelInputComponent/types";
import { useCreateMemoryModal } from "../useCreateMemoryModal";
const mockSetErrorData = jest.fn();
@ -7,7 +8,12 @@ const mockMutate = jest.fn();
jest.mock("@/stores/alertStore", () => ({
__esModule: true,
default: (selector: any) =>
default: (
selector: (s: {
setErrorData: jest.Mock;
setSuccessData: jest.Mock;
}) => unknown,
) =>
selector({
setErrorData: mockSetErrorData,
setSuccessData: mockSetSuccessData,
@ -66,6 +72,44 @@ describe("useCreateMemoryModal", () => {
expect(mockMutate).not.toHaveBeenCalled();
});
it("requires preprocessing instructions when preprocessing is enabled", () => {
const { result } = renderHook(() =>
useCreateMemoryModal({ flowId: "flow-1", onClose: jest.fn() }),
);
act(() => {
result.current.setName("My Memory");
result.current.setSelectedEmbeddingModel([
{
id: "text-embedding-3-small",
name: "text-embedding-3-small",
provider: "OpenAI",
} as ModelOption,
]);
result.current.setPreprocessingEnabled(true);
result.current.setSelectedPreprocessingModel([
{
id: "gpt-4o-mini",
name: "gpt-4o-mini",
provider: "OpenAI",
} as ModelOption,
]);
// deliberately leave preprocessingPrompt empty
});
act(() => {
result.current.handleSubmit();
});
expect(mockSetErrorData).toHaveBeenCalledWith(
expect.objectContaining({
title: "Validation error",
list: ["Please provide preprocessing instructions"],
}),
);
expect(mockMutate).not.toHaveBeenCalled();
});
it("submits valid payload", () => {
const { result } = renderHook(() =>
useCreateMemoryModal({ flowId: "flow-1", onClose: jest.fn() }),
@ -78,12 +122,16 @@ describe("useCreateMemoryModal", () => {
id: "text-embedding-3-small",
name: "text-embedding-3-small",
provider: "OpenAI",
} as any,
} as ModelOption,
]);
result.current.setBatchSizeInput("5");
result.current.setPreprocessingEnabled(true);
result.current.setSelectedPreprocessingModel([
{ id: "gpt-4o-mini", name: "gpt-4o-mini", provider: "OpenAI" } as any,
{
id: "gpt-4o-mini",
name: "gpt-4o-mini",
provider: "OpenAI",
} as ModelOption,
]);
result.current.setPreprocessingPrompt("summarize");
});

View File

@ -1,3 +1,4 @@
import { useTranslation } from "react-i18next";
import ForwardedIconComponent from "@/components/common/genericIconComponent";
import ShadTooltip from "@/components/common/shadTooltipComponent";
import ModelInputComponent from "@/components/core/parameterRenderComponent/components/modelInputComponent";
@ -6,7 +7,6 @@ import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/utils/utils";
import { useTranslation } from "react-i18next";
import BaseModal from "../baseModal";
import { useCreateMemoryModal } from "./useCreateMemoryModal";
@ -183,14 +183,32 @@ export default function CreateMemoryModal({
)}
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="preprocessing-prompt">
Preprocessing Instructions (optional)
</Label>
<div className="flex items-center gap-1.5">
<Label htmlFor="preprocessing-prompt">
Preprocessing Instructions{" "}
<span className="text-destructive">*</span>
</Label>
<ShadTooltip
content={t("memory.preprocessingInstructionsHint")}
side="right"
>
<button
type="button"
tabIndex={0}
aria-label="Preprocessing instructions help"
className="cursor-help rounded focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
<ForwardedIconComponent
name="Info"
className="h-3.5 w-3.5 text-muted-foreground"
/>
</button>
</ShadTooltip>
</div>
<Textarea
id="preprocessing-prompt"
value={preprocessingPrompt}
onChange={(e) => setPreprocessingPrompt(e.target.value)}
placeholder={t("memory.preprocessingPromptPlaceholder")}
className="min-h-[80px] resize-y"
/>
</div>
@ -205,7 +223,8 @@ export default function CreateMemoryModal({
disabled:
!name.trim() ||
selectedEmbeddingModel.length === 0 ||
(preprocessingEnabled && selectedPreprocessingModel.length === 0),
(preprocessingEnabled && selectedPreprocessingModel.length === 0) ||
(preprocessingEnabled && !preprocessingPrompt.trim()),
}}
/>
</BaseModal>

View File

@ -129,6 +129,14 @@ export function useCreateMemoryModal({
return;
}
if (preprocessingEnabled && !preprocessingPrompt.trim()) {
setErrorData({
title: "Validation error",
list: ["Please provide preprocessing instructions"],
});
return;
}
const parsedThreshold = Math.max(1, parseInt(batchSizeInput, 10) || 1);
const embeddingSelection = selectedEmbeddingModel[0];
@ -142,10 +150,9 @@ export function useCreateMemoryModal({
preproc_model: preprocessingEnabled
? selectedPreprocessingModel[0]?.name
: undefined,
preproc_instructions:
preprocessingEnabled && preprocessingPrompt.trim()
? preprocessingPrompt.trim()
: undefined,
preproc_instructions: preprocessingEnabled
? preprocessingPrompt.trim()
: undefined,
});
};

View File

@ -141,17 +141,16 @@ export function MemoryDetails({
</span>
</div>
)}
{memory.preprocessing_enabled &&
memory.preproc_instructions && (
<div className="flex flex-col gap-0.5 border-t border-border pt-3">
<span className="font-medium text-muted-foreground">
{t("memory.preprocessingInstructions")}
</span>
<p className="leading-relaxed text-foreground">
{memory.preproc_instructions}
</p>
</div>
)}
{memory.preprocessing_enabled && (
<div className="flex flex-col gap-0.5 border-t border-border pt-3">
<span className="font-medium text-muted-foreground">
{t("memory.preprocessingInstructions")}
</span>
<p className="leading-relaxed text-foreground">
{memory.preproc_instructions}
</p>
</div>
)}
</div>
</PopoverContent>
</Popover>

View File

@ -208,7 +208,7 @@ describe("MemoryDetails — Config popover", () => {
expect(screen.getByText("gpt-4o-mini")).toBeInTheDocument();
});
it("does not show Preprocessing Instructions when preprocessing is disabled", () => {
it("does not show Preprocessing Instructions section when preprocessing is disabled", () => {
render(
<MemoryDetails
{...baseProps}
@ -225,7 +225,17 @@ describe("MemoryDetails — Config popover", () => {
).not.toBeInTheDocument();
});
it("shows Preprocessing Instructions when preprocessing is enabled and instructions are set", () => {
it("shows Preprocessing Instructions section when preprocessing is enabled", () => {
render(
<MemoryDetails
{...baseProps}
memory={{ ...baseMemory, preprocessing_enabled: true }}
/>,
);
expect(screen.getByText("Preprocessing instructions:")).toBeInTheDocument();
});
it("shows instructions value when preprocessing is enabled and instructions are set", () => {
render(
<MemoryDetails
{...baseProps}
@ -236,7 +246,6 @@ describe("MemoryDetails — Config popover", () => {
}}
/>,
);
// t("memory.preprocessingInstructions") = "Preprocessing instructions:"
expect(screen.getByText("Preprocessing instructions:")).toBeInTheDocument();
expect(screen.getByText("Summarise briefly.")).toBeInTheDocument();
});