@manta-eu/react 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +101 -35
  2. package/dist/components/channel-icon/index.d.ts +1 -2
  3. package/dist/components/channel-icon/index.d.ts.map +1 -1
  4. package/dist/components/channel-icon/index.js +0 -4
  5. package/dist/components/plan-row/index.d.ts +2 -1
  6. package/dist/components/plan-row/index.d.ts.map +1 -1
  7. package/dist/components/plan-row/index.js +2 -14
  8. package/dist/globals.css +146 -90
  9. package/dist/graph/nodes/AdMockupCard.js +1 -1
  10. package/dist/graph/nodes/CreativeMediaCard.js +1 -1
  11. package/dist/graph/nodes/EntityCardShell.js +1 -1
  12. package/dist/hooks/use-conversation-starters.d.ts +1 -1
  13. package/dist/hooks/useValidateActionPlan.d.ts.map +1 -1
  14. package/dist/routes/memories/components/ChangeScopeDialog.d.ts +12 -0
  15. package/dist/routes/memories/components/ChangeScopeDialog.d.ts.map +1 -0
  16. package/dist/routes/memories/components/ChangeScopeDialog.js +89 -0
  17. package/dist/routes/memories/components/ConflictMemoryCard.d.ts +13 -0
  18. package/dist/routes/memories/components/ConflictMemoryCard.d.ts.map +1 -0
  19. package/dist/routes/memories/components/ConflictMemoryCard.js +43 -0
  20. package/dist/routes/memories/components/ConflictTimeoutFooter.d.ts +7 -0
  21. package/dist/routes/memories/components/ConflictTimeoutFooter.d.ts.map +1 -0
  22. package/dist/routes/memories/components/ConflictTimeoutFooter.js +6 -0
  23. package/dist/routes/memories/components/ConsolidationReviewDialog.d.ts +61 -0
  24. package/dist/routes/memories/components/ConsolidationReviewDialog.d.ts.map +1 -0
  25. package/dist/routes/memories/components/ConsolidationReviewDialog.js +121 -0
  26. package/dist/routes/memories/components/EmptyStates.d.ts +18 -0
  27. package/dist/routes/memories/components/EmptyStates.d.ts.map +1 -0
  28. package/dist/routes/memories/components/EmptyStates.js +22 -0
  29. package/dist/routes/memories/components/ImportanceSlider.d.ts +11 -0
  30. package/dist/routes/memories/components/ImportanceSlider.d.ts.map +1 -0
  31. package/dist/routes/memories/components/ImportanceSlider.js +31 -0
  32. package/dist/routes/memories/components/LiveContextTab.d.ts +6 -0
  33. package/dist/routes/memories/components/LiveContextTab.d.ts.map +1 -0
  34. package/dist/routes/memories/components/LiveContextTab.js +37 -0
  35. package/dist/routes/memories/components/LoadingSkeleton.d.ts +2 -0
  36. package/dist/routes/memories/components/LoadingSkeleton.d.ts.map +1 -0
  37. package/dist/routes/memories/components/LoadingSkeleton.js +7 -0
  38. package/dist/routes/memories/components/MemoriesFilters.d.ts +31 -0
  39. package/dist/routes/memories/components/MemoriesFilters.d.ts.map +1 -0
  40. package/dist/routes/memories/components/MemoriesFilters.js +88 -0
  41. package/dist/routes/memories/components/MemoriesPanel.d.ts +6 -0
  42. package/dist/routes/memories/components/MemoriesPanel.d.ts.map +1 -0
  43. package/dist/routes/memories/components/MemoriesPanel.js +99 -0
  44. package/dist/routes/memories/components/MemoriesTabPanel.d.ts +30 -0
  45. package/dist/routes/memories/components/MemoriesTabPanel.d.ts.map +1 -0
  46. package/dist/routes/memories/components/MemoriesTabPanel.js +11 -0
  47. package/dist/routes/memories/components/MemoryActions.d.ts +12 -0
  48. package/dist/routes/memories/components/MemoryActions.d.ts.map +1 -0
  49. package/dist/routes/memories/components/MemoryActions.js +24 -0
  50. package/dist/routes/memories/components/MemoryFields.d.ts +32 -0
  51. package/dist/routes/memories/components/MemoryFields.d.ts.map +1 -0
  52. package/dist/routes/memories/components/MemoryFields.js +30 -0
  53. package/dist/routes/memories/components/MemoryFormDialog.d.ts +15 -0
  54. package/dist/routes/memories/components/MemoryFormDialog.d.ts.map +1 -0
  55. package/dist/routes/memories/components/MemoryFormDialog.js +299 -0
  56. package/dist/routes/memories/components/MemoryOnboardingWizard.d.ts +9 -0
  57. package/dist/routes/memories/components/MemoryOnboardingWizard.d.ts.map +1 -0
  58. package/dist/routes/memories/components/MemoryOnboardingWizard.js +99 -0
  59. package/dist/routes/memories/components/RestoreBlockedDialog.d.ts +8 -0
  60. package/dist/routes/memories/components/RestoreBlockedDialog.d.ts.map +1 -0
  61. package/dist/routes/memories/components/RestoreBlockedDialog.js +7 -0
  62. package/dist/routes/memories/components/SchemasTab.d.ts +6 -0
  63. package/dist/routes/memories/components/SchemasTab.d.ts.map +1 -0
  64. package/dist/routes/memories/components/SchemasTab.js +35 -0
  65. package/dist/routes/memories/components/SupersessionConflictDialog.d.ts +18 -0
  66. package/dist/routes/memories/components/SupersessionConflictDialog.d.ts.map +1 -0
  67. package/dist/routes/memories/components/SupersessionConflictDialog.js +78 -0
  68. package/dist/routes/memories/components/TeamPicker.d.ts +8 -0
  69. package/dist/routes/memories/components/TeamPicker.d.ts.map +1 -0
  70. package/dist/routes/memories/components/TeamPicker.js +25 -0
  71. package/dist/routes/memories/components/badges.d.ts +50 -0
  72. package/dist/routes/memories/components/badges.d.ts.map +1 -0
  73. package/dist/routes/memories/components/badges.js +106 -0
  74. package/dist/routes/memories/expiry-utils.d.ts +23 -0
  75. package/dist/routes/memories/expiry-utils.d.ts.map +1 -0
  76. package/dist/routes/memories/expiry-utils.js +97 -0
  77. package/dist/routes/memories/hooks/useConflictCheck.d.ts +18 -0
  78. package/dist/routes/memories/hooks/useConflictCheck.d.ts.map +1 -0
  79. package/dist/routes/memories/hooks/useConflictCheck.js +35 -0
  80. package/dist/routes/memories/hooks/useDuplicateDetection.d.ts +11 -0
  81. package/dist/routes/memories/hooks/useDuplicateDetection.d.ts.map +1 -0
  82. package/dist/routes/memories/hooks/useDuplicateDetection.js +73 -0
  83. package/dist/routes/memories/hooks/useLiveContext.d.ts +23 -0
  84. package/dist/routes/memories/hooks/useLiveContext.d.ts.map +1 -0
  85. package/dist/routes/memories/hooks/useLiveContext.js +102 -0
  86. package/dist/routes/memories/hooks/useMemoryColumns.d.ts +33 -0
  87. package/dist/routes/memories/hooks/useMemoryColumns.d.ts.map +1 -0
  88. package/dist/routes/memories/hooks/useMemoryColumns.js +188 -0
  89. package/dist/routes/memories/hooks/useMemoryTable.d.ts +33 -0
  90. package/dist/routes/memories/hooks/useMemoryTable.d.ts.map +1 -0
  91. package/dist/routes/memories/hooks/useMemoryTable.js +121 -0
  92. package/dist/routes/memories/index.d.ts +11 -0
  93. package/dist/routes/memories/index.d.ts.map +1 -0
  94. package/dist/routes/memories/index.js +472 -0
  95. package/dist/routes/memories/memory-form-validation.d.ts +8 -0
  96. package/dist/routes/memories/memory-form-validation.d.ts.map +1 -0
  97. package/dist/routes/memories/memory-form-validation.js +33 -0
  98. package/dist/routes/memories/memory-mutation-input.d.ts +6 -0
  99. package/dist/routes/memories/memory-mutation-input.d.ts.map +1 -0
  100. package/dist/routes/memories/memory-mutation-input.js +41 -0
  101. package/dist/routes/plans/components/actionPlansTableConfig.d.ts +3 -3
  102. package/package.json +12 -7
  103. package/src/globals.css +13 -18
  104. package/src/themes/manta-dark.css +0 -4
  105. package/src/themes/standard.css +11 -11
  106. package/dist/components/channel-icon/marketer-icon.d.ts +0 -4
  107. package/dist/components/channel-icon/marketer-icon.d.ts.map +0 -1
  108. package/dist/components/channel-icon/marketer-icon.js +0 -6
  109. package/src/themes/marketer-dark.css +0 -70
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { GQLMemoryScope, useCreateMemoryMutation, useDeleteMemoryMutation, useMemoriesQuery, useUpdateMemoryMutation, } from "@manta-eu/client";
3
+ import { parseError } from "@manta-eu/client/shared/error-utils";
4
+ import { userManualMemorySource } from "@manta-eu/client/shared/memory-sources";
5
+ import { isPresent } from "@manta-eu/lib/present";
6
+ import { useQueryClient } from "@tanstack/react-query";
7
+ import { Brain, Building2, EllipsisIcon, Pencil, Plus, Trash2, X } from "lucide-react";
8
+ import { useCallback, useMemo, useState } from "react";
9
+ import { toast } from "sonner";
10
+ import { liveContextQueryKey } from "../hooks/useLiveContext.js";
11
+ import { buildCreateMemoryInput, buildUpdateMemoryInput } from "../memory-mutation-input.js";
12
+ import { KindBadge, ScopeBadge } from "./badges.js";
13
+ import { MemoryFormDialog } from "./MemoryFormDialog.js";
14
+ import { Banner } from "#components/banner/index";
15
+ import { Button } from "#components/button/index";
16
+ import { Dropdown, DropdownContent, DropdownGroup, DropdownItem, DropdownTrigger } from "#components/dropdown/index";
17
+ import { Skeleton } from "#components/skeleton/index";
18
+ import { useMantaClient } from "#providers/index";
19
+ export function MemoriesPanel(props) {
20
+ const { isOperator, onClose } = props;
21
+ const { mantaClient } = useMantaClient();
22
+ const queryClient = useQueryClient();
23
+ const { data, isLoading, error: queryError } = useMemoriesQuery(mantaClient);
24
+ const allMemories = data?.memories.memories ?? [];
25
+ const memories = useMemo(() => allMemories.filter((m) => m.memorySource === userManualMemorySource), [allMemories]);
26
+ const errorMessage = queryError ? parseError(queryError, "Failed to load memories").message : null;
27
+ const createMutation = useCreateMemoryMutation(mantaClient);
28
+ const updateMutation = useUpdateMemoryMutation(mantaClient);
29
+ const deleteMutation = useDeleteMemoryMutation(mantaClient);
30
+ const [addDialog, setAddDialog] = useState(null);
31
+ const [editingMemory, setEditingMemory] = useState(null);
32
+ const invalidate = useCallback(() => {
33
+ void queryClient.invalidateQueries({ queryKey: useMemoriesQuery.getKey() });
34
+ void queryClient.invalidateQueries({ queryKey: liveContextQueryKey });
35
+ }, [queryClient]);
36
+ const handleCreate = useCallback(async (form, supersedesMemoryIris) => {
37
+ if (!addDialog)
38
+ return;
39
+ if (!isOperator && addDialog.scope !== GQLMemoryScope.User)
40
+ return;
41
+ try {
42
+ const result = await createMutation.mutateAsync({
43
+ input: buildCreateMemoryInput(form, addDialog.scope, supersedesMemoryIris),
44
+ });
45
+ if (result.createMemory.error)
46
+ throw new Error(result.createMemory.error);
47
+ toast.success("Memory added");
48
+ setAddDialog(null);
49
+ invalidate();
50
+ }
51
+ catch (e) {
52
+ const err = parseError(e, "Failed to add memory");
53
+ toast.error(err.message, { description: err.description });
54
+ }
55
+ }, [addDialog, createMutation, invalidate, isOperator]);
56
+ const handleUpdate = useCallback(async (form, supersedesMemoryIris) => {
57
+ if (!editingMemory)
58
+ return;
59
+ if (!isOperator && editingMemory.scope !== GQLMemoryScope.User)
60
+ return;
61
+ try {
62
+ const result = await updateMutation.mutateAsync({
63
+ input: buildUpdateMemoryInput(editingMemory.iri, form, editingMemory.scope, supersedesMemoryIris),
64
+ });
65
+ if (result.updateMemory.error)
66
+ throw new Error(result.updateMemory.error);
67
+ toast.success("Memory updated");
68
+ setEditingMemory(null);
69
+ invalidate();
70
+ }
71
+ catch (e) {
72
+ const err = parseError(e, "Failed to update memory");
73
+ toast.error(err.message, { description: err.description });
74
+ }
75
+ }, [editingMemory, updateMutation, invalidate, isOperator]);
76
+ const handleDelete = useCallback(async (iri) => {
77
+ try {
78
+ await deleteMutation.mutateAsync({ memoryIri: iri });
79
+ toast.success("Memory deleted");
80
+ invalidate();
81
+ }
82
+ catch (e) {
83
+ const err = parseError(e, "Failed to delete memory");
84
+ toast.error(err.message, { description: err.description });
85
+ }
86
+ }, [deleteMutation, invalidate]);
87
+ return (_jsxs("div", { className: "flex flex-col h-full", children: [_jsxs("div", { className: "shrink-0 flex items-center justify-between gap-2 px-4 py-3 border-b border-muted-border", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Brain, { className: "size-4 text-muted-foreground" }), _jsx("span", { className: "typography-small-medium text-default-foreground", children: "Memories" })] }), _jsxs("div", { className: "flex items-center gap-1", children: [isOperator && (_jsx(Button, { variant: "ghost", size: "sm", icon: Building2, onClick: () => setAddDialog({ scope: GQLMemoryScope.Company }), children: "Add org" })), _jsx(Button, { size: "sm", icon: Plus, onClick: () => setAddDialog({ scope: GQLMemoryScope.User }), children: "Add" }), _jsx(Button, { variant: "ghost", size: "sm", iconOnly: true, icon: X, "aria-label": "Close memories panel", onClick: onClose })] })] }), _jsxs("div", { className: "flex-1 overflow-auto", children: [isLoading && (_jsx("ul", { className: "divide-y divide-muted-border", children: Array.from({ length: 4 }, (_, i) => (_jsxs("li", { className: "flex items-start gap-3 px-4 py-3", children: [_jsxs("div", { className: "flex-1 min-w-0 flex flex-col gap-1.5", children: [_jsx(Skeleton, { className: "h-4 w-2/3 rounded-md" }), _jsx(Skeleton, { className: "h-3.5 w-full rounded-md" })] }), _jsxs("div", { className: "shrink-0 flex items-center gap-2 pt-0.5", children: [_jsx(Skeleton, { className: "h-5 w-14 rounded-full" }), _jsx(Skeleton, { className: "size-7 rounded-md" })] })] }, `panel-skeleton-${i}`))) })), errorMessage && _jsx(Banner, { label: errorMessage, className: "m-4" }), !isLoading && !errorMessage && !isPresent(memories) && (_jsxs("div", { className: "flex flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx(Brain, { className: "size-8 text-muted-foreground" }), _jsx("p", { className: "typography-small text-muted-foreground", children: "No memories yet. Add something Manta should always keep in mind." })] })), !isLoading && isPresent(memories) && (_jsx("ul", { className: "divide-y divide-muted-border", children: memories.map((memory) => (_jsx(MemoryListItem, { memory: memory, isOperator: isOperator, onEdit: () => setEditingMemory(memory), onDelete: () => void handleDelete(memory.iri) }, memory.iri))) }))] }), addDialog && (_jsx(MemoryFormDialog, { open: true, onOpenChange: (open) => {
88
+ if (!open)
89
+ setAddDialog(null);
90
+ }, scope: addDialog.scope, onSave: handleCreate, isSaving: createMutation.isPending })), editingMemory && (_jsx(MemoryFormDialog, { open: true, onOpenChange: (open) => {
91
+ if (!open)
92
+ setEditingMemory(null);
93
+ }, scope: editingMemory.scope, initial: editingMemory, onSave: handleUpdate, isSaving: updateMutation.isPending }))] }));
94
+ }
95
+ function MemoryListItem(props) {
96
+ const { memory, isOperator, onEdit, onDelete } = props;
97
+ const canEdit = memory.scope !== GQLMemoryScope.Company || isOperator;
98
+ return (_jsxs("li", { className: "flex items-start gap-3 px-4 py-3", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "typography-caption-medium text-default-foreground truncate", children: memory.label }), _jsx("p", { className: "typography-small text-muted-foreground line-clamp-2 mt-0.5", children: memory.description })] }), _jsxs("div", { className: "shrink-0 flex items-center gap-2 pt-0.5", children: [isOperator && _jsx(ScopeBadge, { scope: memory.scope }), _jsx(KindBadge, { kind: memory.kind }), canEdit && (_jsxs(Dropdown, { children: [_jsx(DropdownTrigger, { asChild: true, children: _jsx(Button, { variant: "tertiary", size: "sm", iconOnly: true, icon: EllipsisIcon, "aria-label": "Memory actions" }) }), _jsx(DropdownContent, { align: "end", className: "z-60", children: _jsxs(DropdownGroup, { children: [_jsxs(DropdownItem, { onSelect: onEdit, children: [_jsx(Pencil, { size: 16 }), "Edit"] }), _jsxs(DropdownItem, { onSelect: onDelete, children: [_jsx(Trash2, { size: 16, className: "text-error-foreground" }), "Delete"] })] }) })] }))] })] }));
99
+ }
@@ -0,0 +1,30 @@
1
+ import type { GQLMemory, GQLMemorySchema } from "@manta-eu/client";
2
+ import type { Table } from "@tanstack/react-table";
3
+ import type { ReactNode } from "react";
4
+ import type { ImportanceFilter, StatusFilterKey } from "../hooks/useMemoryTable.js";
5
+ import type { KindFilter, SchemaFilter } from "./MemoriesFilters.js";
6
+ export type MemoriesTabPanelProps = {
7
+ tabs?: ReactNode;
8
+ isLoading: boolean;
9
+ errorMessage: string | null;
10
+ table: Table<GQLMemory>;
11
+ kindFilter: KindFilter;
12
+ onKindFilterChange: (kind: KindFilter) => void;
13
+ searchQuery: string;
14
+ onSearchQueryChange: (query: string) => void;
15
+ importanceFilter: ImportanceFilter;
16
+ onImportanceFilterChange: (importance: ImportanceFilter) => void;
17
+ memories: GQLMemory[];
18
+ emptyState?: ReactNode;
19
+ columnLabels?: Record<string, string>;
20
+ columnDefaultOrder?: string[];
21
+ schemas?: GQLMemorySchema[];
22
+ schemaFilter?: SchemaFilter;
23
+ onSchemaFilterChange?: (iri: SchemaFilter) => void;
24
+ onRowClick?: (memory: GQLMemory) => void;
25
+ statusFilters?: Record<StatusFilterKey, boolean>;
26
+ onStatusFilterChange?: (key: StatusFilterKey, value: boolean) => void;
27
+ visibleStatusFilters?: StatusFilterKey[];
28
+ };
29
+ export declare function MemoriesTabPanel(props: MemoriesTabPanelProps): import("react/jsx-runtime").JSX.Element;
30
+ //# sourceMappingURL=MemoriesTabPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemoriesTabPanel.d.ts","sourceRoot":"","sources":["../../../../src/routes/memories/components/MemoriesTabPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAO,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAEjF,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAMlE,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACxB,UAAU,EAAE,UAAU,CAAC;IACvB,kBAAkB,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,wBAAwB,EAAE,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACjE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IACjD,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACtE,oBAAoB,CAAC,EAAE,eAAe,EAAE,CAAC;CAC1C,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,2CAyE5D"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { isPresent } from "@manta-eu/lib/present";
3
+ import { LoadingSkeleton } from "./LoadingSkeleton.js";
4
+ import { MemoriesFilters } from "./MemoriesFilters.js";
5
+ import { Banner } from "#components/banner/index";
6
+ import { DataTable } from "#components/table/index";
7
+ import { TablePagination } from "#components/table-pagination/index";
8
+ export function MemoriesTabPanel(props) {
9
+ const { tabs, isLoading, errorMessage, table, kindFilter, onKindFilterChange, searchQuery, onSearchQueryChange, importanceFilter, onImportanceFilterChange, memories, emptyState, columnLabels, columnDefaultOrder, schemas, schemaFilter, onSchemaFilterChange, onRowClick, statusFilters, onStatusFilterChange, visibleStatusFilters, } = props;
10
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "border-b border-muted-border px-5 py-3 shrink-0", children: _jsx(MemoriesFilters, { tabs: tabs, kindFilter: kindFilter, onKindFilterChange: onKindFilterChange, searchQuery: searchQuery, onSearchQueryChange: onSearchQueryChange, importanceFilter: importanceFilter, onImportanceFilterChange: onImportanceFilterChange, table: table, columnLabels: columnLabels, columnDefaultOrder: columnDefaultOrder, schemas: schemas, schemaFilter: schemaFilter, onSchemaFilterChange: onSchemaFilterChange, statusFilters: statusFilters, onStatusFilterChange: onStatusFilterChange, visibleStatusFilters: visibleStatusFilters }) }), _jsxs("div", { className: "flex-1 overflow-auto p-4", children: [isLoading && _jsx(LoadingSkeleton, {}), errorMessage && _jsx(Banner, { label: errorMessage }), !isLoading && !isPresent(memories) && emptyState, !isLoading && isPresent(memories) && (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("div", { className: "rounded-xl border border-muted-border bg-card overflow-hidden", children: onRowClick ? (_jsx(DataTable, { table: table, onRowClick: (row) => onRowClick(row.original), rowClassName: (row) => (isPresent(row.original.supersededBy) ? "opacity-50" : undefined) })) : (_jsx(DataTable, { table: table, rowClassName: (row) => (isPresent(row.original.supersededBy) ? "opacity-50" : undefined) })) }), _jsx(TablePagination, { table: table, total: memories.length })] }))] })] }));
11
+ }
@@ -0,0 +1,12 @@
1
+ import { type GQLMemory } from "@manta-eu/client";
2
+ export type MemoryActionsProps = {
3
+ memory: GQLMemory;
4
+ isOperator?: boolean;
5
+ onEdit: (memory: GQLMemory) => void;
6
+ onDelete: (iri: string) => void;
7
+ onMove?: (memory: GQLMemory) => void;
8
+ onRestore?: (iri: string) => void;
9
+ onReconfirm?: (iri: string) => void;
10
+ };
11
+ export declare function MemoryActions(props: MemoryActionsProps): import("react/jsx-runtime").JSX.Element | null;
12
+ //# sourceMappingURL=MemoryActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemoryActions.d.ts","sourceRoot":"","sources":["../../../../src/routes/memories/components/MemoryActions.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAkBlE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,SAAS,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;IACpC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,kDAsEtD"}
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { GQLMemoryScope } from "@manta-eu/client";
3
+ import { needsTrustReview } from "@manta-eu/client/shared/memory-preload-gate";
4
+ import { isPresent } from "@manta-eu/lib/present";
5
+ import { BadgeCheck, EllipsisIcon, MoveRight, Pencil, Trash2, Undo2 } from "lucide-react";
6
+ import { useState } from "react";
7
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "#components/alert-dialog/index";
8
+ import { Button } from "#components/button/index";
9
+ import { Dropdown, DropdownContent, DropdownGroup, DropdownItem, DropdownTrigger } from "#components/dropdown/index";
10
+ export function MemoryActions(props) {
11
+ const { memory, isOperator, onEdit, onDelete, onMove, onRestore, onReconfirm } = props;
12
+ const [confirmOpen, setConfirmOpen] = useState(false);
13
+ const isRealm = memory.scope === GQLMemoryScope.Company;
14
+ if (isPresent(memory.supersededBy)) {
15
+ const canRestore = isOperator || memory.scope === GQLMemoryScope.User;
16
+ if (!canRestore)
17
+ return null;
18
+ return (_jsx("div", { className: "text-right", onClick: (e) => e.stopPropagation(), children: _jsxs(Button, { variant: "tertiary", size: "sm", rounded: "lg", onClick: () => onRestore?.(memory.iri), className: "gap-1.5", children: [_jsx(Undo2, { size: 14 }), "Restore"] }) }));
19
+ }
20
+ if (!isRealm || isOperator) {
21
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "text-right", onClick: (e) => e.stopPropagation(), children: _jsxs(Dropdown, { children: [_jsx(DropdownTrigger, { asChild: true, children: _jsx(Button, { variant: "tertiary", iconOnly: true, icon: EllipsisIcon, "aria-label": "Actions" }) }), _jsx(DropdownContent, { align: "end", className: "z-60 w-44", children: _jsxs(DropdownGroup, { children: [_jsxs(DropdownItem, { onSelect: () => onEdit(memory), children: [_jsx(Pencil, { size: 16 }), "Edit"] }), onReconfirm && needsTrustReview(memory) && (_jsxs(DropdownItem, { onSelect: () => onReconfirm(memory.iri), children: [_jsx(BadgeCheck, { size: 16 }), "Reconfirm"] })), onMove && isOperator && (_jsxs(DropdownItem, { onSelect: () => onMove(memory), children: [_jsx(MoveRight, { size: 16 }), "Move to\u2026"] })), _jsxs(DropdownItem, { onSelect: () => setConfirmOpen(true), children: [_jsx(Trash2, { size: 16, className: "text-error-foreground" }), "Delete"] })] }) })] }) }), _jsx(AlertDialog, { open: confirmOpen, onOpenChange: setConfirmOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Delete memory?" }), _jsxs(AlertDialogDescription, { children: ["\"", memory.label, "\" will be permanently deleted."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Cancel" }), _jsx(AlertDialogAction, { variant: "destructive", onClick: () => onDelete(memory.iri), children: "Delete" })] })] }) })] }));
22
+ }
23
+ return null;
24
+ }
@@ -0,0 +1,32 @@
1
+ import { GQLMemoryKind, type GQLMemorySchema } from "@manta-eu/client";
2
+ import type { ReactNode } from "react";
3
+ export type FormErrors = Partial<Record<"label" | "description" | "teams" | "memoryTrustedUntil", string>>;
4
+ export type MemoryFormState = {
5
+ label: string;
6
+ description: string | null;
7
+ kind: GQLMemoryKind;
8
+ memorySubclass: string | null;
9
+ customProperties: Record<string, string>;
10
+ memoryValidUntil: string | null;
11
+ memoryTrustedUntil: string | null;
12
+ teams: string[];
13
+ importance: number;
14
+ };
15
+ export declare const emptyForm: MemoryFormState;
16
+ export type MemoryFieldsProps = {
17
+ form: MemoryFormState;
18
+ errors: FormErrors;
19
+ isTeam: boolean;
20
+ isOperator?: boolean;
21
+ schemas?: GQLMemorySchema[];
22
+ onChange: (next: Partial<MemoryFormState>) => void;
23
+ descriptionPlaceholder?: string;
24
+ labelPlaceholder?: string;
25
+ autoFocus?: boolean;
26
+ onEditProperties?: () => void;
27
+ filledPropertyCount?: number;
28
+ totalPropertyCount?: number;
29
+ children?: ReactNode;
30
+ };
31
+ export declare function MemoryFields(props: MemoryFieldsProps): import("react/jsx-runtime").JSX.Element;
32
+ //# sourceMappingURL=MemoryFields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemoryFields.d.ts","sourceRoot":"","sources":["../../../../src/routes/memories/components/MemoryFields.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAIvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAwBvC,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,GAAG,aAAa,GAAG,OAAO,GAAG,oBAAoB,EAAE,MAAM,CAAC,CAAC,CAAC;AAE3G,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,aAAa,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,eAUvB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC;IACnD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,2CAyHpD"}
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { GQLMemoryKind } from "@manta-eu/client";
3
+ import { formatEntityName } from "@manta-eu/client/shared/rdf-namespaces";
4
+ import { isPresent } from "@manta-eu/lib/present";
5
+ import { ChevronDown, ChevronRight } from "lucide-react";
6
+ import { ImportanceSlider } from "./ImportanceSlider.js";
7
+ import { TeamPicker } from "./TeamPicker.js";
8
+ import { Button } from "#components/button/index";
9
+ import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, ComboboxTrigger, } from "#components/combobox/index";
10
+ import { Input, Textarea } from "#components/input/index";
11
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, createSelectClassName, } from "#components/select/index";
12
+ import { cn } from "#lib/utils";
13
+ export const emptyForm = {
14
+ label: "",
15
+ description: "",
16
+ kind: GQLMemoryKind.Fact,
17
+ memorySubclass: null,
18
+ customProperties: {},
19
+ memoryValidUntil: null,
20
+ memoryTrustedUntil: null,
21
+ teams: [],
22
+ importance: 3,
23
+ };
24
+ export function MemoryFields(props) {
25
+ const { form, errors, isTeam, isOperator = false, schemas, onChange, descriptionPlaceholder, labelPlaceholder, autoFocus = false, onEditProperties, filledPropertyCount = 0, totalPropertyCount = 0, children, } = props;
26
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("label", { className: "typography-small", children: "Importance" }), _jsx(ImportanceSlider, { value: form.importance, onChange: (v) => onChange({ importance: v }) })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("label", { className: "typography-small", children: "Kind" }), _jsxs(Select, { value: form.kind, onValueChange: (v) => onChange({ kind: v }), children: [_jsx(SelectTrigger, { label: "Kind", id: "memory-kind", hiddenLabel: true, children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: GQLMemoryKind.Preference, children: "Preference" }), _jsx(SelectItem, { value: GQLMemoryKind.Fact, children: "Fact" }), _jsx(SelectItem, { value: GQLMemoryKind.Objective, children: "Objective" }), _jsx(SelectItem, { value: GQLMemoryKind.Context, children: "Context" })] })] })] }), isPresent(schemas) && (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("label", { className: "typography-small", children: "Schema" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Combobox, { children: [_jsx(ComboboxTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: cn(createSelectClassName({ size: "base" }), "flex-1 min-w-0"), children: [_jsx("span", { className: cn("flex-1 min-w-0 truncate text-left", !form.memorySubclass && "text-placeholder-foreground"), children: form.memorySubclass
27
+ ? (schemas.find((s) => s.iri === form.memorySubclass)?.label ??
28
+ formatEntityName(form.memorySubclass))
29
+ : "Default" }), _jsx(ChevronDown, { className: "size-4 shrink-0 text-muted-foreground" })] }) }), _jsxs(ComboboxContent, { align: "start", className: "w-[var(--radix-popover-trigger-width,320px)]", children: [_jsx(ComboboxInput, { placeholder: "Search schemas\u2026" }), _jsxs(ComboboxList, { children: [_jsx(ComboboxEmpty, { children: "No schemas found" }), _jsx(ComboboxItem, { value: "none", selected: form.memorySubclass === null, onSelect: () => onChange({ memorySubclass: null, customProperties: {} }), children: "Default" }), schemas.map((schema) => (_jsx(ComboboxItem, { value: schema.label, selected: form.memorySubclass === schema.iri, onSelect: () => onChange({ memorySubclass: schema.iri, customProperties: {} }), children: schema.label }, schema.iri)))] })] })] }), onEditProperties && totalPropertyCount > 0 && (_jsxs(Button, { type: "button", variant: "tertiary", size: "base", onClick: onEditProperties, className: "shrink-0", children: [filledPropertyCount > 0 ? `${filledPropertyCount}/${totalPropertyCount} filled` : "Edit properties", _jsx(ChevronRight, { className: "size-3.5" })] }))] })] })), _jsx(Input, { id: "memory-label", label: "Label", placeholder: labelPlaceholder, value: form.label, onChange: (e) => onChange({ label: e.target.value }), error: errors.label }), _jsx(Textarea, { id: "memory-description", label: "Description", placeholder: descriptionPlaceholder, value: form.description ?? "", onChange: (e) => onChange({ description: e.target.value }), minRows: 3, maxRows: 8, autoFocus: autoFocus, error: errors.description }), children, isTeam && (_jsx(TeamPicker, { selected: form.teams, onChange: (teams) => onChange({ teams }), isOperator: isOperator, error: errors.teams }))] }));
30
+ }
@@ -0,0 +1,15 @@
1
+ import { GQLMemoryScope, type GQLMemory, type GQLMemorySchema } from "@manta-eu/client";
2
+ import { type MemoryFormState } from "./MemoryFields.js";
3
+ export type MemoryFormDialogProps = {
4
+ open: boolean;
5
+ onOpenChange: (open: boolean) => void;
6
+ scope: GQLMemoryScope;
7
+ initial?: GQLMemory;
8
+ onSave: (form: MemoryFormState, supersedesMemoryIris?: string[]) => Promise<void>;
9
+ isSaving: boolean;
10
+ isOperator?: boolean;
11
+ schemas?: GQLMemorySchema[];
12
+ currentUserId?: string;
13
+ };
14
+ export declare function MemoryFormDialog(props: MemoryFormDialogProps): import("react/jsx-runtime").JSX.Element;
15
+ //# sourceMappingURL=MemoryFormDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemoryFormDialog.d.ts","sourceRoot":"","sources":["../../../../src/routes/memories/components/MemoryFormDialog.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAA6B,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAUnH,OAAO,EAA4C,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAuBhG,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,cAAc,CAAC;IACtB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,oBAAoB,CAAC,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAsGF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,2CAwe5D"}
@@ -0,0 +1,299 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { GQLMemoryScope } from "@manta-eu/client";
3
+ import { formatEntityName } from "@manta-eu/client/shared/rdf-namespaces";
4
+ import { humanCount } from "@manta-eu/lib/humanize";
5
+ import { isPresent } from "@manta-eu/lib/present";
6
+ import { addYears, endOfDay, format, startOfTomorrow } from "date-fns";
7
+ import { CalendarIcon, ChevronLeft, X } from "lucide-react";
8
+ import { useCallback, useEffect, useRef, useState } from "react";
9
+ import { useConflictCheck } from "../hooks/useConflictCheck.js";
10
+ import { validateMemoryForm } from "../memory-form-validation.js";
11
+ import { ConflictMemoryCard } from "./ConflictMemoryCard.js";
12
+ import { MemoryFields, emptyForm } from "./MemoryFields.js";
13
+ import { Button } from "#components/button/index";
14
+ import { Calendar } from "#components/calendar/index";
15
+ import { Input } from "#components/input/index";
16
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "#components/modal/index";
17
+ import { Popover, PopoverContent, PopoverTrigger } from "#components/popover/index";
18
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, createSelectClassName, } from "#components/select/index";
19
+ import { cn } from "#lib/utils";
20
+ function normalizeDesc(s) {
21
+ return s.trim().replace(/\s+/g, " ");
22
+ }
23
+ function shortDatatype(iri) {
24
+ if (iri.endsWith("#string"))
25
+ return "text";
26
+ if (iri.endsWith("#decimal"))
27
+ return "decimal";
28
+ if (iri.endsWith("#integer"))
29
+ return "integer";
30
+ if (iri.endsWith("#boolean"))
31
+ return "boolean";
32
+ if (iri.endsWith("#anyURI"))
33
+ return "url";
34
+ return formatEntityName(iri);
35
+ }
36
+ function initialSubclass(memory) {
37
+ if (!memory.memorySubclass)
38
+ return null;
39
+ return memory.memorySubclass;
40
+ }
41
+ function buildFormFromMemory(memory) {
42
+ return {
43
+ label: memory.label,
44
+ description: memory.description ?? null,
45
+ kind: memory.kind,
46
+ memorySubclass: initialSubclass(memory),
47
+ customProperties: memory.customProperties ?? {},
48
+ memoryValidUntil: memory.memoryValidUntil ? new Date(memory.memoryValidUntil).toISOString() : null,
49
+ memoryTrustedUntil: memory.memoryTrustedUntil ? new Date(memory.memoryTrustedUntil).toISOString() : null,
50
+ teams: memory.teams,
51
+ importance: memory.importance,
52
+ };
53
+ }
54
+ function MemoryDateField(props) {
55
+ const { id, label, hint, placeholder, value, onChange, notAfter, error } = props;
56
+ const selected = value ? new Date(value) : undefined;
57
+ const latest = notAfter ? new Date(notAfter) : undefined;
58
+ const errorId = `${id}-error`;
59
+ return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("label", { htmlFor: id, className: "typography-small", children: label }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", id: id, "aria-invalid": error ? true : undefined, "aria-describedby": error ? errorId : undefined, className: cn(createSelectClassName({ size: "base" }), "flex-1 min-w-0 gap-2"), children: [_jsx(CalendarIcon, { className: "size-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: cn("flex-1 min-w-0 truncate text-left", !value && "text-placeholder-foreground"), children: selected ? format(selected, "MMM d, yyyy") : placeholder })] }) }), _jsx(PopoverContent, { align: "start", className: "w-auto border-0 bg-transparent p-0 shadow-none", children: _jsx(Calendar, { mode: "single", captionLayout: "dropdown", startMonth: new Date(), endMonth: latest ?? addYears(new Date(), 10), selected: selected, defaultMonth: selected, disabled: latest ? { before: startOfTomorrow(), after: latest } : { before: startOfTomorrow() }, onSelect: (date) => onChange(date ? endOfDay(date).toISOString() : null) }) })] }), value && (_jsxs(Button, { type: "button", variant: "tertiary", size: "base", onClick: () => onChange(null), className: "shrink-0", children: [_jsx(X, { className: "size-3.5" }), "Clear"] }))] }), _jsx("p", { className: "typography-small text-muted-foreground", children: hint }), error && (_jsx("p", { id: errorId, className: "typography-small text-error-foreground", children: error }))] }));
60
+ }
61
+ export function MemoryFormDialog(props) {
62
+ const { open, onOpenChange, scope, initial, onSave, isSaving, isOperator = false, schemas, currentUserId } = props;
63
+ const isEdit = !!initial;
64
+ const isCompany = scope === GQLMemoryScope.Company;
65
+ const isTeam = scope === GQLMemoryScope.Team;
66
+ const [form, setForm] = useState(() => (initial ? buildFormFromMemory(initial) : emptyForm));
67
+ const [errors, setErrors] = useState({});
68
+ const [submitted, setSubmitted] = useState(false);
69
+ const [view, setView] = useState("main");
70
+ const [showDiscardConfirm, setShowDiscardConfirm] = useState(false);
71
+ const [pendingConflicts, setPendingConflicts] = useState([]);
72
+ const [pendingForm, setPendingForm] = useState(null);
73
+ const [conflictCheckTimedOut, setConflictCheckTimedOut] = useState(false);
74
+ const [conflictChecked, setConflictChecked] = useState(new Set());
75
+ const [conflictMerging, setConflictMerging] = useState(new Set());
76
+ const clearConflicts = useCallback(() => {
77
+ setPendingConflicts([]);
78
+ setPendingForm(null);
79
+ setConflictChecked(new Set());
80
+ setConflictMerging(new Set());
81
+ }, []);
82
+ const originalDescRef = useRef(initial ? normalizeDesc(initial.description ?? "") : "");
83
+ const { check, isChecking } = useConflictCheck();
84
+ useEffect(() => {
85
+ if (submitted) {
86
+ setErrors(validateMemoryForm(form, isEdit, isTeam));
87
+ }
88
+ }, [form, submitted, isEdit, isTeam]);
89
+ useEffect(() => {
90
+ if (open) {
91
+ const nextForm = initial ? buildFormFromMemory(initial) : emptyForm;
92
+ setForm(nextForm);
93
+ setErrors({});
94
+ setSubmitted(false);
95
+ setView("main");
96
+ originalDescRef.current = initial ? normalizeDesc(initial.description ?? "") : "";
97
+ setShowDiscardConfirm(false);
98
+ clearConflicts();
99
+ setConflictCheckTimedOut(false);
100
+ }
101
+ }, [open, initial]);
102
+ const isDirty = isEdit
103
+ ? form.label !== initial.label ||
104
+ form.description !== initial.description ||
105
+ form.memorySubclass !== initialSubclass(initial) ||
106
+ form.memoryValidUntil !== (initial.memoryValidUntil ? new Date(initial.memoryValidUntil).toISOString() : null) ||
107
+ form.memoryTrustedUntil !==
108
+ (initial.memoryTrustedUntil ? new Date(initial.memoryTrustedUntil).toISOString() : null) ||
109
+ JSON.stringify(form.customProperties) !==
110
+ JSON.stringify(initial.customProperties ?? {})
111
+ : isPresent(form.label) || isPresent(form.description) || isPresent(form.teams);
112
+ const handleRequestClose = useCallback((nextOpen) => {
113
+ if (!nextOpen && isDirty && !isSaving) {
114
+ setShowDiscardConfirm(true);
115
+ return;
116
+ }
117
+ onOpenChange(nextOpen);
118
+ }, [isDirty, isSaving, onOpenChange]);
119
+ const handleSave = useCallback(async () => {
120
+ setSubmitted(true);
121
+ const validationErrors = validateMemoryForm(form, isEdit, isTeam);
122
+ if (isPresent(validationErrors)) {
123
+ setErrors(validationErrors);
124
+ return;
125
+ }
126
+ const currentForm = form;
127
+ if (isEdit && normalizeDesc(currentForm.description ?? "") === originalDescRef.current) {
128
+ await onSave(currentForm);
129
+ return;
130
+ }
131
+ const { conflicts, timedOut } = await check({
132
+ label: currentForm.label.trim() || (currentForm.description ?? "").slice(0, 40),
133
+ description: currentForm.description ?? "",
134
+ scope,
135
+ teams: isTeam ? currentForm.teams : undefined,
136
+ excludeIri: isEdit ? initial?.iri : undefined,
137
+ });
138
+ if (timedOut) {
139
+ setConflictCheckTimedOut(true);
140
+ setPendingForm(currentForm);
141
+ return;
142
+ }
143
+ if (!isPresent(conflicts)) {
144
+ await onSave(currentForm);
145
+ return;
146
+ }
147
+ // Pre-check all conflicts; user can uncheck to keep any.
148
+ setPendingConflicts(conflicts);
149
+ setPendingForm(currentForm);
150
+ setConflictChecked(new Set(conflicts.map((c) => c.iri)));
151
+ setConflictMerging(new Set());
152
+ setView("conflicts");
153
+ }, [form, isEdit, initial, scope, isTeam, check, onSave]);
154
+ const toggleConflictChecked = useCallback((iri) => {
155
+ setConflictChecked((prev) => {
156
+ const next = new Set(prev);
157
+ if (next.has(iri)) {
158
+ next.delete(iri);
159
+ // Unchecking stops superseding it, so it must also stop contributing its teams.
160
+ setConflictMerging((m) => {
161
+ if (!m.has(iri))
162
+ return m;
163
+ const nm = new Set(m);
164
+ nm.delete(iri);
165
+ return nm;
166
+ });
167
+ }
168
+ else {
169
+ next.add(iri);
170
+ }
171
+ return next;
172
+ });
173
+ }, []);
174
+ const toggleConflictMerge = useCallback((iri) => {
175
+ setConflictMerging((prev) => {
176
+ const next = new Set(prev);
177
+ if (next.has(iri))
178
+ next.delete(iri);
179
+ else {
180
+ next.add(iri);
181
+ setConflictChecked((c) => new Set([...c, iri]));
182
+ }
183
+ return next;
184
+ });
185
+ }, []);
186
+ const allConflictsChecked = isPresent(pendingConflicts) && pendingConflicts.every((c) => conflictChecked.has(c.iri));
187
+ const toggleAllConflicts = useCallback(() => {
188
+ if (allConflictsChecked) {
189
+ setConflictChecked(new Set());
190
+ setConflictMerging(new Set());
191
+ }
192
+ else {
193
+ setConflictChecked(new Set(pendingConflicts.map((c) => c.iri)));
194
+ }
195
+ }, [allConflictsChecked, pendingConflicts]);
196
+ const handleConflictBack = useCallback(() => {
197
+ clearConflicts();
198
+ setView("main");
199
+ }, [clearConflicts]);
200
+ const handleSaveWithoutSuperseding = useCallback(async () => {
201
+ if (!pendingForm)
202
+ return;
203
+ const saved = pendingForm;
204
+ clearConflicts();
205
+ setView("main");
206
+ await onSave(saved, []);
207
+ }, [pendingForm, clearConflicts, onSave]);
208
+ const handleConflictConfirmInline = useCallback(async () => {
209
+ if (!pendingForm)
210
+ return;
211
+ const supersedesIris = [...conflictChecked];
212
+ let finalForm = pendingForm;
213
+ if (isTeam && conflictMerging.size > 0) {
214
+ const allOldTeams = pendingConflicts.filter((c) => conflictMerging.has(c.iri)).flatMap((c) => c.currentTeams);
215
+ finalForm = { ...pendingForm, teams: [...new Set([...pendingForm.teams, ...allOldTeams])] };
216
+ }
217
+ clearConflicts();
218
+ setView("main");
219
+ await onSave(finalForm, supersedesIris);
220
+ }, [pendingForm, pendingConflicts, conflictChecked, conflictMerging, isTeam, clearConflicts, onSave]);
221
+ const selectedSchema = form.memorySubclass ? (schemas?.find((s) => s.iri === form.memorySubclass) ?? null) : null;
222
+ const schemaProperties = selectedSchema && isPresent(selectedSchema.properties)
223
+ ? [...selectedSchema.properties].sort((a, b) => {
224
+ if (a.required && !b.required)
225
+ return -1;
226
+ if (!a.required && b.required)
227
+ return 1;
228
+ return 0;
229
+ })
230
+ : [];
231
+ const hasProperties = isPresent(schemaProperties);
232
+ const requiredPropertyCount = schemaProperties.filter((p) => p.required).length;
233
+ const filledPropertyCount = schemaProperties.filter((p) => isPresent(form.customProperties[p.path])).length;
234
+ const scopeLabel = isTeam ? "team" : isCompany ? "company" : "user";
235
+ const titleText = isEdit
236
+ ? `Edit ${scopeLabel} memory`
237
+ : isTeam
238
+ ? "Add team memory"
239
+ : isCompany
240
+ ? "Add company memory"
241
+ : "Add memory";
242
+ const hasErrors = isPresent(errors);
243
+ const isBusy = isSaving || isChecking;
244
+ function getSaveLabel() {
245
+ if (isChecking)
246
+ return "Checking for conflicts…";
247
+ if (isSaving)
248
+ return "Saving…";
249
+ if (isEdit)
250
+ return "Save changes";
251
+ return titleText;
252
+ }
253
+ function getDescriptionText() {
254
+ if (isEdit)
255
+ return `Update this ${scopeLabel}-level memory that Manta uses in every chat.`;
256
+ if (isTeam)
257
+ return "Add a fact or preference shared with specific teams.";
258
+ if (isCompany)
259
+ return "Add a fact or preference visible to all users in the company.";
260
+ return "Tell Manta something it should always remember about you or your business.";
261
+ }
262
+ function getDescriptionPlaceholder() {
263
+ if (isTeam)
264
+ return "e.g. Reduce customer acquisition cost by 20% this quarter";
265
+ if (isCompany)
266
+ return "e.g. Our fiscal year ends in March, not December";
267
+ return "e.g. I prefer concise answers with concrete examples over long explanations";
268
+ }
269
+ function getReplaceLabel() {
270
+ if (isSaving)
271
+ return "Saving…";
272
+ if (conflictChecked.size > 0)
273
+ return `Save & replace ${humanCount(conflictChecked.size, "memory")}`;
274
+ return "Save & replace selected";
275
+ }
276
+ return (_jsx(Dialog, { open: open, onOpenChange: handleRequestClose, children: _jsxs(DialogContent, { className: "flex flex-col overflow-hidden", children: [view === "conflicts" ? (_jsxs(DialogHeader, { children: [_jsxs("button", { type: "button", onClick: handleConflictBack, className: "flex items-center gap-1 typography-small text-muted-foreground hover:text-default-foreground w-fit transition-colors mb-0.5", children: [_jsx(ChevronLeft, { className: "size-3.5" }), "Back to memory"] }), _jsx(DialogTitle, { text: "Similar memories found" }), _jsx(DialogDescription, { children: "The memory you're saving may replace the following existing memories. Select which ones to replace and they will be hidden and superseded by the new memory." })] })) : view === "properties" ? (_jsxs(DialogHeader, { children: [_jsxs("button", { type: "button", onClick: () => setView("main"), className: "flex items-center gap-1 typography-small text-muted-foreground hover:text-default-foreground w-fit transition-colors mb-0.5", children: [_jsx(ChevronLeft, { className: "size-3.5" }), "Back to memory"] }), _jsx(DialogTitle, { text: "Schema properties" }), _jsxs(DialogDescription, { children: [selectedSchema?.label, requiredPropertyCount > 0 ? ` · ${requiredPropertyCount} required` : "", ` · ${humanCount(schemaProperties.length, "property")}`] })] })) : (_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { text: titleText }), _jsx(DialogDescription, { children: getDescriptionText() })] })), _jsx("div", { className: "flex flex-col gap-3 flex-1 min-h-0 overflow-y-auto -mx-8 px-8 py-1", children: view === "conflicts" ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "text-left typography-small text-accent-foreground hover:underline w-fit", onClick: toggleAllConflicts, children: allConflictsChecked ? "Select none" : "Select all" }), pendingConflicts.map((conflict) => (_jsx(ConflictMemoryCard, { conflict: conflict, scope: scope, checked: conflictChecked.has(conflict.iri), onToggleChecked: () => toggleConflictChecked(conflict.iri), showMerge: isTeam, merging: conflictMerging.has(conflict.iri), onToggleMerge: () => toggleConflictMerge(conflict.iri), currentUserId: currentUserId }, conflict.iri)))] })) : view === "properties" ? (schemaProperties.map((prop) => {
277
+ const localName = formatEntityName(prop.path);
278
+ const value = form.customProperties[prop.path] ?? "";
279
+ const isBoolean = prop.dataType === "http://www.w3.org/2001/XMLSchema#boolean";
280
+ const isNumeric = prop.dataType === "http://www.w3.org/2001/XMLSchema#integer" ||
281
+ prop.dataType === "http://www.w3.org/2001/XMLSchema#decimal";
282
+ const hasSmallEnum = isPresent(prop.allowedValues) && prop.allowedValues.length <= 4;
283
+ const hasLargeEnum = prop.allowedValues && prop.allowedValues.length > 4;
284
+ function setPropValue(v) {
285
+ setForm((prev) => ({
286
+ ...prev,
287
+ customProperties: { ...prev.customProperties, [prop.path]: v },
288
+ }));
289
+ }
290
+ return (_jsxs("div", { className: "flex flex-col gap-2 rounded-lg bg-faint p-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("label", { htmlFor: hasLargeEnum || (!hasSmallEnum && !isBoolean) ? `prop-${localName}` : undefined, className: "typography-small-medium text-default-foreground flex-1", children: [prop.label, prop.required && _jsx("span", { className: "text-error-foreground ml-1", children: "*" })] }), _jsx("span", { className: "typography-tiny text-muted-foreground bg-card border border-muted-border rounded px-1.5 py-0.5 shrink-0", children: shortDatatype(prop.dataType) })] }), hasSmallEnum ? (_jsx("div", { className: "flex gap-1 flex-wrap", children: prop.allowedValues.map((v) => (_jsx(Button, { type: "button", variant: value === v ? "primary" : "tertiary", size: "sm", rounded: "md", onClick: () => setPropValue(v), children: v }, v))) })) : hasLargeEnum ? (_jsxs(Select, { value: value, onValueChange: setPropValue, children: [_jsx(SelectTrigger, { label: prop.label, id: `prop-${localName}`, hiddenLabel: true, children: _jsx(SelectValue, { placeholder: `Select ${prop.label.toLowerCase()}` }) }), _jsx(SelectContent, { children: prop.allowedValues.map((v) => (_jsx(SelectItem, { value: v, children: v }, v))) })] })) : isBoolean ? (_jsx("div", { className: "flex gap-1", children: ["true", "false"].map((v) => (_jsx(Button, { type: "button", variant: (value || "false") === v ? "primary" : "tertiary", size: "sm", rounded: "md", className: "flex-1", onClick: () => setPropValue(v), children: v === "true" ? "Yes" : "No" }, v))) })) : (_jsx(Input, { id: `prop-${localName}`, type: isNumeric ? "number" : "text", value: value, onChange: (e) => setPropValue(e.target.value), placeholder: `Enter ${prop.label.toLowerCase()}` }))] }, prop.path));
291
+ })) : (_jsxs(MemoryFields, { form: form, errors: errors, isTeam: isTeam, isOperator: isOperator, schemas: schemas, onChange: (next) => setForm((prev) => ({ ...prev, ...next })), labelPlaceholder: isEdit ? "e.g. Target ROAS" : "Auto-generated from description", descriptionPlaceholder: getDescriptionPlaceholder(), autoFocus: !isEdit, onEditProperties: hasProperties ? () => setView("properties") : undefined, filledPropertyCount: filledPropertyCount, totalPropertyCount: schemaProperties.length, children: [_jsx(MemoryDateField, { id: "memory-expiry", label: "Valid until", hint: "No longer true after this date, so the agent stops using it. Nothing is deleted. Use it only for a real deadline.", placeholder: "Never expires", value: form.memoryValidUntil, onChange: (memoryValidUntil) => setForm((prev) => ({ ...prev, memoryValidUntil })) }), _jsx(MemoryDateField, { id: "memory-trust-horizon", label: "Trusted until", hint: "Stops pre-loading after this date but stays searchable. Use it for anything that drifts.", placeholder: "Trusted indefinitely", value: form.memoryTrustedUntil, notAfter: form.memoryValidUntil, error: errors.memoryTrustedUntil, onChange: (memoryTrustedUntil) => setForm((prev) => ({ ...prev, memoryTrustedUntil })) })] })) }), _jsx(DialogFooter, { children: view === "conflicts" ? (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "secondary", onClick: handleSaveWithoutSuperseding, className: "sm:mr-auto", disabled: isSaving, children: "Save & keep both" }), _jsx(Button, { onClick: handleConflictConfirmInline, disabled: conflictChecked.size === 0 || isSaving, children: getReplaceLabel() })] })) : view === "properties" ? (_jsx(Button, { onClick: () => setView("main"), children: "Done" })) : showDiscardConfirm ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "typography-small text-muted-foreground mr-auto", children: "Discard unsaved changes?" }), _jsx(Button, { variant: "tertiary", onClick: () => setShowDiscardConfirm(false), children: "Keep editing" }), _jsx(Button, { variant: "destructive", onClick: () => onOpenChange(false), children: "Discard" })] })) : conflictCheckTimedOut && pendingForm ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "typography-small text-muted-foreground mr-auto", children: "Conflict check timed out. Save without checking?" }), _jsx(Button, { variant: "tertiary", onClick: () => {
292
+ setConflictCheckTimedOut(false);
293
+ setPendingForm(null);
294
+ }, disabled: isSaving, children: "Cancel" }), _jsx(Button, { onClick: () => {
295
+ setConflictCheckTimedOut(false);
296
+ void onSave(pendingForm, []);
297
+ setPendingForm(null);
298
+ }, disabled: isSaving, children: isSaving ? "Saving…" : "Save anyway" })] })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "tertiary", onClick: () => handleRequestClose(false), children: "Cancel" }), _jsx(Button, { onClick: handleSave, disabled: (submitted && hasErrors) || isBusy, children: getSaveLabel() })] })) })] }) }));
299
+ }
@@ -0,0 +1,9 @@
1
+ import { type GQLMemoryScope } from "@manta-eu/client";
2
+ type MemoryOnboardingWizardProps = {
3
+ scope?: GQLMemoryScope;
4
+ onComplete: () => void;
5
+ onSkip: () => void;
6
+ };
7
+ export declare function MemoryOnboardingWizard(props: MemoryOnboardingWizardProps): import("react/jsx-runtime").JSX.Element | null;
8
+ export {};
9
+ //# sourceMappingURL=MemoryOnboardingWizard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemoryOnboardingWizard.d.ts","sourceRoot":"","sources":["../../../../src/routes/memories/components/MemoryOnboardingWizard.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,cAAc,EAA6C,MAAM,kBAAkB,CAAC;AAgDjH,KAAK,2BAA2B,GAAG;IACjC,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,kDAyIxE"}