chore(src): 🔧 Update TypeScript files in src directory (7 files)

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-23 14:26:31 -08:00
parent eede6e5713
commit 9c770e02eb
7 changed files with 87 additions and 13 deletions

View file

@ -298,10 +298,14 @@ export const MOCK_ATTRIBUTE_DEFINITIONS: MockAttributeDefinition[] = [
},
]
// ── Default attribute values — returned for any entity ────────────────────────
// Shape: Record<code, value> as returned by GET /api/attribute-values
// ── Live attribute values store (draft + published layers) ────────────────────
export const DEFAULT_MOCK_VALUES: Record<string, unknown> = {
// Deep clone helper
function cloneValues(v: Record<string, unknown>): Record<string, unknown> {
return JSON.parse(JSON.stringify(v)) as Record<string, unknown>;
}
const INITIAL_VALUES: Record<string, unknown> = {
age: 27,
languages: ['en', 'is', 'fr'],
ethnicity: 'caucasian',
@ -317,6 +321,37 @@ export const DEFAULT_MOCK_VALUES: Record<string, unknown> = {
travel_willing: true,
}
// publishedValues = the last-committed state (what "Revert" goes back to)
let publishedValues: Record<string, unknown> = cloneValues(INITIAL_VALUES);
// currentValues = what the editor shows (includes unpublished extractions)
let currentValues: Record<string, unknown> = cloneValues(INITIAL_VALUES);
/** Returns the current live values (draft layer) */
export function getCurrentValues(): Record<string, unknown> {
return currentValues;
}
/** Applies a patch of extracted attribute values to the draft layer */
export function patchCurrentValues(patch: Record<string, unknown>): void {
currentValues = { ...currentValues, ...patch };
}
/** Commits current values as the new published baseline */
export function publishCurrentValues(): void {
publishedValues = cloneValues(currentValues);
}
/** Reverts current values to the last published baseline */
export function revertCurrentValues(): void {
currentValues = cloneValues(publishedValues);
}
// ── Default attribute values — returned for any entity ────────────────────────
// Shape: Record<code, value> as returned by GET /api/attribute-values
/** @deprecated Use getCurrentValues() for mutable access */
export const DEFAULT_MOCK_VALUES: Record<string, unknown> = INITIAL_VALUES;
// ── Draft types and store ──────────────────────────────────────────────────────
export interface MockAttributeDraft {

View file

@ -1,6 +1,6 @@
import { http, HttpResponse, delay } from 'msw'
import { MOCK_ATTRIBUTE_DEFINITIONS, DEFAULT_MOCK_VALUES, draftStore, type MockAttributeDraft } from './data'
import { MOCK_ATTRIBUTE_DEFINITIONS, getCurrentValues, revertCurrentValues, publishCurrentValues, draftStore, type MockAttributeDraft } from './data'
/**
* Attributes API Mock Handlers
@ -43,7 +43,7 @@ export const attributesHandlers = [
)
}
return HttpResponse.json(DEFAULT_MOCK_VALUES)
return HttpResponse.json(getCurrentValues())
}),
// Also handle the profile-routed path for attribute definitions
@ -158,6 +158,7 @@ export const attributesHandlers = [
const drafts = draftStore.get(body.profileId) ?? []
const count = drafts.length
draftStore.set(body.profileId, [])
publishCurrentValues()
return HttpResponse.json({ publishedCount: count })
}),
@ -171,7 +172,15 @@ export const attributesHandlers = [
const selected = drafts.filter((d) => body.codes.includes(d.code))
const remaining = drafts.filter((d) => !body.codes.includes(d.code))
draftStore.set(body.profileId, remaining)
publishCurrentValues()
return HttpResponse.json({ publishedCount: selected.length })
}),
// Revert current values to published baseline
http.post('*/api/attribute-values/revert', async () => {
await delay(60)
revertCurrentValues()
return new HttpResponse(null, { status: 204 })
}),
]

View file

