feat(FN-2732): add manual dream processing actions in dashboard
- Extend useMemoryData with triggerDreamNow and dreamRunning state by locating and invoking the Memory Dreams automation - Add Dream Now actions to MemoryView and SettingsModal memory settings with loading UI, success/error toasts, and file refresh in MemoryView - Expand hook and component tests to cover dream-trigger visibility and execution flows in both views - Update terminal mobile keyboard layout CSS contract expectations for the current mobile modal width and min-height rules
This commit is contained in:
@@ -13,6 +13,15 @@ vi.mock("../../api", () => ({
|
||||
fetchMemoryStats: vi.fn(),
|
||||
compactMemory: vi.fn(),
|
||||
fetchMemoryBackendStatus: vi.fn(),
|
||||
fetchSettings: vi.fn(),
|
||||
updateSettings: vi.fn(),
|
||||
fetchMemoryFiles: vi.fn(),
|
||||
fetchMemoryFile: vi.fn(),
|
||||
saveMemoryFile: vi.fn(),
|
||||
installQmd: vi.fn(),
|
||||
testMemoryRetrieval: vi.fn(),
|
||||
fetchAutomations: vi.fn(),
|
||||
runAutomation: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useMemoryBackendStatus hook
|
||||
@@ -42,6 +51,8 @@ import {
|
||||
triggerInsightExtraction,
|
||||
fetchMemoryAudit,
|
||||
compactMemory,
|
||||
fetchAutomations,
|
||||
runAutomation,
|
||||
} from "../../api";
|
||||
|
||||
describe("useMemoryData", () => {
|
||||
@@ -273,6 +284,69 @@ describe("useMemoryData", () => {
|
||||
expect(fetchMemoryInsights).toHaveBeenCalledTimes(2); // Initial + refresh
|
||||
});
|
||||
|
||||
it("triggerDreamNow finds and runs the Memory Dreams automation", async () => {
|
||||
vi.mocked(fetchMemory).mockResolvedValue({ content: "Initial content" });
|
||||
vi.mocked(fetchMemoryInsights).mockResolvedValue({ content: null, exists: false });
|
||||
vi.mocked(fetchMemoryAudit).mockResolvedValue({
|
||||
generatedAt: "2024-01-01T00:00:00.000Z",
|
||||
workingMemory: { exists: true, size: 20, sectionCount: 1 },
|
||||
insightsMemory: { exists: false, size: 0, insightCount: 0, categories: {} },
|
||||
extraction: { runAt: "", success: false, insightCount: 0, duplicateCount: 0, skippedCount: 0, summary: "" },
|
||||
pruning: { applied: false, reason: "", sizeDelta: 0, originalSize: 0, newSize: 0 },
|
||||
checks: [],
|
||||
health: "warning",
|
||||
});
|
||||
vi.mocked(fetchAutomations).mockResolvedValue([
|
||||
{ id: "auto-1", name: "Other", cron: "* * * * *", enabled: true },
|
||||
{ id: "auto-2", name: "Memory Dreams", cron: "0 4 * * *", enabled: true },
|
||||
] as any);
|
||||
vi.mocked(runAutomation).mockResolvedValue({ schedule: { id: "auto-2" }, result: { success: true } } as any);
|
||||
|
||||
const { result } = renderHook(() => useMemoryData({ projectId: "test-project" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.workingMemoryLoading).toBe(false);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.triggerDreamNow();
|
||||
});
|
||||
|
||||
expect(fetchAutomations).toHaveBeenCalledWith({ scope: "project", projectId: "test-project" });
|
||||
expect(runAutomation).toHaveBeenCalledWith("auto-2", { scope: "project", projectId: "test-project" });
|
||||
expect(result.current.dreamRunning).toBe(false);
|
||||
});
|
||||
|
||||
it("triggerDreamNow throws when Memory Dreams schedule is missing and resets state", async () => {
|
||||
vi.mocked(fetchMemory).mockResolvedValue({ content: "Initial content" });
|
||||
vi.mocked(fetchMemoryInsights).mockResolvedValue({ content: null, exists: false });
|
||||
vi.mocked(fetchMemoryAudit).mockResolvedValue({
|
||||
generatedAt: "2024-01-01T00:00:00.000Z",
|
||||
workingMemory: { exists: true, size: 20, sectionCount: 1 },
|
||||
insightsMemory: { exists: false, size: 0, insightCount: 0, categories: {} },
|
||||
extraction: { runAt: "", success: false, insightCount: 0, duplicateCount: 0, skippedCount: 0, summary: "" },
|
||||
pruning: { applied: false, reason: "", sizeDelta: 0, originalSize: 0, newSize: 0 },
|
||||
checks: [],
|
||||
health: "warning",
|
||||
});
|
||||
vi.mocked(fetchAutomations).mockResolvedValue([{ id: "auto-1", name: "Other" }] as any);
|
||||
|
||||
const { result } = renderHook(() => useMemoryData({ projectId: "test-project" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.workingMemoryLoading).toBe(false);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await expect(result.current.triggerDreamNow()).rejects.toThrow(
|
||||
"Memory Dreams schedule not found. Enable dream processing in memory settings first.",
|
||||
);
|
||||
});
|
||||
|
||||
expect(runAutomation).not.toHaveBeenCalled();
|
||||
expect(result.current.dreamRunning).toBe(false);
|
||||
});
|
||||
|
||||
it("sets correct loading states during async operations", async () => {
|
||||
vi.mocked(fetchMemory).mockResolvedValue({ content: "Initial content" });
|
||||
vi.mocked(fetchMemoryInsights).mockResolvedValue({ content: null, exists: false });
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
fetchMemoryFiles,
|
||||
fetchMemoryFile,
|
||||
saveMemoryFile,
|
||||
fetchAutomations,
|
||||
runAutomation,
|
||||
installQmd,
|
||||
testMemoryRetrieval,
|
||||
type MemoryAuditReport,
|
||||
@@ -85,6 +87,10 @@ interface UseMemoryDataResult {
|
||||
extractInsights: () => Promise<{ success: boolean; summary: string }>;
|
||||
extracting: boolean;
|
||||
|
||||
// Dreams
|
||||
triggerDreamNow: () => Promise<unknown>;
|
||||
dreamRunning: boolean;
|
||||
|
||||
// Audit
|
||||
auditReport: MemoryAuditReport | null;
|
||||
auditLoading: boolean;
|
||||
@@ -162,6 +168,9 @@ export function useMemoryData(options: UseMemoryDataOptions = {}): UseMemoryData
|
||||
// Extraction state
|
||||
const [extracting, setExtracting] = useState(false);
|
||||
|
||||
// Dreams state
|
||||
const [dreamRunning, setDreamRunning] = useState(false);
|
||||
|
||||
// Audit state
|
||||
const [auditReport, setAuditReport] = useState<MemoryAuditReport | null>(null);
|
||||
const [auditLoading, setAuditLoading] = useState(true);
|
||||
@@ -514,6 +523,21 @@ export function useMemoryData(options: UseMemoryDataOptions = {}): UseMemoryData
|
||||
}
|
||||
}, [projectId, refreshInsights, refreshAudit]);
|
||||
|
||||
const triggerDreamNow = useCallback(async () => {
|
||||
setDreamRunning(true);
|
||||
try {
|
||||
const automations = await fetchAutomations({ scope: "project", projectId });
|
||||
const memoryDreamsSchedule = automations.find((automation) => automation.name === "Memory Dreams");
|
||||
if (!memoryDreamsSchedule) {
|
||||
throw new Error("Memory Dreams schedule not found. Enable dream processing in memory settings first.");
|
||||
}
|
||||
|
||||
return await runAutomation(memoryDreamsSchedule.id, { scope: "project", projectId });
|
||||
} finally {
|
||||
setDreamRunning(false);
|
||||
}
|
||||
}, [projectId]);
|
||||
|
||||
// Compact memory
|
||||
const compactMemoryAction = useCallback(async (path?: string) => {
|
||||
setCompacting(true);
|
||||
@@ -582,6 +606,10 @@ export function useMemoryData(options: UseMemoryDataOptions = {}): UseMemoryData
|
||||
extractInsights,
|
||||
extracting,
|
||||
|
||||
// Dreams
|
||||
triggerDreamNow,
|
||||
dreamRunning,
|
||||
|
||||
// Audit
|
||||
auditReport,
|
||||
auditLoading,
|
||||
|
||||
Reference in New Issue
Block a user