@manta-eu/react 0.4.0 → 0.5.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.
- package/README.md +1 -0
- package/dist/components/chat-view/Chat.d.ts +27 -1
- package/dist/components/chat-view/Chat.d.ts.map +1 -1
- package/dist/components/chat-view/Chat.js +76 -18
- 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/layouts/Sidebar.d.ts.map +1 -1
- package/dist/components/layouts/Sidebar.js +4 -2
- package/dist/components/layouts/Sidebar.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 +5 -2
- package/dist/components/planning/plan-detail-utils.js.map +1 -1
- package/dist/components/sidebar/nav-user.js +1 -1
- package/dist/components/sidebar/nav-user.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/explain-fact/rule-presentation.d.ts.map +1 -1
- package/dist/explain-fact/rule-presentation.js +1 -6
- package/dist/explain-fact/rule-presentation.js.map +1 -1
- package/dist/explain-fact/tree-model.d.ts +2 -1
- package/dist/explain-fact/tree-model.d.ts.map +1 -1
- package/dist/explain-fact/tree-model.js +17 -2
- package/dist/explain-fact/tree-model.js.map +1 -1
- package/dist/hooks/use-connection-problems.d.ts.map +1 -1
- package/dist/hooks/use-manual-actions.js +1 -1
- package/dist/hooks/use-manual-actions.js.map +1 -1
- package/dist/hooks/useAuth.d.ts +1 -0
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +2 -1
- package/dist/hooks/useAuth.js.map +1 -1
- package/dist/hooks/useValidateActionPlan.d.ts.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/lib/datastore-route-table.d.ts +1 -0
- package/dist/lib/datastore-route-table.d.ts.map +1 -1
- package/dist/lib/datastore-route-table.js +1 -0
- package/dist/lib/datastore-route-table.js.map +1 -1
- package/dist/providers/AuthContext.d.ts +1 -0
- package/dist/providers/AuthContext.d.ts.map +1 -1
- package/dist/providers/AuthContext.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/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 +122 -20
- package/src/components/embedded-chat/index.tsx +9 -2
- package/src/components/layouts/Sidebar.tsx +3 -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 +6 -2
- package/src/components/sidebar/nav-user.tsx +1 -1
- package/src/components/tool-invocation-card/ToolOutputRenderer.tsx +5 -2
- package/src/explain-fact/rule-presentation.ts +1 -6
- package/src/explain-fact/tree-model.ts +23 -1
- package/src/hooks/use-manual-actions.ts +1 -1
- package/src/hooks/useAuth.ts +3 -1
- package/src/lib/chat-types.ts +18 -0
- package/src/lib/datastore-route-table.ts +2 -0
- package/src/providers/AuthContext.ts +1 -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/plans/components/ActionPlansTableColumns.tsx +3 -2
|
@@ -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
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
IntegrationIcon,
|
|
17
17
|
integrationToChannel,
|
|
18
18
|
isTerminalActionPlanState,
|
|
19
|
+
needsApproval,
|
|
19
20
|
planStateVariant,
|
|
20
21
|
statusLabel,
|
|
21
22
|
} from "#components/planning/plan-detail-utils";
|
|
@@ -38,9 +39,9 @@ function planStateLabel(plan: ActionPlan, pendingApprovalCount: number): string
|
|
|
38
39
|
return match({
|
|
39
40
|
state: plan.state,
|
|
40
41
|
hasCurrentFacts: plan.hasCurrentFacts,
|
|
41
|
-
|
|
42
|
+
needsApproval: needsApproval(plan.state, pendingApprovalCount),
|
|
42
43
|
})
|
|
43
|
-
.with({
|
|
44
|
+
.with({ needsApproval: true }, () => "Needs approval")
|
|
44
45
|
.with({ state: GQLActionPlanState.Draft, hasCurrentFacts: false }, () => "Not currently executable")
|
|
45
46
|
.with({ state: GQLActionPlanState.Draft }, () => "Ready to launch")
|
|
46
47
|
.otherwise(({ state: matchedState }) => statusLabel(matchedState));
|