perf: fix N+1 query pattern across all modules + rack numbering
CRITICAL PERF BUG: Every hook did storage.list() (1 HTTP call fetching ALL items with values, discarding values, returning only keys) then storage.get() for EACH key (N individual HTTP calls re-fetching values one by one). With 6 entries + contacts + tags, Registratura page fired ~40 sequential HTTP requests on load, where 3 would suffice. Fix: Replace list()+N*get() with single exportAll() call in ALL hooks: - registratura/registry-service.ts (added exportAll to RegistryStorage interface) - address-book/use-contacts.ts - it-inventory/use-inventory.ts - password-vault/use-vault.ts - word-templates/use-templates.ts - prompt-generator/use-prompt-generator.ts - hot-desk/use-reservations.ts - email-signature/use-saved-signatures.ts - digital-signatures/use-signatures.ts - ai-chat/use-chat.ts - core/tagging/tag-service.ts (uses storage.export()) Additional fixes: - registratura/use-registry.ts: addEntry uses optimistic local state update instead of double-refresh; closeEntry batches saves with Promise.all + single refresh - server-rack.tsx: reversed slot rendering so U1 is at bottom (standard rack numbering, per user's physical rack) Performance impact: ~90% reduction in HTTP requests on page load for all modules
This commit is contained in:
@@ -1,39 +1,42 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import { useStorage } from '@/core/storage';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import type { PromptTemplate, PromptHistoryEntry, OutputMode } from '../types';
|
||||
import { BUILTIN_TEMPLATES } from '../services/builtin-templates';
|
||||
import { composePrompt } from '../services/prompt-composer';
|
||||
import { useState, useCallback, useMemo, useEffect } from "react";
|
||||
import { useStorage } from "@/core/storage";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import type { PromptTemplate, PromptHistoryEntry, OutputMode } from "../types";
|
||||
import { BUILTIN_TEMPLATES } from "../services/builtin-templates";
|
||||
import { composePrompt } from "../services/prompt-composer";
|
||||
|
||||
const HISTORY_PREFIX = 'history:';
|
||||
const TEMPLATE_PREFIX = 'template:';
|
||||
const HISTORY_PREFIX = "history:";
|
||||
const TEMPLATE_PREFIX = "template:";
|
||||
|
||||
export function usePromptGenerator() {
|
||||
const storage = useStorage('prompt-generator');
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<PromptTemplate | null>(null);
|
||||
const storage = useStorage("prompt-generator");
|
||||
const [selectedTemplate, setSelectedTemplate] =
|
||||
useState<PromptTemplate | null>(null);
|
||||
const [values, setValues] = useState<Record<string, unknown>>({});
|
||||
const [customTemplates, setCustomTemplates] = useState<PromptTemplate[]>([]);
|
||||
const [history, setHistory] = useState<PromptHistoryEntry[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const allTemplates = useMemo(() => [...BUILTIN_TEMPLATES, ...customTemplates], [customTemplates]);
|
||||
const allTemplates = useMemo(
|
||||
() => [...BUILTIN_TEMPLATES, ...customTemplates],
|
||||
[customTemplates],
|
||||
);
|
||||
|
||||
// Load custom templates and history
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
const keys = await storage.list();
|
||||
const all = await storage.exportAll();
|
||||
const templates: PromptTemplate[] = [];
|
||||
const entries: PromptHistoryEntry[] = [];
|
||||
for (const key of keys) {
|
||||
for (const [key, value] of Object.entries(all)) {
|
||||
if (!value) continue;
|
||||
if (key.startsWith(TEMPLATE_PREFIX)) {
|
||||
const t = await storage.get<PromptTemplate>(key);
|
||||
if (t) templates.push(t);
|
||||
templates.push(value as PromptTemplate);
|
||||
} else if (key.startsWith(HISTORY_PREFIX)) {
|
||||
const h = await storage.get<PromptHistoryEntry>(key);
|
||||
if (h) entries.push(h);
|
||||
entries.push(value as PromptHistoryEntry);
|
||||
}
|
||||
}
|
||||
entries.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
||||
@@ -59,7 +62,7 @@ export function usePromptGenerator() {
|
||||
}, []);
|
||||
|
||||
const composedPrompt = useMemo(() => {
|
||||
if (!selectedTemplate) return '';
|
||||
if (!selectedTemplate) return "";
|
||||
return composePrompt(selectedTemplate, values);
|
||||
}, [selectedTemplate, values]);
|
||||
|
||||
@@ -74,7 +77,10 @@ export function usePromptGenerator() {
|
||||
composedPrompt,
|
||||
outputMode: selectedTemplate.outputMode,
|
||||
providerProfile: selectedTemplate.providerProfile ?? null,
|
||||
safetyBlocks: selectedTemplate.safetyBlocks?.filter((s) => s.enabled).map((s) => s.id) ?? [],
|
||||
safetyBlocks:
|
||||
selectedTemplate.safetyBlocks
|
||||
?.filter((s) => s.enabled)
|
||||
.map((s) => s.id) ?? [],
|
||||
tags: selectedTemplate.tags,
|
||||
isFavorite: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
@@ -84,10 +90,13 @@ export function usePromptGenerator() {
|
||||
return entry;
|
||||
}, [storage, selectedTemplate, values, composedPrompt]);
|
||||
|
||||
const deleteHistoryEntry = useCallback(async (id: string) => {
|
||||
await storage.delete(`${HISTORY_PREFIX}${id}`);
|
||||
setHistory((prev) => prev.filter((h) => h.id !== id));
|
||||
}, [storage]);
|
||||
const deleteHistoryEntry = useCallback(
|
||||
async (id: string) => {
|
||||
await storage.delete(`${HISTORY_PREFIX}${id}`);
|
||||
setHistory((prev) => prev.filter((h) => h.id !== id));
|
||||
},
|
||||
[storage],
|
||||
);
|
||||
|
||||
const clearSelection = useCallback(() => {
|
||||
setSelectedTemplate(null);
|
||||
|
||||
Reference in New Issue
Block a user