@manta-eu/react 0.3.0 → 0.5.0

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 (167) hide show
  1. package/README.md +1 -0
  2. package/dist/components/chat-view/Chat.d.ts +20 -0
  3. package/dist/components/chat-view/Chat.d.ts.map +1 -1
  4. package/dist/components/chat-view/Chat.js +51 -15
  5. package/dist/components/chat-view/Chat.js.map +1 -1
  6. package/dist/components/embedded-chat/index.d.ts +1 -1
  7. package/dist/components/embedded-chat/index.d.ts.map +1 -1
  8. package/dist/components/embedded-chat/index.js +1 -1
  9. package/dist/components/embedded-chat/index.js.map +1 -1
  10. package/dist/components/planning/ActioningView.d.ts.map +1 -1
  11. package/dist/components/planning/ActioningView.js +1 -1
  12. package/dist/components/planning/ActioningView.js.map +1 -1
  13. package/dist/components/planning/ExecutionTopBar.d.ts +1 -0
  14. package/dist/components/planning/ExecutionTopBar.d.ts.map +1 -1
  15. package/dist/components/planning/ExecutionTopBar.js +3 -2
  16. package/dist/components/planning/ExecutionTopBar.js.map +1 -1
  17. package/dist/components/planning/plan-detail-utils.d.ts +1 -0
  18. package/dist/components/planning/plan-detail-utils.d.ts.map +1 -1
  19. package/dist/components/planning/plan-detail-utils.js +7 -2
  20. package/dist/components/planning/plan-detail-utils.js.map +1 -1
  21. package/dist/components/tool-invocation-card/ToolOutputRenderer.d.ts.map +1 -1
  22. package/dist/components/tool-invocation-card/ToolOutputRenderer.js +5 -2
  23. package/dist/components/tool-invocation-card/ToolOutputRenderer.js.map +1 -1
  24. package/dist/graph/EntityCard.d.ts.map +1 -1
  25. package/dist/graph/EntityCard.js +2 -1
  26. package/dist/graph/EntityCard.js.map +1 -1
  27. package/dist/graph/EntityViewWithLinks.d.ts +7 -0
  28. package/dist/graph/EntityViewWithLinks.d.ts.map +1 -0
  29. package/dist/graph/EntityViewWithLinks.js +28 -0
  30. package/dist/graph/EntityViewWithLinks.js.map +1 -0
  31. package/dist/graph/card-links.d.ts +19 -0
  32. package/dist/graph/card-links.d.ts.map +1 -0
  33. package/dist/graph/card-links.js +23 -0
  34. package/dist/graph/card-links.js.map +1 -0
  35. package/dist/graph/nodes/BrandAssetAnalysisView.d.ts.map +1 -1
  36. package/dist/graph/nodes/BrandAssetAnalysisView.js +1 -1
  37. package/dist/graph/nodes/BrandAssetAnalysisView.js.map +1 -1
  38. package/dist/graph/nodes/BrandFontSuggestionView.d.ts.map +1 -1
  39. package/dist/graph/nodes/BrandFontSuggestionView.js +1 -1
  40. package/dist/graph/nodes/BrandFontSuggestionView.js.map +1 -1
  41. package/dist/graph/nodes/EntityCardShell.d.ts +2 -0
  42. package/dist/graph/nodes/EntityCardShell.d.ts.map +1 -1
  43. package/dist/graph/nodes/EntityCardShell.js +13 -3
  44. package/dist/graph/nodes/EntityCardShell.js.map +1 -1
  45. package/dist/graph/nodes/NodePropertyTable.d.ts +2 -1
  46. package/dist/graph/nodes/NodePropertyTable.d.ts.map +1 -1
  47. package/dist/graph/nodes/NodePropertyTable.js +10 -1
  48. package/dist/graph/nodes/NodePropertyTable.js.map +1 -1
  49. package/dist/graph/nodes/node-property-display.d.ts +0 -1
  50. package/dist/graph/nodes/node-property-display.d.ts.map +1 -1
  51. package/dist/graph/nodes/node-property-display.js +2 -6
  52. package/dist/graph/nodes/node-property-display.js.map +1 -1
  53. package/dist/graph/special-nodes/AttributeList.d.ts.map +1 -1
  54. package/dist/graph/special-nodes/AttributeList.js +3 -2
  55. package/dist/graph/special-nodes/AttributeList.js.map +1 -1
  56. package/dist/graph/special-nodes/RelatedSection.d.ts +6 -0
  57. package/dist/graph/special-nodes/RelatedSection.d.ts.map +1 -0
  58. package/dist/graph/special-nodes/RelatedSection.js +16 -0
  59. package/dist/graph/special-nodes/RelatedSection.js.map +1 -0
  60. package/dist/graph/special-nodes/card-facts.d.ts +2 -0
  61. package/dist/graph/special-nodes/card-facts.d.ts.map +1 -1
  62. package/dist/graph/special-nodes/card-facts.js +11 -0
  63. package/dist/graph/special-nodes/card-facts.js.map +1 -1
  64. package/dist/graph/types.d.ts +2 -2
  65. package/dist/graph/types.d.ts.map +1 -1
  66. package/dist/hooks/use-approve-anyway.d.ts.map +1 -1
  67. package/dist/hooks/use-approve-anyway.js +8 -8
  68. package/dist/hooks/use-approve-anyway.js.map +1 -1
  69. package/dist/hooks/use-connection-problems.d.ts.map +1 -1
  70. package/dist/hooks/useValidateActionPlan.d.ts.map +1 -1
  71. package/dist/ldo-ui.d.ts +1 -0
  72. package/dist/ldo-ui.d.ts.map +1 -1
  73. package/dist/ldo-ui.js +1 -0
  74. package/dist/ldo-ui.js.map +1 -1
  75. package/dist/lib/chat-types.d.ts +17 -0
  76. package/dist/lib/chat-types.d.ts.map +1 -1
  77. package/dist/lib/chat-types.js.map +1 -1
  78. package/dist/providers/ChatManagerProvider.d.ts +7 -0
  79. package/dist/providers/ChatManagerProvider.d.ts.map +1 -1
  80. package/dist/providers/ChatManagerProvider.js +129 -16
  81. package/dist/providers/ChatManagerProvider.js.map +1 -1
  82. package/dist/providers/chat-connection-context.d.ts +4 -1
  83. package/dist/providers/chat-connection-context.d.ts.map +1 -1
  84. package/dist/providers/chat-connection-context.js.map +1 -1
  85. package/dist/providers/chat-connection-lifecycle.d.ts +1 -0
  86. package/dist/providers/chat-connection-lifecycle.d.ts.map +1 -1
  87. package/dist/providers/chat-connection-lifecycle.js.map +1 -1
  88. package/dist/providers/chat-socket-auth.d.ts +3 -1
  89. package/dist/providers/chat-socket-auth.d.ts.map +1 -1
  90. package/dist/providers/chat-socket-auth.js +27 -9
  91. package/dist/providers/chat-socket-auth.js.map +1 -1
  92. package/dist/providers/chat-startup-timing.d.ts +14 -0
  93. package/dist/providers/chat-startup-timing.d.ts.map +1 -0
  94. package/dist/providers/chat-startup-timing.js +72 -0
  95. package/dist/providers/chat-startup-timing.js.map +1 -0
  96. package/dist/routes/data-explorer/components/record-detail/ActionFormDialog.d.ts.map +1 -1
  97. package/dist/routes/data-explorer/components/record-detail/ActionFormDialog.js +7 -21
  98. package/dist/routes/data-explorer/components/record-detail/ActionFormDialog.js.map +1 -1
  99. package/dist/routes/data-explorer/components/record-detail/ChatPreview.d.ts.map +1 -1
  100. package/dist/routes/data-explorer/components/record-detail/ChatPreview.js +2 -2
  101. package/dist/routes/data-explorer/components/record-detail/ChatPreview.js.map +1 -1
  102. package/dist/routes/data-explorer/components/record-detail/action-properties.d.ts +2 -2
  103. package/dist/routes/data-explorer/components/record-detail/action-properties.d.ts.map +1 -1
  104. package/dist/routes/data-explorer/components/record-detail/action-properties.js +3 -3
  105. package/dist/routes/data-explorer/components/record-detail/action-properties.js.map +1 -1
  106. package/dist/routes/integrations/catalog-shape.d.ts +1 -0
  107. package/dist/routes/integrations/catalog-shape.d.ts.map +1 -1
  108. package/dist/routes/integrations/catalog-shape.js.map +1 -1
  109. package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.d.ts +7 -0
  110. package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.d.ts.map +1 -0
  111. package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.js +76 -0
  112. package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.js.map +1 -0
  113. package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.d.ts +7 -0
  114. package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.d.ts.map +1 -1
  115. package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.js +8 -7
  116. package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.js.map +1 -1
  117. package/dist/routes/integrations/components/connect-forms/inferal-locations.d.ts +3 -0
  118. package/dist/routes/integrations/components/connect-forms/inferal-locations.d.ts.map +1 -0
  119. package/dist/routes/integrations/components/connect-forms/inferal-locations.js +13 -0
  120. package/dist/routes/integrations/components/connect-forms/inferal-locations.js.map +1 -0
  121. package/dist/routes/integrations/connect-forms.d.ts +2 -0
  122. package/dist/routes/integrations/connect-forms.d.ts.map +1 -1
  123. package/dist/routes/integrations/connect-forms.js +2 -0
  124. package/dist/routes/integrations/connect-forms.js.map +1 -1
  125. package/dist/routes/integrations/connection-view.d.ts.map +1 -1
  126. package/dist/routes/integrations/connection-view.js +1 -0
  127. package/dist/routes/integrations/connection-view.js.map +1 -1
  128. package/dist/routes/plans/components/ActionPlansTableColumns.d.ts.map +1 -1
  129. package/dist/routes/plans/components/ActionPlansTableColumns.js +3 -3
  130. package/dist/routes/plans/components/ActionPlansTableColumns.js.map +1 -1
  131. package/package.json +7 -7
  132. package/src/components/chat-view/Chat.tsx +62 -15
  133. package/src/components/embedded-chat/index.tsx +9 -2
  134. package/src/components/planning/ActioningView.tsx +1 -0
  135. package/src/components/planning/ExecutionTopBar.tsx +14 -2
  136. package/src/components/planning/plan-detail-utils.tsx +8 -2
  137. package/src/components/tool-invocation-card/ToolOutputRenderer.tsx +5 -2
  138. package/src/graph/EntityCard.tsx +2 -1
  139. package/src/graph/EntityViewWithLinks.tsx +43 -0
  140. package/src/graph/card-links.tsx +43 -0
  141. package/src/graph/nodes/BrandAssetAnalysisView.tsx +1 -0
  142. package/src/graph/nodes/BrandFontSuggestionView.tsx +1 -0
  143. package/src/graph/nodes/EntityCardShell.tsx +37 -3
  144. package/src/graph/nodes/NodePropertyTable.tsx +16 -2
  145. package/src/graph/nodes/node-property-display.ts +2 -7
  146. package/src/graph/special-nodes/AttributeList.tsx +4 -2
  147. package/src/graph/special-nodes/RelatedSection.tsx +29 -0
  148. package/src/graph/special-nodes/card-facts.ts +13 -0
  149. package/src/graph/types.ts +1 -1
  150. package/src/hooks/use-approve-anyway.ts +8 -11
  151. package/src/ldo-ui.ts +1 -0
  152. package/src/lib/chat-types.ts +18 -0
  153. package/src/providers/ChatManagerProvider.tsx +166 -15
  154. package/src/providers/chat-connection-context.ts +4 -0
  155. package/src/providers/chat-connection-lifecycle.ts +1 -0
  156. package/src/providers/chat-socket-auth.ts +31 -12
  157. package/src/providers/chat-startup-timing.ts +89 -0
  158. package/src/routes/data-explorer/components/record-detail/ActionFormDialog.tsx +7 -29
  159. package/src/routes/data-explorer/components/record-detail/ChatPreview.tsx +6 -1
  160. package/src/routes/data-explorer/components/record-detail/action-properties.ts +3 -3
  161. package/src/routes/integrations/catalog-shape.ts +2 -0
  162. package/src/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.tsx +140 -0
  163. package/src/routes/integrations/components/connect-forms/OAuthSetupConnectForm.tsx +27 -5
  164. package/src/routes/integrations/components/connect-forms/inferal-locations.ts +13 -0
  165. package/src/routes/integrations/connect-forms.ts +2 -0
  166. package/src/routes/integrations/connection-view.ts +1 -0
  167. package/src/routes/plans/components/ActionPlansTableColumns.tsx +3 -2