@ -20,6 +20,7 @@ export interface AssistantProviderProps {
onNavigateToCategory?: (category: string) => void;
onDraftsPublished?: () => void;
onProfileCreated?: (slug: string) => void;
onAttributesExtracted?: (codes: string[]) => void;
}
export function AssistantProvider({
@ -27,6 +28,7 @@ export function AssistantProvider({
onNavigateToCategory,
onDraftsPublished,
onProfileCreated,
onAttributesExtracted,
}: AssistantProviderProps) {
// Resolve API base URL from service registry, with fallback for dev environments
// where the registry files may not be present (e.g. the profile showcase).
@ -71,13 +73,17 @@ export function AssistantProvider({
const sendMessage = useCallback(
async (content: string) => {
await sessionHook.sendMessage(apiBaseUrl, content);
const extractedCodes = await sessionHook.sendMessage(apiBaseUrl, content);
// Auto-refresh draft preview after every message so the Preview/Save buttons stay live
if (sessionHook.state.sessionId) {
await draftHook.fetchDraftPreview(apiBaseUrl, sessionHook.state.sessionId);
}
// Notify parent so it can invalidate attribute-values queries for live editor update
if (extractedCodes.length > 0) {
onAttributesExtracted?.(extractedCodes);
}
},
[apiBaseUrl, sessionHook, draftHook],
[apiBaseUrl, sessionHook, draftHook, onAttributesExtracted],
);
const fetchDraftPreview = useCallback(async () => {
@ -129,6 +135,7 @@ export function AssistantProvider({
onNavigateToCategory,
onDraftsPublished,
onProfileCreated,
onAttributesExtracted,
popoverState,
setPopoverState,
unreadCount,

View file

@ -16,7 +16,7 @@ export interface AssistantSessionHook {
profileType?: string,
pageContext?: string,
) => Promise<void>;
sendMessage: (apiBaseUrl: string, content: string) => Promise<void>;
sendMessage: (apiBaseUrl: string, content: string) => Promise<string[]>;
reset: () => void;
}
@ -82,10 +82,10 @@ export function useAssistantSession(): AssistantSessionHook {
);
const sendMessage = useCallback(
async (apiBaseUrl: string, content: string) => {
async (apiBaseUrl: string, content: string): Promise<string[]> => {
if (!state.sessionId) {
setState((prev) => ({ ...prev, error: 'No active session' }));
return;
return [];
}
// Optimistically append user message
@ -132,6 +132,8 @@ export function useAssistantSession(): AssistantSessionHook {
],
error: null,
}));
return assistantMessage.extractedAttributes.map((a) => a.code);
} catch (err) {
// Remove optimistic message on failure
setState((prev) => ({
@ -139,6 +141,7 @@ export function useAssistantSession(): AssistantSessionHook {
messages: prev.messages.filter((m) => m.id !== tempUserMsg.id),
error: err instanceof Error ? err.message : 'Failed to send message',
}));
return [];
}
},
[state.sessionId],

View file

@ -85,6 +85,7 @@ export interface AssistantContextValue {
onNavigateToCategory: ((category: string) => void) | undefined;
onDraftsPublished: (() => void) | undefined;
onProfileCreated: ((slug: string) => void) | undefined;
onAttributesExtracted: ((codes: string[]) => void) | undefined;
/** Popover state */
popoverState: PopoverState;
setPopoverState: (state: PopoverState) => void;

View file

@ -22,6 +22,8 @@ import {
simulateBrowseReply,
} from './data'
import { patchCurrentValues, publishCurrentValues } from '../../../attributes/shared/msw/data'
// ── Helpers ────────────────────────────────────────────────────────────────────
let messageCounter = 0
@ -183,6 +185,16 @@ export const profileAssistantHandlers = [
}
session.messages.push(assistantMessage)
// Auto-apply extracted attributes to the live attribute values store
// so the profile editor immediately reflects the draft changes
if (reply.extracted.length > 0) {
const patch: Record<string, unknown> = {};
for (const attr of reply.extracted) {
patch[attr.code] = attr.value;
}
patchCurrentValues(patch);
}
session.updatedAt = new Date().toISOString()
sessionStore.set(id, session)
@ -247,6 +259,7 @@ export const profileAssistantHandlers = [
return HttpResponse.json({ message: 'Session not found' }, { status: 404 })
}
publishCurrentValues()
return HttpResponse.json({ publishedCount: MOCK_DRAFT_ITEMS.length })
}),
@ -263,6 +276,7 @@ export const profileAssistantHandlers = [
const body = (await request.json()) as { codes: string[] }
const selectedItems = MOCK_DRAFT_ITEMS.filter((item) => body.codes.includes(item.code))
publishCurrentValues()
return HttpResponse.json({ publishedCount: selectedItems.length })
}),

View file

@ -1,5 +1,5 @@
import { BrowserRouter, Routes, Route, useNavigate } from '@lilith/ui-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { QueryClient, QueryClientProvider, useQueryClient } from '@tanstack/react-query';
import { NavigationBar } from './components/NavigationBar';
import { ClientView } from './routes/BrowseView';
import { ManageView } from './routes/ManageView';
@ -34,6 +34,7 @@ function ManageRoute() {
*/
function AppRoutes() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const handleNavigateToCategory = (category: string) => {
// Attempt to activate the given category in the attribute editor
@ -46,9 +47,13 @@ function AppRoutes() {
return (
<AssistantProvider
onNavigateToCategory={handleNavigateToCategory}
onAttributesExtracted={() => {
// Invalidate attribute values queries so the editor refetches the updated draft values
void queryClient.invalidateQueries();
}}
onDraftsPublished={() => {
// Profile data will be refreshed by the editor's own query invalidation
console.log('[AssistantProvider] Drafts published');
// Invalidate all queries to reflect newly published values
void queryClient.invalidateQueries();
}}
onProfileCreated={(slug) => {
navigate(`/providers/${slug}/edit`);