mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-25 23:45:29 +08:00
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:
@ -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",
|
||||
|
||||
@ -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:",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -951,7 +951,6 @@
|
||||
"memory.noChunksYet": "まだチャンクはありません",
|
||||
"memory.noId": "(IDなし)",
|
||||
"memory.preprocessing": "前処理:",
|
||||
"memory.preprocessingPromptPlaceholder": "重要な事実と背景を捉えた簡潔な要約を作成してください。",
|
||||
"memory.processedMessages": "処理済みのメッセージ",
|
||||
"memory.providerLabel": "プロバイダー:",
|
||||
"memory.reloadSessions": "セッションとメッセージを再読み込みする",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -951,7 +951,6 @@
|
||||
"memory.noChunksYet": "目前还没有块",
|
||||
"memory.noId": "(无ID)",
|
||||
"memory.preprocessing": "预处理:",
|
||||
"memory.preprocessingPromptPlaceholder": "撰写一份简明扼要的摘要,概括关键事实和背景。",
|
||||
"memory.processedMessages": "已处理的消息",
|
||||
"memory.providerLabel": "提供商:",
|
||||
"memory.reloadSessions": "重新加载会话和消息",
|
||||
|
||||
@ -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");
|
||||
});
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user