@@ -1,3 +1,4 @@
1
+ import { withChatModeQueryParam } from "@manta-eu/client/shared/chat-manager";
1
2
  import { withDatastoreQueryParam } from "@manta-eu/client/shared/datastore-path";
2
3
  import { isShowEntitiesResult } from "@manta-eu/client/shared/tools";
3
4
  import {
@@ -5,7 +6,7 @@ import {
5
6
  type WorkspaceArtifact,
6
7
  } from "@manta-eu/client/shared/workspace-artifacts";
7
8
  import type { ToolUIPart } from "ai";
8
- import { useCallback } from "react";
9
+ import { useCallback, useContext } from "react";
9
10
  import { match } from "ts-pattern";
10
11
  import { FileMessagePart } from "../chat-view/FileMessagePart";
11
12
  import { ActionPlanShowcaseCard } from "./ActionPlanShowcaseCard";
@@ -45,6 +46,7 @@ import {
45
46
  } from "#components/chart/index";
46
47
  import { decodeChartEntities } from "#lib/decode-chart-entities";
47
48
  import { cn } from "#lib/utils";
49
+ import { ChatManagerContext } from "#providers/chat-connection-context";
48
50
  import { useChatManagerConnection } from "#providers/ChatManagerProvider";
49
51
  import { useMantaClient } from "#providers/MantaClientProvider";
50
52
 
@@ -129,12 +131,13 @@ function WorkspaceArtifactResultDisplay(props: WorkspaceArtifactResultDisplayPro
129
131
  const { result } = props;
130
132
 
131
133
  const { datastoreId } = useMantaClient();
134
+ const mode = useContext(ChatManagerContext)?.managerConnection.mode;
132
135
 
133
136
  return (
134
137
  <FileMessagePart
135
138
  part={{
136
139
  type: "file",
137
- url: withDatastoreQueryParam(result.url, datastoreId),
140
+ url: withChatModeQueryParam(withDatastoreQueryParam(result.url, datastoreId), mode),
138
141
  mediaType: result.mediaType,
139
142
  filename: result.filename,
140
143
  }}
@@ -3,6 +3,7 @@ import { type JsonLdGraphDocument } from "@manta-eu/rdf-lib/jsonld";
3
3
  import { expandPrefix } from "@manta-eu/rdf-lib/namespaces";
4
4
  import { useMemo } from "react";
5
5
  import { findDetailViewForTypes } from "./detail-views";
6
+ import { EntityViewWithLinks } from "./EntityViewWithLinks";
6
7
  import { rdfGraphToNodesAndEdges } from "./graph-model";
7
8
  import { jsonLdToRdfGraph } from "./jsonld-bridge";
8
9
  import { GenericView, getEntityView } from "./special-nodes";
@@ -35,7 +36,7 @@ export function EntityCard(props: EntityCardProps) {
35
36
 
36
37
  const View = node ? getEntityView(node.data.type, node.data.actionShape) : undefined;
37
38
 
38
- if (node && View) return <View data={node.data} />;
39
+ if (node && View) return <EntityViewWithLinks view={View} data={node.data} />;
39
40
 
40
41
  return <GenericView data={node?.data ?? stubNodeData(entityIri)} />;
41
42
  }
@@ -0,0 +1,43 @@
1
+ import { isPresent } from "@manta-eu/lib/present";
2
+ import { uniq } from "ramda";
3
+ import { useMemo } from "react";
4
+ import { type CardLinks, CardLinksProvider } from "./card-links";
5
+ import { shouldHidePreviewField } from "./graph-list-utils";
6
+ import { namedRelations } from "./special-nodes/card-facts";
7
+ import type { EntityView, NodeRelation, RDFNodeData } from "./types";
8
+ import { useInstanceLabels } from "#hooks/use-instance-labels";
9
+
10
+ export type EntityViewWithLinksProps = { view: EntityView; data: RDFNodeData };
11
+
12
+ const noRelations: NodeRelation[] = [];
13
+
14
+ function relationKey(relation: NodeRelation): string {
15
+ return `${relation.predicate} ${relation.target}`;
16
+ }
17
+
18
+ // The named records a typed card links to go at the top of its frame.
19
+ export function EntityViewWithLinks(props: EntityViewWithLinksProps) {
20
+ const { view: View, data } = props;
21
+
22
+ const relations = useMemo(
23
+ () => (data.relations ?? noRelations).filter((relation) => !shouldHidePreviewField("", relation.predicate)),
24
+ [data.relations],
25
+ );
26
+ const targets = useMemo(() => uniq(relations.map((relation) => relation.target)), [relations]);
27
+ const instanceLabels = useInstanceLabels(targets);
28
+
29
+ // Keyed on which links are named, so a card does not rebuild each time another card's names load.
30
+ const namedKey = namedRelations(relations, instanceLabels).map(relationKey).join("\n");
31
+
32
+ const links = useMemo((): CardLinks | null => {
33
+ const namedKeys = new Set(namedKey.split("\n"));
34
+ const named = relations.filter((relation) => namedKeys.has(relationKey(relation)));
35
+ return isPresent(named) ? { relations: named } : null;
36
+ }, [relations, namedKey]);
37
+
38
+ return (
39
+ <CardLinksProvider links={links}>
40
+ <View data={data} />
41
+ </CardLinksProvider>
42
+ );
43
+ }
@@ -0,0 +1,43 @@
1
+ import type { IRI } from "@manta-eu/rdf-lib/namespaces";
2
+ import { createContext, type ReactNode, useContext } from "react";
3
+ import type { NodeRelation } from "./types";
4
+
5
+ // The named records a card links to, for the card to list at the top of its frame.
6
+ export type CardLinks = { relations: NodeRelation[] };
7
+
8
+ // A surface such as chat hands the card the records it links to; the canvas sets none.
9
+ const CardLinksContext = createContext<CardLinks | null>(null);
10
+
11
+ const noRecords: ReadonlySet<IRI> = new Set();
12
+
13
+ // The records the card already shows above its content, so a property table leaves them out.
14
+ const LinksShownAboveContext = createContext(noRecords);
15
+
16
+ export type CardLinksProviderProps = { links: CardLinks | null; children: ReactNode };
17
+
18
+ export function CardLinksProvider(props: CardLinksProviderProps) {
19
+ const { links, children } = props;
20
+
21
+ return <CardLinksContext.Provider value={links}>{children}</CardLinksContext.Provider>;
22
+ }
23
+
24
+ export function useCardLinks(): CardLinks | null {
25
+ return useContext(CardLinksContext);
26
+ }
27
+
28
+ export type CardContentProviderProps = { shown: ReadonlySet<IRI>; children: ReactNode };
29
+
30
+ // Wraps a card's own content: nested cards get no links of their own, and tables learn what is shown above.
31
+ export function CardContentProvider(props: CardContentProviderProps) {
32
+ const { shown, children } = props;
33
+
34
+ return (
35
+ <CardLinksContext.Provider value={null}>
36
+ <LinksShownAboveContext.Provider value={shown}>{children}</LinksShownAboveContext.Provider>
37
+ </CardLinksContext.Provider>
38
+ );
39
+ }
40
+
41
+ export function useLinksShownAbove(): ReadonlySet<IRI> {
42
+ return useContext(LinksShownAboveContext);
43
+ }
@@ -32,6 +32,7 @@ export const BrandAssetAnalysisView = memo(function BrandAssetAnalysisView(props
32
32
  icon={ScanEye}
33
33
  label={`Asset Analysis: ${displayName}`}
34
34
  labelMaxWidth="max-w-140"
35
+ omitPredicates={listedElsewhere}
35
36
  selected={selected}
36
37
  ringColor={ringColor}
37
38
  onContextMenu={onContextMenu}
@@ -101,6 +101,7 @@ export const BrandFontSuggestionView = memo(function BrandFontSuggestionView(pro
101
101
  icon={Type}
102
102
  label={`Font Suggestion: ${family}`}
103
103
  labelMaxWidth="max-w-140"
104
+ omitPredicates={listedElsewhere}
104
105
  selected={selected}
105
106
  ringColor={ringColor}
106
107
  onContextMenu={onContextMenu}
@@ -1,5 +1,10 @@
1
+ import { isPresent } from "@manta-eu/lib/present";
2
+ import type { IRI } from "@manta-eu/rdf-lib/namespaces";
1
3
  import type { LucideIcon } from "lucide-react";
2
- import { memo, type MouseEvent, type ReactNode } from "react";
4
+ import { memo, type MouseEvent, type ReactNode, useMemo } from "react";
5
+ import { CardContentProvider, useCardLinks } from "../card-links";
6
+ import { RelatedSection } from "../special-nodes/RelatedSection";
7
+ import type { NodeRelation } from "../types";
3
8
  import { cn } from "#style";
4
9
 
5
10
  export type EntityCardShellProps = {
@@ -9,11 +14,31 @@ export type EntityCardShellProps = {
9
14
  selected?: boolean;
10
15
  ringColor?: string;
11
16
  onContextMenu?: (e: MouseEvent) => void;
17
+ // Fields the card body shows itself (an image, a weights list), so the links section leaves them out too.
18
+ omitPredicates?: readonly IRI[];
12
19
  children: ReactNode;
13
20
  };
14
21
 
22
+ const noRelations: NodeRelation[] = [];
23
+
15
24
  export const EntityCardShell = memo(function EntityCardShell(props: EntityCardShellProps) {
16
- const { icon: Icon, label, labelMaxWidth = "max-w-50", selected = false, ringColor, onContextMenu, children } = props;
25
+ const {
26
+ icon: Icon,
27
+ label,
28
+ labelMaxWidth = "max-w-50",
29
+ selected = false,
30
+ ringColor,
31
+ onContextMenu,
32
+ omitPredicates,
33
+ children,
34
+ } = props;
35
+ const links = useCardLinks();
36
+
37
+ const listed = useMemo(() => {
38
+ const omitted = new Set(omitPredicates);
39
+ return (links?.relations ?? noRelations).filter((relation) => !omitted.has(relation.predicate));
40
+ }, [links, omitPredicates]);
41
+ const shown = useMemo(() => new Set(listed.map((relation) => relation.target)), [listed]);
17
42
 
18
43
  return (
19
44
  <div className="w-fit flex flex-col items-start gap-1" onContextMenu={onContextMenu}>
@@ -34,7 +59,16 @@ export const EntityCardShell = memo(function EntityCardShell(props: EntityCardSh
34
59
  )}
35
60
  style={selected && ringColor ? { boxShadow: `0 0 0 1.5px ${ringColor}` } : undefined}
36
61
  >
37
- {children}
62
+ <CardContentProvider shown={shown}>
63
+ {isPresent(listed) ? (
64
+ <div className="overflow-hidden rounded-md bg-muted">
65
+ <RelatedSection relations={listed} />
66
+ {children}
67
+ </div>
68
+ ) : (
69
+ children
70
+ )}
71
+ </CardContentProvider>
38
72
  </div>
39
73
  </div>
40
74
  );
@@ -1,4 +1,7 @@
1
+ import { isPresent } from "@manta-eu/lib/present";
2
+ import type { IRI } from "@manta-eu/rdf-lib/namespaces";
1
3
  import { useCallback, useMemo } from "react";
4
+ import { useLinksShownAbove } from "../card-links";
2
5
  import { shouldHidePreviewField } from "../graph-list-utils";
3
6
  import { AttributeList, useAttributeFacts } from "../special-nodes/AttributeList";
4
7
  import type { NodeProperty, NodeRelation } from "../types";
@@ -17,7 +20,7 @@ export type NodePropertyTableProps = {
17
20
  // The node's raw values, so a multi-valued property renders as tags rather than a joined string.
18
21
  jsonLd?: Record<string, unknown>;
19
22
  // Rendered above the table by the card itself, so the table skips them.
20
- omitPredicates?: readonly string[];
23
+ omitPredicates?: readonly IRI[];
21
24
  relations?: NodeRelation[];
22
25
  };
23
26
 
@@ -45,6 +48,17 @@ export function NodePropertyTable(props: NodePropertyTableProps) {
45
48
  );
46
49
 
47
50
  const { facts, previewCount } = useAttributeFacts({ properties, relations: visibleRelations, jsonLd, include });
51
+ const shownAbove = useLinksShownAbove();
52
+ const shown = useMemo(
53
+ () =>
54
+ facts.flatMap((fact) => {
55
+ if (!isPresent(fact.entityIris)) return [fact];
56
+
57
+ const rest = fact.entityIris.filter((iri) => !shownAbove.has(iri));
58
+ return isPresent(rest) ? [{ ...fact, entityIris: rest }] : [];
59
+ }),
60
+ [facts, shownAbove],
61
+ );
48
62
 
49
- return <AttributeList facts={facts} previewCount={previewCount} showAll />;
63
+ return <AttributeList facts={shown} previewCount={previewCount} showAll />;
50
64
  }
@@ -1,3 +1,4 @@
1
+ import { isHexColor } from "@manta-eu/lib/color";
1
2
  import { formatPercent, parseNumeric } from "../property-formatters";
2
3
 
3
4
  export type PropertyDisplay =
@@ -9,12 +10,6 @@ export type PropertyDisplay =
9
10
  | { kind: "prose"; text: string }
10
11
  | { kind: "text"; text: string };
11
12
 
12
- const hexPattern = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
13
-
14
- // Shared with the palette swatch: both guard a style sink, so the two must not drift.
15
- export function isHexColor(value: string): boolean {
16
- return hexPattern.test(value);
17
- }
18
13
  const prosePhraseLength = 60;
19
14
 
20
15
  // Wider than formatDisplayValue's isPercentLabel, which excludes score — merging them would change both.
@@ -36,7 +31,7 @@ export function classifyPropertyDisplay(args: { label: string; formatted: string
36
31
  if (values.length > 1) return { kind: "tags", values };
37
32
  }
38
33
 
39
- if (hexPattern.test(formatted)) return { kind: "color", hex: formatted };
34
+ if (isHexColor(formatted)) return { kind: "color", hex: formatted };
40
35
 
41
36
  // RDF booleans arrive as the literals "true"/"false" from the graph, as real booleans from fixtures.
42
37
  if (raw === true || raw === false) return { kind: "boolean", value: raw };
@@ -97,8 +97,10 @@ export function AttributeList(props: AttributeListProps) {
97
97
  );
98
98
 
99
99
  const visibleFacts = searching || expanded ? matchingFacts : facts.slice(0, previewCount);
100
- const hasHiddenChips = visibleFacts.some((fact) => visibleChips(fact.entityIris, expanded).hiddenCount > 0);
101
100
  const hiddenCount = facts.length - previewCount;
101
+ const collapsible =
102
+ hiddenCount > 0 ||
103
+ facts.slice(0, previewCount).some((fact) => visibleChips(fact.entityIris, false).hiddenCount > 0);
102
104
 
103
105
  if (!isPresent(facts)) return null;
104
106
 
@@ -161,7 +163,7 @@ export function AttributeList(props: AttributeListProps) {
161
163
  {searching && !isPresent(visibleFacts) && (
162
164
  <span className="typography-tiny py-1 text-muted-foreground">No matching attributes.</span>
163
165
  )}
164
- {!showAll && (hiddenCount > 0 || hasHiddenChips || expanded) && (
166
+ {!showAll && collapsible && (
165
167
  <NodePropertyExpander
166
168
  expanded={expanded}
167
169
  collapsedLabel={hiddenCount > 0 ? `Show all ${facts.length}` : "Show all"}
@@ -0,0 +1,29 @@
1
+ import { isPresent } from "@manta-eu/lib/present";
2
+ import { useMemo } from "react";
3
+ import type { NodeProperty, NodeRelation } from "../types";
4
+ import { AttributeList, useAttributeFacts } from "./AttributeList";
5
+ import { singleLinksFirst } from "./card-facts";
6
+
7
+ export type RelatedSectionProps = { relations: NodeRelation[] };
8
+
9
+ const noProperties: NodeProperty[] = [];
10
+ const noJsonLd: Record<string, unknown> = {};
11
+
12
+ export function RelatedSection(props: RelatedSectionProps) {
13
+ const { relations } = props;
14
+
15
+ const { facts, previewCount } = useAttributeFacts({ properties: noProperties, relations, jsonLd: noJsonLd });
16
+ const ordered = useMemo(() => singleLinksFirst(facts), [facts]);
17
+
18
+ if (!isPresent(ordered)) return null;
19
+
20
+ return (
21
+ <div
22
+ role="group"
23
+ aria-label="Related records"
24
+ className="flex max-w-90 flex-col border-b border-muted-border px-3 pb-2 pt-2.5 text-default-foreground"
25
+ >
26
+ <AttributeList facts={ordered} previewCount={previewCount} />
27
+ </div>
28
+ );
29
+ }
@@ -107,6 +107,19 @@ export function keyWords(key: string): string[] {
107
107
  .map((word) => word.toLowerCase());
108
108
  }
109
109
 
110
+ // Links to records with a name of their own, so unnamed sub-records (an insight's action rows) stay off the card.
111
+ export function namedRelations(relations: NodeRelation[], instanceLabels: Map<string, string | null>): NodeRelation[] {
112
+ return relations.filter((relation) => isPresent(instanceLabels.get(relation.target)));
113
+ }
114
+
115
+ // What the record belongs to (one campaign, one account) reads before what it has many of.
116
+ export function singleLinksFirst(facts: CardFact[]): CardFact[] {
117
+ return [
118
+ ...facts.filter((fact) => fact.entityIris.length <= 1),
119
+ ...facts.filter((fact) => fact.entityIris.length > 1),
120
+ ];
121
+ }
122
+
110
123
  export function relationFacts(
111
124
  relations: NodeRelation[],
112
125
  terms: Map<string, OntologyTerm>,
@@ -11,7 +11,7 @@ export type NodeProperty = {
11
11
  };
12
12
 
13
13
  // A property whose value is another entity in the graph; the canvas draws it as an edge instead.
14
- export type NodeRelation = { predicate: string; target: string };
14
+ export type NodeRelation = { predicate: IRI; target: IRI };
15
15
 
16
16
  export type RDFNodeData = {
17
17
  label: string;
@@ -1,4 +1,6 @@
1
1
  import { useExecuteAgentActionAdditionMutation } from "@manta-eu/client";
2
+ import { entityActionInput } from "@manta-eu/client/shared/action-document";
3
+ import { BrandAssetDecisionSchema } from "@manta-eu/ldo/schemas/brand";
2
4
  import { isPresent } from "@manta-eu/lib/present";
3
5
  import { isValidFullIri, namespaces } from "@manta-eu/rdf-lib/namespaces";
4
6
  import { useQueryClient } from "@tanstack/react-query";
@@ -8,13 +10,12 @@ import { useCallback, useMemo, useRef, useState } from "react";
8
10
  import { toast } from "sonner";
9
11
  import type { ManualAction } from "./use-manual-actions";
10
12
  import { useSparqlSelect } from "./use-sparql-construct";
11
- import { buildActionDocument, mintSubjectIri } from "#lib/action-document";
13
+ import { mintSubjectIri } from "#lib/action-document";
12
14
  import { useMantaClient } from "#providers/MantaClientProvider";
13
15
  import { invalidateActionPlanQueries } from "#shared/query-keys";
14
16
 
15
17
  const df = new DataFactory();
16
18
  const assetAnalysis = `${namespaces.brand}AssetAnalysis`;
17
- const assetDecision = `${namespaces.brand}AssetDecision`;
18
19
  const decideAssetShape = `${namespaces.brand}DecideAssetShape`;
19
20
 
20
21
  export type ApproveAnywayHandle = {
@@ -68,16 +69,12 @@ export function useApproveAnyway(planGraphIri: string): ApproveAnywayHandle {
68
69
  if (inFlight.current.has(analysisIri)) return;
69
70
  inFlight.current.add(analysisIri);
70
71
  setApproving(new Set(inFlight.current));
71
- const subjectIri = mintSubjectIri(assetDecision);
72
- const properties = buildActionDocument(
73
- { subjectIri, rdfType: assetDecision },
74
- {
75
- [`${namespaces.brand}decisionOnAsset`]: { "@id": analysisIri },
76
- [`${namespaces.brand}decision`]: "approved",
77
- },
78
- );
79
72
  void startAddition({
80
- input: { subjectIri, rdfType: assetDecision, actionShape: decideAssetShape, properties },
73
+ input: entityActionInput(BrandAssetDecisionSchema, decideAssetShape, {
74
+ $id: mintSubjectIri(BrandAssetDecisionSchema["@type"]),
75
+ decisionOnAsset: analysisIri,
76
+ decision: "approved",
77
+ }),
81
78
  })
82
79
  .then(({ executeActionAddition }) => {
83
80
  if (!executeActionAddition.success) throw new Error(executeActionAddition.error ?? "The approval failed");
package/src/ldo-ui.ts CHANGED
@@ -344,6 +344,7 @@ export {
344
344
  export { NodeFrame, type NodeFrameProps } from "./graph/special-nodes/NodeFrame";
345
345
 
346
346
  export { EntityCard, findEntityNode, type EntityCardProps } from "./graph/EntityCard";
347
+ export { EntityViewWithLinks, type EntityViewWithLinksProps } from "./graph/EntityViewWithLinks";
347
348
  export { findDetailViewForTypes, findPluralViewForTypes, type DetailView, type PluralView } from "./graph/detail-views";
348
349
  export { EntityStripDisplay, type EntityStripDisplayProps } from "./graph/EntityStripDisplay";
349
350
  export { stubNodeData } from "./graph/stub-node";
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable import/order -- each type is imported beside the re-export it exists for, which is
2
2
  how this file is organized; they are type-only, so the positions have no runtime meaning. */
3
+ import type { ConversationMode } from "@manta-eu/client/shared/chat-manager";
3
4
  import type { MantaModelId } from "@manta-eu/client/shared/model";
4
5
  import type { ReactNode } from "react";
5
6
  import type { Datastore } from "#ldo-ui";
@@ -26,6 +27,21 @@ export type CopilotMenuDefaults = {
26
27
 
27
28
  export type ChatDraft = { id: string; text: string };
28
29
 
30
+ export type ChatStartupReport = {
31
+ surface: "embedded" | "app";
32
+ mode: "ticket" | "cookie";
33
+ openIndex: number;
34
+ hostTokenMs?: number;
35
+ ticketMs?: number;
36
+ ticketAttempts?: number;
37
+ socketMs: number;
38
+ socketAttempts: number;
39
+ chatMountMs: number;
40
+ socketReadyAtOpen: boolean;
41
+ firstPaintMs: number;
42
+ interactiveMs: number;
43
+ };
44
+
29
45
  // NOTE: Adding to this is a breaking change to library clients
30
46
  export type AuthenticatedChatProps = {
31
47
  datastore?: Datastore;
@@ -39,6 +55,7 @@ export type AuthenticatedChatProps = {
39
55
  collapsible?: boolean;
40
56
  isCollapsed?: boolean;
41
57
  pageContext?: PageContext;
58
+ mode?: ConversationMode;
42
59
  allowedModels?: MantaModelId[];
43
60
  initialPrompt?: string;
44
61
  initialDraft?: ChatDraft;
@@ -48,6 +65,7 @@ export type AuthenticatedChatProps = {
48
65
  assistantIcon?: AssistantIcon;
49
66
  mark?: ReactNode;
50
67
  renderMenuItems?: (defaults: CopilotMenuDefaults) => ReactNode[];
68
+ onStartupTiming?: (report: ChatStartupReport) => void;
51
69
  };
52
70
 
53
71
  export type ResolvedChatProps = AuthenticatedChatProps & {