@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.
- package/README.md +1 -0
- package/dist/components/chat-view/Chat.d.ts +20 -0
- package/dist/components/chat-view/Chat.d.ts.map +1 -1
- package/dist/components/chat-view/Chat.js +51 -15
- package/dist/components/chat-view/Chat.js.map +1 -1
- package/dist/components/embedded-chat/index.d.ts +1 -1
- package/dist/components/embedded-chat/index.d.ts.map +1 -1
- package/dist/components/embedded-chat/index.js +1 -1
- package/dist/components/embedded-chat/index.js.map +1 -1
- package/dist/components/planning/ActioningView.d.ts.map +1 -1
- package/dist/components/planning/ActioningView.js +1 -1
- package/dist/components/planning/ActioningView.js.map +1 -1
- package/dist/components/planning/ExecutionTopBar.d.ts +1 -0
- package/dist/components/planning/ExecutionTopBar.d.ts.map +1 -1
- package/dist/components/planning/ExecutionTopBar.js +3 -2
- package/dist/components/planning/ExecutionTopBar.js.map +1 -1
- package/dist/components/planning/plan-detail-utils.d.ts +1 -0
- package/dist/components/planning/plan-detail-utils.d.ts.map +1 -1
- package/dist/components/planning/plan-detail-utils.js +7 -2
- package/dist/components/planning/plan-detail-utils.js.map +1 -1
- package/dist/components/tool-invocation-card/ToolOutputRenderer.d.ts.map +1 -1
- package/dist/components/tool-invocation-card/ToolOutputRenderer.js +5 -2
- package/dist/components/tool-invocation-card/ToolOutputRenderer.js.map +1 -1
- package/dist/graph/EntityCard.d.ts.map +1 -1
- package/dist/graph/EntityCard.js +2 -1
- package/dist/graph/EntityCard.js.map +1 -1
- package/dist/graph/EntityViewWithLinks.d.ts +7 -0
- package/dist/graph/EntityViewWithLinks.d.ts.map +1 -0
- package/dist/graph/EntityViewWithLinks.js +28 -0
- package/dist/graph/EntityViewWithLinks.js.map +1 -0
- package/dist/graph/card-links.d.ts +19 -0
- package/dist/graph/card-links.d.ts.map +1 -0
- package/dist/graph/card-links.js +23 -0
- package/dist/graph/card-links.js.map +1 -0
- package/dist/graph/nodes/BrandAssetAnalysisView.d.ts.map +1 -1
- package/dist/graph/nodes/BrandAssetAnalysisView.js +1 -1
- package/dist/graph/nodes/BrandAssetAnalysisView.js.map +1 -1
- package/dist/graph/nodes/BrandFontSuggestionView.d.ts.map +1 -1
- package/dist/graph/nodes/BrandFontSuggestionView.js +1 -1
- package/dist/graph/nodes/BrandFontSuggestionView.js.map +1 -1
- package/dist/graph/nodes/EntityCardShell.d.ts +2 -0
- package/dist/graph/nodes/EntityCardShell.d.ts.map +1 -1
- package/dist/graph/nodes/EntityCardShell.js +13 -3
- package/dist/graph/nodes/EntityCardShell.js.map +1 -1
- package/dist/graph/nodes/NodePropertyTable.d.ts +2 -1
- package/dist/graph/nodes/NodePropertyTable.d.ts.map +1 -1
- package/dist/graph/nodes/NodePropertyTable.js +10 -1
- package/dist/graph/nodes/NodePropertyTable.js.map +1 -1
- package/dist/graph/nodes/node-property-display.d.ts +0 -1
- package/dist/graph/nodes/node-property-display.d.ts.map +1 -1
- package/dist/graph/nodes/node-property-display.js +2 -6
- package/dist/graph/nodes/node-property-display.js.map +1 -1
- package/dist/graph/special-nodes/AttributeList.d.ts.map +1 -1
- package/dist/graph/special-nodes/AttributeList.js +3 -2
- package/dist/graph/special-nodes/AttributeList.js.map +1 -1
- package/dist/graph/special-nodes/RelatedSection.d.ts +6 -0
- package/dist/graph/special-nodes/RelatedSection.d.ts.map +1 -0
- package/dist/graph/special-nodes/RelatedSection.js +16 -0
- package/dist/graph/special-nodes/RelatedSection.js.map +1 -0
- package/dist/graph/special-nodes/card-facts.d.ts +2 -0
- package/dist/graph/special-nodes/card-facts.d.ts.map +1 -1
- package/dist/graph/special-nodes/card-facts.js +11 -0
- package/dist/graph/special-nodes/card-facts.js.map +1 -1
- package/dist/graph/types.d.ts +2 -2
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/hooks/use-approve-anyway.d.ts.map +1 -1
- package/dist/hooks/use-approve-anyway.js +8 -8
- package/dist/hooks/use-approve-anyway.js.map +1 -1
- package/dist/hooks/use-connection-problems.d.ts.map +1 -1
- package/dist/hooks/useValidateActionPlan.d.ts.map +1 -1
- package/dist/ldo-ui.d.ts +1 -0
- package/dist/ldo-ui.d.ts.map +1 -1
- package/dist/ldo-ui.js +1 -0
- package/dist/ldo-ui.js.map +1 -1
- package/dist/lib/chat-types.d.ts +17 -0
- package/dist/lib/chat-types.d.ts.map +1 -1
- package/dist/lib/chat-types.js.map +1 -1
- package/dist/providers/ChatManagerProvider.d.ts +7 -0
- package/dist/providers/ChatManagerProvider.d.ts.map +1 -1
- package/dist/providers/ChatManagerProvider.js +129 -16
- package/dist/providers/ChatManagerProvider.js.map +1 -1
- package/dist/providers/chat-connection-context.d.ts +4 -1
- package/dist/providers/chat-connection-context.d.ts.map +1 -1
- package/dist/providers/chat-connection-context.js.map +1 -1
- package/dist/providers/chat-connection-lifecycle.d.ts +1 -0
- package/dist/providers/chat-connection-lifecycle.d.ts.map +1 -1
- package/dist/providers/chat-connection-lifecycle.js.map +1 -1
- package/dist/providers/chat-socket-auth.d.ts +3 -1
- package/dist/providers/chat-socket-auth.d.ts.map +1 -1
- package/dist/providers/chat-socket-auth.js +27 -9
- package/dist/providers/chat-socket-auth.js.map +1 -1
- package/dist/providers/chat-startup-timing.d.ts +14 -0
- package/dist/providers/chat-startup-timing.d.ts.map +1 -0
- package/dist/providers/chat-startup-timing.js +72 -0
- package/dist/providers/chat-startup-timing.js.map +1 -0
- package/dist/routes/data-explorer/components/record-detail/ActionFormDialog.d.ts.map +1 -1
- package/dist/routes/data-explorer/components/record-detail/ActionFormDialog.js +7 -21
- package/dist/routes/data-explorer/components/record-detail/ActionFormDialog.js.map +1 -1
- package/dist/routes/data-explorer/components/record-detail/ChatPreview.d.ts.map +1 -1
- package/dist/routes/data-explorer/components/record-detail/ChatPreview.js +2 -2
- package/dist/routes/data-explorer/components/record-detail/ChatPreview.js.map +1 -1
- package/dist/routes/data-explorer/components/record-detail/action-properties.d.ts +2 -2
- package/dist/routes/data-explorer/components/record-detail/action-properties.d.ts.map +1 -1
- package/dist/routes/data-explorer/components/record-detail/action-properties.js +3 -3
- package/dist/routes/data-explorer/components/record-detail/action-properties.js.map +1 -1
- package/dist/routes/integrations/catalog-shape.d.ts +1 -0
- package/dist/routes/integrations/catalog-shape.d.ts.map +1 -1
- package/dist/routes/integrations/catalog-shape.js.map +1 -1
- package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.d.ts +7 -0
- package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.d.ts.map +1 -0
- package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.js +76 -0
- package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.js.map +1 -0
- package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.d.ts +7 -0
- package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.d.ts.map +1 -1
- package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.js +8 -7
- package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.js.map +1 -1
- package/dist/routes/integrations/components/connect-forms/inferal-locations.d.ts +3 -0
- package/dist/routes/integrations/components/connect-forms/inferal-locations.d.ts.map +1 -0
- package/dist/routes/integrations/components/connect-forms/inferal-locations.js +13 -0
- package/dist/routes/integrations/components/connect-forms/inferal-locations.js.map +1 -0
- package/dist/routes/integrations/connect-forms.d.ts +2 -0
- package/dist/routes/integrations/connect-forms.d.ts.map +1 -1
- package/dist/routes/integrations/connect-forms.js +2 -0
- package/dist/routes/integrations/connect-forms.js.map +1 -1
- package/dist/routes/integrations/connection-view.d.ts.map +1 -1
- package/dist/routes/integrations/connection-view.js +1 -0
- package/dist/routes/integrations/connection-view.js.map +1 -1
- package/dist/routes/plans/components/ActionPlansTableColumns.d.ts.map +1 -1
- package/dist/routes/plans/components/ActionPlansTableColumns.js +3 -3
- package/dist/routes/plans/components/ActionPlansTableColumns.js.map +1 -1
- package/package.json +7 -7
- package/src/components/chat-view/Chat.tsx +62 -15
- package/src/components/embedded-chat/index.tsx +9 -2
- package/src/components/planning/ActioningView.tsx +1 -0
- package/src/components/planning/ExecutionTopBar.tsx +14 -2
- package/src/components/planning/plan-detail-utils.tsx +8 -2
- package/src/components/tool-invocation-card/ToolOutputRenderer.tsx +5 -2
- package/src/graph/EntityCard.tsx +2 -1
- package/src/graph/EntityViewWithLinks.tsx +43 -0
- package/src/graph/card-links.tsx +43 -0
- package/src/graph/nodes/BrandAssetAnalysisView.tsx +1 -0
- package/src/graph/nodes/BrandFontSuggestionView.tsx +1 -0
- package/src/graph/nodes/EntityCardShell.tsx +37 -3
- package/src/graph/nodes/NodePropertyTable.tsx +16 -2
- package/src/graph/nodes/node-property-display.ts +2 -7
- package/src/graph/special-nodes/AttributeList.tsx +4 -2
- package/src/graph/special-nodes/RelatedSection.tsx +29 -0
- package/src/graph/special-nodes/card-facts.ts +13 -0
- package/src/graph/types.ts +1 -1
- package/src/hooks/use-approve-anyway.ts +8 -11
- package/src/ldo-ui.ts +1 -0
- package/src/lib/chat-types.ts +18 -0
- package/src/providers/ChatManagerProvider.tsx +166 -15
- package/src/providers/chat-connection-context.ts +4 -0
- package/src/providers/chat-connection-lifecycle.ts +1 -0
- package/src/providers/chat-socket-auth.ts +31 -12
- package/src/providers/chat-startup-timing.ts +89 -0
- package/src/routes/data-explorer/components/record-detail/ActionFormDialog.tsx +7 -29
- package/src/routes/data-explorer/components/record-detail/ChatPreview.tsx +6 -1
- package/src/routes/data-explorer/components/record-detail/action-properties.ts +3 -3
- package/src/routes/integrations/catalog-shape.ts +2 -0
- package/src/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.tsx +140 -0
- package/src/routes/integrations/components/connect-forms/OAuthSetupConnectForm.tsx +27 -5
- package/src/routes/integrations/components/connect-forms/inferal-locations.ts +13 -0
- package/src/routes/integrations/connect-forms.ts +2 -0
- package/src/routes/integrations/connection-view.ts +1 -0
- package/src/routes/plans/components/ActionPlansTableColumns.tsx +3 -2
|
@@ -3,35 +3,55 @@ import { ChatContextKind, type ChatContext } from "@manta-eu/client/shared/agent
|
|
|
3
3
|
import {
|
|
4
4
|
chatIdSchema,
|
|
5
5
|
chatManagerBasePath,
|
|
6
|
+
chatModeQuery,
|
|
6
7
|
type ChatManagerClient,
|
|
8
|
+
type ConversationMode,
|
|
7
9
|
type ConversationSummary,
|
|
8
10
|
type DeleteConversationResult,
|
|
9
11
|
type ListConversationsInput,
|
|
10
12
|
} from "@manta-eu/client/shared/chat-manager";
|
|
11
13
|
import { datastoreSocketQuery } from "@manta-eu/client/shared/datastore-path";
|
|
12
14
|
import { chatIdToPath, newChatPath, type ChatUIMessage } from "@manta-eu/client/shared/index";
|
|
15
|
+
import { DEFAULT_CALL_TIMEOUT_MS } from "agents/client";
|
|
13
16
|
import { useAgent } from "agents/react";
|
|
14
|
-
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
|
17
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
|
15
18
|
import { matchPath, useLocation, useMatch, useNavigate } from "react-router";
|
|
16
19
|
import { toast } from "sonner";
|
|
17
20
|
import { match } from "ts-pattern";
|
|
21
|
+
import type { ChatStartupReport } from "../lib/chat-types";
|
|
18
22
|
import { ChatManagerContext, type ChatManagerContextValue, type MessageHandoff } from "./chat-connection-context";
|
|
19
23
|
import {
|
|
20
24
|
createBlankConversationLifecycle,
|
|
21
25
|
mapManagerConnectionState,
|
|
22
26
|
type BlankConversationLifecycle,
|
|
23
27
|
} from "./chat-connection-lifecycle";
|
|
24
|
-
import { useChatSocketAuth } from "./chat-socket-auth";
|
|
28
|
+
import { isRequestedClose, useChatSocketAuth } from "./chat-socket-auth";
|
|
29
|
+
import {
|
|
30
|
+
chatStartupReport,
|
|
31
|
+
createChatStartupTimeline,
|
|
32
|
+
markChatStartup,
|
|
33
|
+
type ChatViewStartupPhase,
|
|
34
|
+
} from "./chat-startup-timing";
|
|
25
35
|
import { useMantaClient } from "./index";
|
|
26
36
|
|
|
27
37
|
export { useChatManagerConnection, type ChatManagerConnectionContextValue } from "./chat-connection-context";
|
|
28
38
|
|
|
39
|
+
export type { ChatStartupReport } from "../lib/chat-types";
|
|
40
|
+
|
|
41
|
+
export type { ChatViewStartupPhase } from "./chat-startup-timing";
|
|
42
|
+
|
|
29
43
|
export type ChatManagerProviderProps = {
|
|
30
44
|
onSelectPlan?: (planIri: string) => void;
|
|
31
45
|
datastoreId: string | undefined;
|
|
46
|
+
// Fixed for the provider's lifetime: it picks the manager, so key the provider on it.
|
|
47
|
+
mode?: ConversationMode;
|
|
48
|
+
surface?: "embedded" | "app";
|
|
49
|
+
onStartupTiming?: (report: ChatStartupReport) => void;
|
|
32
50
|
children: ReactNode;
|
|
33
51
|
};
|
|
34
52
|
|
|
53
|
+
let chatStartupReports = 0;
|
|
54
|
+
|
|
35
55
|
type ContextTransition =
|
|
36
56
|
| { kind: "select"; context: ChatContext }
|
|
37
57
|
| { kind: "start"; context: ChatContext; message: ChatUIMessage }
|
|
@@ -45,6 +65,20 @@ type ExpectedConversationClose = {
|
|
|
45
65
|
conversationId: string;
|
|
46
66
|
};
|
|
47
67
|
|
|
68
|
+
type ManagerOpenWaiter = {
|
|
69
|
+
resolve: () => void;
|
|
70
|
+
reject: (error: Error) => void;
|
|
71
|
+
timer: ReturnType<typeof setTimeout>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function settleManagerOpenWaiters(waiters: Set<ManagerOpenWaiter>, settle: (waiter: ManagerOpenWaiter) => void) {
|
|
75
|
+
for (const waiter of waiters) {
|
|
76
|
+
clearTimeout(waiter.timer);
|
|
77
|
+
settle(waiter);
|
|
78
|
+
}
|
|
79
|
+
waiters.clear();
|
|
80
|
+
}
|
|
81
|
+
|
|
48
82
|
function activeConversationFromSelection(selection: ConversationSelection | undefined) {
|
|
49
83
|
return selection?.kind === "active" ? selection.conversation : undefined;
|
|
50
84
|
}
|
|
@@ -57,9 +91,13 @@ function clearExpectedConversationClose(
|
|
|
57
91
|
}
|
|
58
92
|
|
|
59
93
|
export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
60
|
-
const { onSelectPlan, datastoreId, children } = props;
|
|
94
|
+
const { onSelectPlan, datastoreId, surface = "app", onStartupTiming, children } = props;
|
|
95
|
+
const [mode] = useState<ConversationMode>(props.mode ?? "datastore");
|
|
61
96
|
const { mantaBaseUrl } = useMantaClient();
|
|
62
|
-
const datastoreQuery = useMemo(
|
|
97
|
+
const datastoreQuery = useMemo(
|
|
98
|
+
() => ({ ...datastoreSocketQuery(datastoreId), ...chatModeQuery(mode) }),
|
|
99
|
+
[datastoreId, mode],
|
|
100
|
+
);
|
|
63
101
|
const navigate = useNavigate();
|
|
64
102
|
const location = useLocation();
|
|
65
103
|
const chatMatch = useMatch("/chat/:chatId");
|
|
@@ -91,7 +129,35 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
91
129
|
pathnameRef.current = location.pathname;
|
|
92
130
|
routedConversationIdRef.current = routedConversationId;
|
|
93
131
|
|
|
94
|
-
const
|
|
132
|
+
const [timeline] = useState(createChatStartupTimeline);
|
|
133
|
+
const onStartupTimingRef = useRef(onStartupTiming);
|
|
134
|
+
onStartupTimingRef.current = onStartupTiming;
|
|
135
|
+
const socketAuth = useChatSocketAuth(datastoreQuery, (phase) => markChatStartup(timeline, phase));
|
|
136
|
+
const authMode = "enabled" in socketAuth ? "ticket" : "cookie";
|
|
137
|
+
const socketKey = "enabled" in socketAuth && !socketAuth.enabled ? undefined : JSON.stringify(socketAuth.query);
|
|
138
|
+
const enabledSocketKeyRef = useRef<string>(undefined);
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (socketKey === undefined || enabledSocketKeyRef.current === socketKey) return;
|
|
141
|
+
enabledSocketKeyRef.current = socketKey;
|
|
142
|
+
markChatStartup(timeline, "socket-enabled");
|
|
143
|
+
}, [socketKey, timeline]);
|
|
144
|
+
|
|
145
|
+
const reportedStartupRef = useRef(false);
|
|
146
|
+
const reportStartup = useCallback(() => {
|
|
147
|
+
if (reportedStartupRef.current) return;
|
|
148
|
+
const report = chatStartupReport(timeline, { surface, mode: authMode, openIndex: chatStartupReports + 1 });
|
|
149
|
+
if (!report) return;
|
|
150
|
+
reportedStartupRef.current = true;
|
|
151
|
+
chatStartupReports += 1;
|
|
152
|
+
try {
|
|
153
|
+
onStartupTimingRef.current?.(report);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error("onStartupTiming threw", error);
|
|
156
|
+
}
|
|
157
|
+
}, [surface, authMode, timeline]);
|
|
158
|
+
|
|
159
|
+
const managerOpenWaitersRef = useRef(new Set<ManagerOpenWaiter>());
|
|
160
|
+
|
|
95
161
|
const managerAgent = useAgent<ChatManagerClient, ChatManagerClient["state"]>({
|
|
96
162
|
agent: "ChatManager",
|
|
97
163
|
basePath: chatManagerBasePath,
|
|
@@ -100,8 +166,56 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
100
166
|
...("enabled" in socketAuth
|
|
101
167
|
? { enabled: socketAuth.enabled, shouldReconnectOnClose: socketAuth.shouldReconnectOnClose }
|
|
102
168
|
: {}),
|
|
169
|
+
onOpen: () => {
|
|
170
|
+
markChatStartup(timeline, "socket-open");
|
|
171
|
+
reportStartup();
|
|
172
|
+
settleManagerOpenWaiters(managerOpenWaitersRef.current, (waiter) => waiter.resolve());
|
|
173
|
+
},
|
|
174
|
+
onClose: (event) => {
|
|
175
|
+
if (authMode === "cookie" && !isRequestedClose(event) && timeline.marks["socket-open"] === undefined) {
|
|
176
|
+
timeline.socketAttempts += 1;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
103
179
|
});
|
|
104
180
|
|
|
181
|
+
const managerAgentRef = useRef(managerAgent);
|
|
182
|
+
managerAgentRef.current = managerAgent;
|
|
183
|
+
|
|
184
|
+
const disposedRef = useRef(false);
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
disposedRef.current = false;
|
|
187
|
+
return () => {
|
|
188
|
+
disposedRef.current = true;
|
|
189
|
+
};
|
|
190
|
+
}, []);
|
|
191
|
+
|
|
192
|
+
const waitForManagerOpen = useCallback((): Promise<void> => {
|
|
193
|
+
const agent = managerAgentRef.current;
|
|
194
|
+
if (agent.readyState === WebSocket.OPEN) return Promise.resolve();
|
|
195
|
+
if (agent.connectionError !== null && agent.readyState === WebSocket.CLOSED) {
|
|
196
|
+
return Promise.reject(new Error("The chat connection closed."));
|
|
197
|
+
}
|
|
198
|
+
return new Promise((resolve, reject) => {
|
|
199
|
+
const waiters = managerOpenWaitersRef.current;
|
|
200
|
+
const waiter: ManagerOpenWaiter = {
|
|
201
|
+
resolve,
|
|
202
|
+
reject,
|
|
203
|
+
timer: setTimeout(() => {
|
|
204
|
+
waiters.delete(waiter);
|
|
205
|
+
if (!disposedRef.current) reject(new Error("The chat connection did not open."));
|
|
206
|
+
}, DEFAULT_CALL_TIMEOUT_MS),
|
|
207
|
+
};
|
|
208
|
+
waiters.add(waiter);
|
|
209
|
+
});
|
|
210
|
+
}, []);
|
|
211
|
+
|
|
212
|
+
const markStartup = useCallback(
|
|
213
|
+
(phase: ChatViewStartupPhase) => {
|
|
214
|
+
if (markChatStartup(timeline, phase) && phase === "interactive") reportStartup();
|
|
215
|
+
},
|
|
216
|
+
[reportStartup, timeline],
|
|
217
|
+
);
|
|
218
|
+
|
|
105
219
|
const listConversations = useCallback(
|
|
106
220
|
(input: ListConversationsInput = {}): Promise<ConversationSummary[]> => managerAgent.stub.listConversations(input),
|
|
107
221
|
[managerAgent],
|
|
@@ -166,10 +280,14 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
166
280
|
|
|
167
281
|
const sendBlankConversationMessage = useCallback(
|
|
168
282
|
(message: ChatUIMessage): Promise<void> => {
|
|
169
|
-
|
|
170
|
-
if (lifecycle.
|
|
283
|
+
let lifecycle = blankConversationRef.current;
|
|
284
|
+
if (lifecycle.conversationId || lifecycle.creating) {
|
|
171
285
|
return Promise.reject(new Error("A new chat is already starting."));
|
|
172
286
|
}
|
|
287
|
+
if (lifecycle.materialization) {
|
|
288
|
+
lifecycle = createBlankConversationLifecycle();
|
|
289
|
+
blankConversationRef.current = lifecycle;
|
|
290
|
+
}
|
|
173
291
|
|
|
174
292
|
setMessageHandoff({ message });
|
|
175
293
|
function clearMessageHandoff() {
|
|
@@ -187,13 +305,23 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
187
305
|
}
|
|
188
306
|
const materialization = (async () => {
|
|
189
307
|
try {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
308
|
+
const deadline = performance.now() + DEFAULT_CALL_TIMEOUT_MS;
|
|
309
|
+
await waitForManagerOpen();
|
|
310
|
+
if (!isCurrentLifecycle()) {
|
|
311
|
+
clearMessageHandoff();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
lifecycle.creating = true;
|
|
316
|
+
const conversation = await managerAgentRef.current.call(
|
|
317
|
+
"createConversation",
|
|
318
|
+
[{ idempotencyKey: lifecycle.idempotencyKey }],
|
|
319
|
+
{ timeout: Math.max(deadline - performance.now(), 1) },
|
|
320
|
+
);
|
|
193
321
|
|
|
194
322
|
if (!isCurrentLifecycle()) {
|
|
195
323
|
clearMessageHandoff();
|
|
196
|
-
await
|
|
324
|
+
await managerAgentRef.current.stub.deleteConversation({ id: conversation.id });
|
|
197
325
|
return;
|
|
198
326
|
}
|
|
199
327
|
|
|
@@ -213,13 +341,14 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
213
341
|
throw error;
|
|
214
342
|
} finally {
|
|
215
343
|
lifecycle.materialization = undefined;
|
|
344
|
+
lifecycle.creating = false;
|
|
216
345
|
}
|
|
217
346
|
})();
|
|
218
347
|
|
|
219
348
|
lifecycle.materialization = materialization;
|
|
220
349
|
return materialization;
|
|
221
350
|
},
|
|
222
|
-
[
|
|
351
|
+
[navigate, waitForManagerOpen],
|
|
223
352
|
);
|
|
224
353
|
|
|
225
354
|
const startNewChat = useCallback(() => {
|
|
@@ -234,8 +363,13 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
234
363
|
}
|
|
235
364
|
}, [onNewChatPath, navigate]);
|
|
236
365
|
|
|
366
|
+
const handledPathnameRef = useRef(location.pathname);
|
|
367
|
+
|
|
237
368
|
// `/` is still a blank chat in an app that gives its datastores no home page.
|
|
238
|
-
|
|
369
|
+
useLayoutEffect(() => {
|
|
370
|
+
if (handledPathnameRef.current === location.pathname) return;
|
|
371
|
+
handledPathnameRef.current = location.pathname;
|
|
372
|
+
|
|
239
373
|
if (onNewChatPath || location.pathname === "/") {
|
|
240
374
|
const preserveBlankLifecycle = preserveBlankLifecycleOnNextBlankChatRef.current;
|
|
241
375
|
if (!preserveBlankLifecycle) {
|
|
@@ -247,6 +381,7 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
247
381
|
setConversationSelection(undefined);
|
|
248
382
|
} else {
|
|
249
383
|
preserveBlankLifecycleOnNextBlankChatRef.current = false;
|
|
384
|
+
setMessageHandoff((current) => (current?.conversationId ? current : undefined));
|
|
250
385
|
}
|
|
251
386
|
}, [location.pathname, onNewChatPath]);
|
|
252
387
|
|
|
@@ -394,6 +529,10 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
394
529
|
onSelectPlan(planIri);
|
|
395
530
|
return;
|
|
396
531
|
}
|
|
532
|
+
if (mode === "group") {
|
|
533
|
+
toast.info("Open this plan from its own datastore.");
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
397
536
|
|
|
398
537
|
const context = { kind: ChatContextKind.Plan, planGraphIri: planIri } as const;
|
|
399
538
|
const source = activeConversationFromSelection(conversationSelectionRef.current);
|
|
@@ -412,12 +551,20 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
412
551
|
if (pendingPlanNavigationRef.current === navigation) pendingPlanNavigationRef.current = undefined;
|
|
413
552
|
}
|
|
414
553
|
},
|
|
415
|
-
[navigate, onSelectPlan, reassignCurrentConversation],
|
|
554
|
+
[mode, navigate, onSelectPlan, reassignCurrentConversation],
|
|
416
555
|
);
|
|
417
556
|
|
|
418
557
|
const managerConnectionState = mapManagerConnectionState(managerAgent);
|
|
558
|
+
|
|
559
|
+
useEffect(() => {
|
|
560
|
+
if (managerAgent.connectionError === null) return;
|
|
561
|
+
const error = new Error("The chat connection closed.");
|
|
562
|
+
settleManagerOpenWaiters(managerOpenWaitersRef.current, (waiter) => waiter.reject(error));
|
|
563
|
+
}, [managerAgent.connectionError]);
|
|
564
|
+
|
|
419
565
|
const selectedContext = activeConversation?.context ?? pendingContext;
|
|
420
|
-
const isConversationLoading =
|
|
566
|
+
const isConversationLoading =
|
|
567
|
+
invalidConversationRoute || (routedConversationId !== undefined && activeConversation?.id !== routedConversationId);
|
|
421
568
|
const context = useMemo<ChatManagerContextValue>(
|
|
422
569
|
() => ({
|
|
423
570
|
managerConnection: {
|
|
@@ -428,6 +575,7 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
428
575
|
pendingMessage: messageHandoff?.message,
|
|
429
576
|
isConversationLoading,
|
|
430
577
|
connectionState: managerConnectionState,
|
|
578
|
+
mode,
|
|
431
579
|
listConversations,
|
|
432
580
|
deleteConversation,
|
|
433
581
|
openConversation,
|
|
@@ -437,6 +585,7 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
437
585
|
startContextConversation,
|
|
438
586
|
reassignCurrentConversation,
|
|
439
587
|
handleSelectPlan,
|
|
588
|
+
markStartup,
|
|
440
589
|
},
|
|
441
590
|
session: {
|
|
442
591
|
activeConversationId: activeConversation?.id,
|
|
@@ -451,11 +600,13 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
451
600
|
[
|
|
452
601
|
activeConversation,
|
|
453
602
|
deleteConversation,
|
|
603
|
+
mode,
|
|
454
604
|
handleSelectPlan,
|
|
455
605
|
isConversationLoading,
|
|
456
606
|
listConversations,
|
|
457
607
|
managerAgent,
|
|
458
608
|
managerAgent.state,
|
|
609
|
+
markStartup,
|
|
459
610
|
managerConnectionState,
|
|
460
611
|
mantaBaseUrl.host,
|
|
461
612
|
datastoreQuery,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ChatContext, SendAgentMessage } from "@manta-eu/client/shared/agent-types";
|
|
2
2
|
import type {
|
|
3
3
|
ChatManagerState,
|
|
4
|
+
ConversationMode,
|
|
4
5
|
ConversationSummary,
|
|
5
6
|
DeleteConversationResult,
|
|
6
7
|
ListConversationsInput,
|
|
@@ -8,6 +9,7 @@ import type {
|
|
|
8
9
|
import type { ChatUIMessage } from "@manta-eu/client/shared/index";
|
|
9
10
|
import { createContext, useContext } from "react";
|
|
10
11
|
import type { ConnectionState } from "../lib/chat-types";
|
|
12
|
+
import type { ChatViewStartupPhase } from "./chat-startup-timing";
|
|
11
13
|
|
|
12
14
|
export type ChatManagerConnectionContextValue = {
|
|
13
15
|
agent: { readyState: number; reconnect: () => void };
|
|
@@ -17,6 +19,7 @@ export type ChatManagerConnectionContextValue = {
|
|
|
17
19
|
pendingMessage?: ChatUIMessage;
|
|
18
20
|
isConversationLoading: boolean;
|
|
19
21
|
connectionState: ConnectionState;
|
|
22
|
+
mode: ConversationMode;
|
|
20
23
|
listConversations(input?: ListConversationsInput): Promise<ConversationSummary[]>;
|
|
21
24
|
deleteConversation(id: string): Promise<DeleteConversationResult>;
|
|
22
25
|
openConversation(id: string): void;
|
|
@@ -26,6 +29,7 @@ export type ChatManagerConnectionContextValue = {
|
|
|
26
29
|
startContextConversation: (context: ChatContext, message: ChatUIMessage) => Promise<ConversationSummary | undefined>;
|
|
27
30
|
reassignCurrentConversation: (context: ChatContext) => Promise<ConversationSummary | undefined>;
|
|
28
31
|
handleSelectPlan: (planIri: string) => Promise<void>;
|
|
32
|
+
markStartup: (phase: ChatViewStartupPhase) => void;
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
export type MessageHandoff = {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bearerFetch } from "@manta-eu/client";
|
|
2
|
+
import { chatModeQueryParam, chatTicketPath, chatTicketQueryParam } from "@manta-eu/client/shared/chat-manager";
|
|
2
3
|
import { datastoreHeader, datastoreQueryParam } from "@manta-eu/client/shared/datastore-path";
|
|
3
4
|
import { isPresent } from "@manta-eu/lib/present";
|
|
4
5
|
import { isTerminalCloseEvent } from "agents/client";
|
|
5
6
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
-
import {
|
|
7
|
+
import type { ChatSocketAuthPhase } from "./chat-startup-timing";
|
|
7
8
|
import { useMantaClient } from "./MantaClientProvider";
|
|
8
9
|
|
|
9
10
|
const tokenRefreshCheckMs = 30_000;
|
|
@@ -30,7 +31,7 @@ function ticketFrom(body: unknown): string | undefined {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
// partysocket clears shouldReconnect before a close it was asked for: an unmount, or a socket being replaced.
|
|
33
|
-
function isRequestedClose(event: CloseEvent): boolean {
|
|
34
|
+
export function isRequestedClose(event: CloseEvent): boolean {
|
|
34
35
|
const target: unknown = event.target;
|
|
35
36
|
return typeof target === "object" && target !== null && "shouldReconnect" in target && !target.shouldReconnect;
|
|
36
37
|
}
|
|
@@ -39,12 +40,16 @@ function isRequestedClose(event: CloseEvent): boolean {
|
|
|
39
40
|
* A ticket is spent on its first connect, and partysocket would reconnect to the same URL, so the socket never
|
|
40
41
|
* reconnects by itself: each connect waits for a new ticket, and a dropped socket asks for one.
|
|
41
42
|
*/
|
|
42
|
-
export function useChatSocketAuth(
|
|
43
|
+
export function useChatSocketAuth(
|
|
44
|
+
datastoreQuery: Record<string, string>,
|
|
45
|
+
onPhase?: (phase: ChatSocketAuthPhase) => void,
|
|
46
|
+
): ChatSocketAuth {
|
|
43
47
|
const client = useMantaClient();
|
|
44
48
|
const { getAccessToken, mantaBaseUrl } = client;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
const onPhaseRef = useRef(onPhase);
|
|
50
|
+
onPhaseRef.current = onPhase;
|
|
51
|
+
const getAccessTokenRef = useRef(getAccessToken);
|
|
52
|
+
getAccessTokenRef.current = getAccessToken;
|
|
48
53
|
const hasAccessToken = getAccessToken !== undefined;
|
|
49
54
|
const datastoreKey = JSON.stringify(datastoreQuery);
|
|
50
55
|
const datastoreQueryRef = useRef(datastoreQuery);
|
|
@@ -70,11 +75,24 @@ export function useChatSocketAuth(datastoreQuery: Record<string, string>): ChatS
|
|
|
70
75
|
const delay = reconnectDelaysMs[Math.min(Math.max(drops.current.count - 1, 0), reconnectDelaysMs.length - 1)];
|
|
71
76
|
const timer = setTimeout(() => {
|
|
72
77
|
const query = datastoreQueryRef.current;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
const getToken = getAccessTokenRef.current;
|
|
79
|
+
if (!getToken) return;
|
|
80
|
+
let sends = 0;
|
|
81
|
+
const requestTicket = bearerFetch(getToken, async (input, init) => {
|
|
82
|
+
sends += 1;
|
|
83
|
+
onPhaseRef.current?.("ticket-requested");
|
|
84
|
+
const response = await fetch(input, init);
|
|
85
|
+
if (response.status === 401 && sends === 1) onPhaseRef.current?.("token-requested");
|
|
86
|
+
return response;
|
|
87
|
+
});
|
|
88
|
+
const ticketUrl = new URL(`/${chatTicketPath}`, mantaBaseUrl);
|
|
89
|
+
const mode = query[chatModeQueryParam];
|
|
90
|
+
if (isPresent(mode)) ticketUrl.searchParams.set(chatModeQueryParam, mode);
|
|
91
|
+
onPhaseRef.current?.("token-requested");
|
|
92
|
+
requestTicket(ticketUrl, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: datastoreHeader(query[datastoreQueryParam]),
|
|
95
|
+
})
|
|
78
96
|
.then(async (response) => {
|
|
79
97
|
if (response.status >= 400 && response.status < 500 && !retriedRefusals.has(response.status)) {
|
|
80
98
|
if (!cancelled) console.warn(`Manta refused a chat ticket (${response.status}); not asking again`);
|
|
@@ -84,6 +102,7 @@ export function useChatSocketAuth(datastoreQuery: Record<string, string>): ChatS
|
|
|
84
102
|
if (!isPresent(ticket)) throw new Error(`Manta refused a chat ticket (${response.status})`);
|
|
85
103
|
if (cancelled) return;
|
|
86
104
|
ticketedAt.current = Date.now();
|
|
105
|
+
onPhaseRef.current?.("ticket-received");
|
|
87
106
|
setTicketed({ datastoreKey, query: { ...query, [chatTicketQueryParam]: ticket } });
|
|
88
107
|
})
|
|
89
108
|
.catch((error: unknown) => {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ChatStartupReport } from "../lib/chat-types";
|
|
2
|
+
|
|
3
|
+
export type ChatSocketAuthPhase = "token-requested" | "ticket-requested" | "ticket-received";
|
|
4
|
+
|
|
5
|
+
export type ChatViewStartupPhase = "chat-mounted" | "first-paint" | "interactive";
|
|
6
|
+
|
|
7
|
+
export type ChatStartupPhase = ChatSocketAuthPhase | "socket-enabled" | "socket-open" | ChatViewStartupPhase;
|
|
8
|
+
|
|
9
|
+
export type ChatStartupTimeline = {
|
|
10
|
+
startedAt: number;
|
|
11
|
+
marks: Partial<Record<ChatStartupPhase, number>>;
|
|
12
|
+
ticketAttempts: number;
|
|
13
|
+
socketAttempts: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function createChatStartupTimeline(): ChatStartupTimeline {
|
|
17
|
+
return { startedAt: performance.now(), marks: {}, ticketAttempts: 0, socketAttempts: 0 };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const attemptEnds: Partial<Record<ChatStartupPhase, ChatStartupPhase>> = {
|
|
21
|
+
"token-requested": "socket-open",
|
|
22
|
+
"ticket-requested": "socket-open",
|
|
23
|
+
"ticket-received": "socket-open",
|
|
24
|
+
"socket-enabled": "socket-open",
|
|
25
|
+
"chat-mounted": "interactive",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const measures: Partial<Record<ChatStartupPhase, { name: string; from: ChatStartupPhase | "start" }>> = {
|
|
29
|
+
"ticket-requested": { name: "host-token", from: "token-requested" },
|
|
30
|
+
"ticket-received": { name: "ticket", from: "ticket-requested" },
|
|
31
|
+
"socket-open": { name: "socket", from: "socket-enabled" },
|
|
32
|
+
"chat-mounted": { name: "until-chat-mount", from: "start" },
|
|
33
|
+
"first-paint": { name: "until-first-paint", from: "chat-mounted" },
|
|
34
|
+
interactive: { name: "until-interactive", from: "chat-mounted" },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function markChatStartup(timeline: ChatStartupTimeline, phase: ChatStartupPhase): boolean {
|
|
38
|
+
const attemptEnd = attemptEnds[phase];
|
|
39
|
+
if (timeline.marks[attemptEnd ?? phase] !== undefined) return false;
|
|
40
|
+
const now = performance.now();
|
|
41
|
+
timeline.marks[phase] = now - timeline.startedAt;
|
|
42
|
+
if (phase === "chat-mounted") delete timeline.marks["first-paint"];
|
|
43
|
+
if (phase === "token-requested") timeline.ticketAttempts += 1;
|
|
44
|
+
if (phase === "socket-enabled") timeline.socketAttempts += 1;
|
|
45
|
+
const measure = measures[phase];
|
|
46
|
+
const from = measure && (measure.from === "start" ? 0 : timeline.marks[measure.from]);
|
|
47
|
+
try {
|
|
48
|
+
performance.mark(`manta-chat:${phase}`);
|
|
49
|
+
if (measure && from !== undefined) {
|
|
50
|
+
performance.measure(`manta-chat:${measure.name}`, { start: timeline.startedAt + from, end: now });
|
|
51
|
+
}
|
|
52
|
+
} catch {}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function elapsed(from: number, to: number): number {
|
|
57
|
+
return Math.max(0, Math.round(to - from));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function chatStartupReport(
|
|
61
|
+
timeline: ChatStartupTimeline,
|
|
62
|
+
input: Pick<ChatStartupReport, "surface" | "mode" | "openIndex">,
|
|
63
|
+
): ChatStartupReport | undefined {
|
|
64
|
+
const { marks } = timeline;
|
|
65
|
+
const socketOpen = marks["socket-open"];
|
|
66
|
+
const chatMounted = marks["chat-mounted"];
|
|
67
|
+
const firstPaint = marks["first-paint"];
|
|
68
|
+
const interactive = marks.interactive;
|
|
69
|
+
if (socketOpen === undefined || chatMounted === undefined || firstPaint === undefined || interactive === undefined) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const tokenRequested = marks["token-requested"];
|
|
73
|
+
const ticketRequested = marks["ticket-requested"];
|
|
74
|
+
const ticketReceived = marks["ticket-received"];
|
|
75
|
+
const ticketed = input.mode === "ticket" && ticketRequested !== undefined && ticketReceived !== undefined;
|
|
76
|
+
return {
|
|
77
|
+
...input,
|
|
78
|
+
...(ticketed && tokenRequested !== undefined ? { hostTokenMs: elapsed(tokenRequested, ticketRequested) } : {}),
|
|
79
|
+
...(ticketed
|
|
80
|
+
? { ticketMs: elapsed(ticketRequested, ticketReceived), ticketAttempts: Math.max(1, timeline.ticketAttempts) }
|
|
81
|
+
: {}),
|
|
82
|
+
socketMs: elapsed(marks["socket-enabled"] ?? 0, socketOpen),
|
|
83
|
+
socketAttempts: Math.max(1, timeline.socketAttempts),
|
|
84
|
+
chatMountMs: elapsed(0, chatMounted),
|
|
85
|
+
socketReadyAtOpen: socketOpen <= chatMounted,
|
|
86
|
+
firstPaintMs: elapsed(chatMounted, firstPaint),
|
|
87
|
+
interactiveMs: elapsed(chatMounted, interactive),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -59,7 +59,6 @@ export function ActionFormDialog(props: ActionFormDialogProps) {
|
|
|
59
59
|
const baselineRef = useRef<PropertyMap | null>(null);
|
|
60
60
|
const [refusal, setRefusal] = useState<string | null>(null);
|
|
61
61
|
const [formElement, setFormElement] = useState<ShaclFormElement | null>(null);
|
|
62
|
-
const [isReadingForm, setIsReadingForm] = useState(false);
|
|
63
62
|
|
|
64
63
|
const attachForm = useCallback((element: ShaclFormElement | null) => setFormElement(element), []);
|
|
65
64
|
|
|
@@ -77,29 +76,20 @@ export function ActionFormDialog(props: ActionFormDialogProps) {
|
|
|
77
76
|
useEffect(() => {
|
|
78
77
|
if (!formElement || !isUpdate) return;
|
|
79
78
|
|
|
80
|
-
let cancelled = false;
|
|
81
|
-
|
|
82
79
|
// Baseline is the form's own serialization: its editors normalize literals, the triples do not.
|
|
83
|
-
|
|
80
|
+
function onReady() {
|
|
84
81
|
const store = formElement?.toRDF();
|
|
85
|
-
const baseline = store ?
|
|
86
|
-
|
|
87
|
-
if (baseline && !cancelled) baselineRef.current = baseline;
|
|
88
|
-
}
|
|
82
|
+
const baseline = store ? baselineFromStore(store, subjectIri) : null;
|
|
89
83
|
|
|
90
|
-
|
|
91
|
-
void captureBaseline();
|
|
84
|
+
if (baseline) baselineRef.current = baseline;
|
|
92
85
|
}
|
|
93
86
|
|
|
94
87
|
onReady();
|
|
95
88
|
formElement.addEventListener("ready", onReady);
|
|
96
|
-
return () =>
|
|
97
|
-
cancelled = true;
|
|
98
|
-
formElement.removeEventListener("ready", onReady);
|
|
99
|
-
};
|
|
89
|
+
return () => formElement.removeEventListener("ready", onReady);
|
|
100
90
|
}, [formElement, isUpdate, subjectIri]);
|
|
101
91
|
|
|
102
|
-
|
|
92
|
+
function submit() {
|
|
103
93
|
const store = formElement?.toRDF();
|
|
104
94
|
const baseline = baselineRef.current;
|
|
105
95
|
|
|
@@ -108,14 +98,7 @@ export function ActionFormDialog(props: ActionFormDialogProps) {
|
|
|
108
98
|
return;
|
|
109
99
|
}
|
|
110
100
|
|
|
111
|
-
|
|
112
|
-
const edited = await propertiesFromStore(store, subjectIri).catch(() => null);
|
|
113
|
-
setIsReadingForm(false);
|
|
114
|
-
|
|
115
|
-
if (!edited) {
|
|
116
|
-
setRefusal("The form could not be read.");
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
101
|
+
const edited = propertiesFromStore(store, subjectIri);
|
|
119
102
|
|
|
120
103
|
if (isUpdate && baseline) {
|
|
121
104
|
const cleared = removedPredicates(baseline, edited);
|
|
@@ -176,12 +159,7 @@ export function ActionFormDialog(props: ActionFormDialogProps) {
|
|
|
176
159
|
<Button variant="tertiary" size="sm" onClick={() => onOpenChange(false)}>
|
|
177
160
|
Cancel
|
|
178
161
|
</Button>
|
|
179
|
-
<Button
|
|
180
|
-
size="sm"
|
|
181
|
-
onClick={() => void submit()}
|
|
182
|
-
isLoading={isPending || isReadingForm}
|
|
183
|
-
disabled={!shapesTurtle}
|
|
184
|
-
>
|
|
162
|
+
<Button size="sm" onClick={submit} isLoading={isPending} disabled={!shapesTurtle}>
|
|
185
163
|
{copy.submitLabel}
|
|
186
164
|
</Button>
|
|
187
165
|
</DialogFooter>
|
|
@@ -7,6 +7,7 @@ import { actionShapesForTypes } from "./action-shapes";
|
|
|
7
7
|
import { Badge } from "#components/badge/index";
|
|
8
8
|
import {
|
|
9
9
|
type ActionShapeSpec,
|
|
10
|
+
EntityViewWithLinks,
|
|
10
11
|
findEntityNode,
|
|
11
12
|
GenericView,
|
|
12
13
|
getEntityView,
|
|
@@ -97,7 +98,11 @@ export function ChatPreview(props: ChatPreviewProps) {
|
|
|
97
98
|
<section>
|
|
98
99
|
<h3 className={cn(sectionLabelClass, "mb-1.5 text-muted-foreground")}>Chat rendering</h3>
|
|
99
100
|
<div className="max-w-md">
|
|
100
|
-
{EntityView ?
|
|
101
|
+
{EntityView ? (
|
|
102
|
+
<EntityViewWithLinks view={EntityView} data={node.data} />
|
|
103
|
+
) : (
|
|
104
|
+
<GenericView data={node.data} defaultExpanded />
|
|
105
|
+
)}
|
|
101
106
|
</div>
|
|
102
107
|
</section>
|
|
103
108
|
|
|
@@ -7,14 +7,14 @@ export type PropertyMap = Record<string, unknown>;
|
|
|
7
7
|
// The form stamps this on every serialization
|
|
8
8
|
const conformsTo = `${namespaces.dcterms}conformsTo`;
|
|
9
9
|
|
|
10
|
-
export
|
|
10
|
+
export function propertiesFromStore(store: ShaclFormStore, subjectIri: string): PropertyMap {
|
|
11
11
|
const quads = [...store].filter((quad) => quad.predicate.value !== conformsTo);
|
|
12
|
-
const { "@id": _id, "@type": _type, ...properties } =
|
|
12
|
+
const { "@id": _id, "@type": _type, ...properties } = documentFromQuads(quads, subjectIri);
|
|
13
13
|
|
|
14
14
|
return properties;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export
|
|
17
|
+
export function baselineFromStore(store: ShaclFormStore, subjectIri: string): PropertyMap | null {
|
|
18
18
|
if (store.size === 0) return null;
|
|
19
19
|
|
|
20
20
|
return propertiesFromStore(store, subjectIri);
|
|
@@ -22,6 +22,8 @@ export type OAuthConnectionMethod = ConnectionMethodBase & {
|
|
|
22
22
|
source: "oauth";
|
|
23
23
|
integration: GQLIntegration;
|
|
24
24
|
callbackProvider?: string;
|
|
25
|
+
// Further oauth_provider values whose callback resumes this same method, e.g. a second identity provider.
|
|
26
|
+
alternateCallbackProviders?: readonly string[];
|
|
25
27
|
resumesSetupAfterOAuth?: boolean;
|
|
26
28
|
connectedAction?: ComponentType;
|
|
27
29
|
};
|