@manta-eu/react 0.5.0 → 0.6.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/dist/components/chat-view/Chat.d.ts +8 -2
- package/dist/components/chat-view/Chat.d.ts.map +1 -1
- package/dist/components/chat-view/Chat.js +25 -3
- package/dist/components/chat-view/Chat.js.map +1 -1
- package/dist/components/chat-view/ToolApprovalPart.d.ts +12 -0
- package/dist/components/chat-view/ToolApprovalPart.d.ts.map +1 -0
- package/dist/components/chat-view/ToolApprovalPart.js +12 -0
- package/dist/components/chat-view/ToolApprovalPart.js.map +1 -0
- package/dist/components/chat-view/ToolMessagePart.d.ts.map +1 -1
- package/dist/components/chat-view/ToolMessagePart.js +7 -15
- package/dist/components/chat-view/ToolMessagePart.js.map +1 -1
- package/dist/components/chat-view/tool-part-rendering.js +1 -1
- package/dist/components/chat-view/tool-part-rendering.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/ActionPlanDetailView.d.ts +1 -0
- package/dist/components/planning/ActionPlanDetailView.d.ts.map +1 -1
- package/dist/components/planning/ActionPlanDetailView.js +2 -2
- package/dist/components/planning/ActionPlanDetailView.js.map +1 -1
- package/dist/components/planning/ConnectedPlanDetailView.d.ts +1 -0
- package/dist/components/planning/ConnectedPlanDetailView.d.ts.map +1 -1
- package/dist/components/planning/ConnectedPlanDetailView.js.map +1 -1
- package/dist/components/planning/TopBar.d.ts +1 -0
- package/dist/components/planning/TopBar.d.ts.map +1 -1
- package/dist/components/planning/TopBar.js +7 -4
- package/dist/components/planning/TopBar.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 +4 -1
- 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/ToolActions.d.ts +2 -4
- package/dist/components/tool-invocation-card/ToolActions.d.ts.map +1 -1
- package/dist/components/tool-invocation-card/ToolActions.js +2 -9
- package/dist/components/tool-invocation-card/ToolActions.js.map +1 -1
- package/dist/components/tool-invocation-card/ToolHeader.d.ts +0 -1
- package/dist/components/tool-invocation-card/ToolHeader.d.ts.map +1 -1
- package/dist/components/tool-invocation-card/ToolHeader.js +11 -4
- package/dist/components/tool-invocation-card/ToolHeader.js.map +1 -1
- package/dist/components/tool-invocation-card/ToolInvocationCard.d.ts +0 -7
- package/dist/components/tool-invocation-card/ToolInvocationCard.d.ts.map +1 -1
- package/dist/components/tool-invocation-card/ToolInvocationCard.js +6 -8
- package/dist/components/tool-invocation-card/ToolInvocationCard.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/useActionGraph.d.ts +7 -0
- package/dist/hooks/useActionGraph.d.ts.map +1 -0
- package/dist/hooks/useActionGraph.js +23 -0
- package/dist/hooks/useActionGraph.js.map +1 -0
- 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/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/lib/tool-icons.d.ts.map +1 -1
- package/dist/lib/tool-icons.js +1 -0
- package/dist/lib/tool-icons.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/routes/plans/components/ActionPlansPage.d.ts.map +1 -1
- package/dist/routes/plans/components/ActionPlansPage.js +3 -3
- package/dist/routes/plans/components/ActionPlansPage.js.map +1 -1
- package/dist/routes/plans/components/PlanDetailRouteContent.d.ts.map +1 -1
- package/dist/routes/plans/components/PlanDetailRouteContent.js +3 -1
- package/dist/routes/plans/components/PlanDetailRouteContent.js.map +1 -1
- package/package.json +12 -7
- package/src/app-subpaths.json +1 -0
- package/src/components/chat-view/Chat.tsx +60 -5
- package/src/components/chat-view/ToolApprovalPart.tsx +27 -0
- package/src/components/chat-view/ToolMessagePart.tsx +8 -25
- package/src/components/chat-view/tool-part-rendering.ts +1 -1
- package/src/components/layouts/Sidebar.tsx +3 -2
- package/src/components/planning/ActionPlanDetailView.tsx +3 -0
- package/src/components/planning/ConnectedPlanDetailView.tsx +1 -0
- package/src/components/planning/TopBar.tsx +14 -3
- package/src/components/planning/plan-detail-utils.tsx +5 -1
- package/src/components/sidebar/nav-user.tsx +1 -1
- package/src/components/tool-invocation-card/ToolActions.tsx +5 -16
- package/src/components/tool-invocation-card/ToolHeader.tsx +13 -6
- package/src/components/tool-invocation-card/ToolInvocationCard.tsx +6 -23
- 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/useActionGraph.ts +41 -0
- package/src/hooks/useAuth.ts +3 -1
- package/src/lib/datastore-route-table.ts +2 -0
- package/src/lib/tool-icons.ts +1 -0
- package/src/providers/AuthContext.ts +1 -0
- package/src/routes/plans/components/ActionPlansPage.tsx +4 -4
- package/src/routes/plans/components/PlanDetailRouteContent.tsx +3 -0
|
@@ -16,7 +16,7 @@ import { humanCount } from "@manta-eu/lib/humanize";
|
|
|
16
16
|
import { isPresent } from "@manta-eu/lib/present";
|
|
17
17
|
import type { ChatStatus, FileUIPart } from "ai";
|
|
18
18
|
import { ArrowUp } from "lucide-react";
|
|
19
|
-
import type { ReactNode } from "react";
|
|
19
|
+
import type { ChangeEvent, ReactNode, RefObject } from "react";
|
|
20
20
|
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
21
21
|
import { toast } from "sonner";
|
|
22
22
|
import { match, P } from "ts-pattern";
|
|
@@ -138,9 +138,41 @@ export function useInitialPrompt(initialPrompt: string | undefined, submit: (pro
|
|
|
138
138
|
}, [initialPrompt, isConversationLoading, activeConversation, submit]);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
export type ComposerCarryOver = { conversationId: string | undefined; text: string };
|
|
142
|
+
|
|
143
|
+
/** The unsent text a remounted composer starts with: its own chat's, or that of the blank chat it just became. */
|
|
144
|
+
export function carriedComposerText(
|
|
145
|
+
carryOver: ComposerCarryOver,
|
|
146
|
+
conversationId: string | undefined,
|
|
147
|
+
hasPendingMessage: boolean,
|
|
148
|
+
): string {
|
|
149
|
+
if (carryOver.conversationId === conversationId) return carryOver.text;
|
|
150
|
+
return carryOver.conversationId === undefined && hasPendingMessage ? carryOver.text : "";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function useComposerCarryOver(
|
|
154
|
+
carryOverRef: RefObject<ComposerCarryOver>,
|
|
155
|
+
conversationId: string | undefined,
|
|
156
|
+
hasPendingMessage: boolean,
|
|
157
|
+
) {
|
|
158
|
+
const [initialText] = useState(() => carriedComposerText(carryOverRef.current, conversationId, hasPendingMessage));
|
|
159
|
+
const rememberText = useCallback(
|
|
160
|
+
(text: string) => {
|
|
161
|
+
carryOverRef.current = { conversationId, text };
|
|
162
|
+
},
|
|
163
|
+
[carryOverRef, conversationId],
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
useEffect(() => rememberText(initialText), [rememberText, initialText]);
|
|
167
|
+
|
|
168
|
+
return { initialText: isPresent(initialText) ? initialText : undefined, rememberText };
|
|
169
|
+
}
|
|
170
|
+
|
|
141
171
|
export function Chat(props: ChatProps) {
|
|
142
172
|
const turnState = useChatTurnState();
|
|
143
|
-
|
|
173
|
+
// ChatContent remounts when a blank chat becomes a conversation; the composer's unsent text outlives it here.
|
|
174
|
+
const composerCarryOver = useRef<ComposerCarryOver>({ conversationId: undefined, text: "" });
|
|
175
|
+
const content = <ChatContent {...props} turnState={turnState} composerCarryOver={composerCarryOver} />;
|
|
144
176
|
|
|
145
177
|
return <ActiveChatProvider>{props.isEmbedded ? <Suspense>{content}</Suspense> : content}</ActiveChatProvider>;
|
|
146
178
|
}
|
|
@@ -208,7 +240,12 @@ export function chatIsConnectingOrSyncing(args: ChatConnectingArgs): boolean {
|
|
|
208
240
|
return !args.isBlankChat && !args.hasMessages && (!args.isSocketOpen || !args.isTranscriptSynced);
|
|
209
241
|
}
|
|
210
242
|
|
|
211
|
-
|
|
243
|
+
type ChatContentProps = ChatProps & {
|
|
244
|
+
turnState: ChatTurnState;
|
|
245
|
+
composerCarryOver: RefObject<ComposerCarryOver>;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
function ChatContent(props: ChatContentProps) {
|
|
212
249
|
const {
|
|
213
250
|
datastore,
|
|
214
251
|
user,
|
|
@@ -225,6 +262,7 @@ function ChatContent(props: ChatProps & { turnState: ChatTurnState }) {
|
|
|
225
262
|
mark,
|
|
226
263
|
renderMenuItems,
|
|
227
264
|
turnState,
|
|
265
|
+
composerCarryOver,
|
|
228
266
|
} = props;
|
|
229
267
|
|
|
230
268
|
const assistantName = useAssistantName();
|
|
@@ -377,6 +415,22 @@ function ChatContent(props: ChatProps & { turnState: ChatTurnState }) {
|
|
|
377
415
|
);
|
|
378
416
|
|
|
379
417
|
const composerDraft = useComposerDraft(initialDraft, handleAgentSubmit);
|
|
418
|
+
const { initialText: carriedText, rememberText } = useComposerCarryOver(
|
|
419
|
+
composerCarryOver,
|
|
420
|
+
activeConversation?.id,
|
|
421
|
+
hasPendingMessage,
|
|
422
|
+
);
|
|
423
|
+
const submitComposer = useCallback(
|
|
424
|
+
(value: string, files: File[]) => {
|
|
425
|
+
rememberText("");
|
|
426
|
+
return composerDraft.onSubmit(value, files);
|
|
427
|
+
},
|
|
428
|
+
[composerDraft, rememberText],
|
|
429
|
+
);
|
|
430
|
+
const rememberComposerText = useCallback(
|
|
431
|
+
(event: ChangeEvent<HTMLTextAreaElement>) => rememberText(event.target.value),
|
|
432
|
+
[rememberText],
|
|
433
|
+
);
|
|
380
434
|
|
|
381
435
|
const handleConversationStarterSubmit = useCallback(
|
|
382
436
|
(value: string) => {
|
|
@@ -584,9 +638,9 @@ function ChatContent(props: ChatProps & { turnState: ChatTurnState }) {
|
|
|
584
638
|
|
|
585
639
|
<ChatInput
|
|
586
640
|
key={composerDraft.key}
|
|
587
|
-
initialValue={composerDraft.initialValue}
|
|
641
|
+
initialValue={composerDraft.initialValue ?? carriedText}
|
|
588
642
|
dropTargetRef={dropZoneRef}
|
|
589
|
-
onSubmit={isComposerBusy ? undefined :
|
|
643
|
+
onSubmit={isComposerBusy ? undefined : submitComposer}
|
|
590
644
|
onRejected={handleFilesRejected}
|
|
591
645
|
topContent={<ChatInputFilePreview progress={uploadProgress} />}
|
|
592
646
|
>
|
|
@@ -596,6 +650,7 @@ function ChatContent(props: ChatProps & { turnState: ChatTurnState }) {
|
|
|
596
650
|
className="pl-5"
|
|
597
651
|
autoFocus={shouldAutoFocusInput}
|
|
598
652
|
disabled={hasPendingQuestionnaire}
|
|
653
|
+
onChange={rememberComposerText}
|
|
599
654
|
data-chat-input
|
|
600
655
|
/>
|
|
601
656
|
<ChatInputSubmit
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getToolUIPartLabel } from "@manta-eu/client/shared/tools";
|
|
2
|
+
import type { ToolUIPart } from "ai";
|
|
3
|
+
import { Card } from "#components/card/index";
|
|
4
|
+
import { ToolActions } from "#components/tool-invocation-card/ToolActions";
|
|
5
|
+
|
|
6
|
+
type ToolApprovalResponse = { id: string; approved: boolean };
|
|
7
|
+
|
|
8
|
+
type ToolApprovalPartProps = {
|
|
9
|
+
part: ToolUIPart;
|
|
10
|
+
onRespond: (response: ToolApprovalResponse) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function ToolApprovalPart(props: ToolApprovalPartProps) {
|
|
14
|
+
const { part, onRespond } = props;
|
|
15
|
+
const id = part.approval?.id;
|
|
16
|
+
if (id === undefined) return null;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Card padding="md" className="my-3 w-full max-w-2xl card-base gap-3">
|
|
20
|
+
<h4 className="typography-small-medium">{getToolUIPartLabel(part)}</h4>
|
|
21
|
+
<ToolActions
|
|
22
|
+
onApprove={() => onRespond({ id, approved: true })}
|
|
23
|
+
onReject={() => onRespond({ id, approved: false })}
|
|
24
|
+
/>
|
|
25
|
+
</Card>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -7,7 +7,9 @@ import { ActionPlanShowcaseCard } from "../tool-invocation-card/ActionPlanShowca
|
|
|
7
7
|
import { ToolInvocationCard } from "../tool-invocation-card/ToolInvocationCard";
|
|
8
8
|
import { isErrorToolResult, isShowcaseToolResult } from "../tool-invocation-card/types";
|
|
9
9
|
import { RequestUserInputToolPart } from "./RequestUserInputToolPart";
|
|
10
|
+
import { ToolApprovalPart } from "./ToolApprovalPart";
|
|
10
11
|
import { Banner } from "#components/banner/index";
|
|
12
|
+
import { useActiveChatConnection } from "#providers/ActiveChatProvider";
|
|
11
13
|
import { useChatManagerConnection } from "#providers/ChatManagerProvider";
|
|
12
14
|
|
|
13
15
|
type ActionPlanShowcaseToolPartProps = {
|
|
@@ -67,11 +69,16 @@ export function ToolMessagePart(props: ToolMessagePartProps) {
|
|
|
67
69
|
const { part, toolsUsedInSession, onAddToolResult } = props;
|
|
68
70
|
const toolCallId = part.toolCallId;
|
|
69
71
|
const toolName = part.type.replace("tool-", "");
|
|
72
|
+
const chatData = useActiveChatConnection()?.chatData;
|
|
70
73
|
|
|
71
74
|
if (toolName === "actionPlanShowcase") {
|
|
72
75
|
return <ActionPlanShowcaseToolPart part={part} />;
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
if (part.state === "approval-requested") {
|
|
79
|
+
return <ToolApprovalPart part={part} onRespond={(response) => chatData?.addToolApprovalResponse(response)} />;
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
if (toolName === requestUserInputToolName) {
|
|
76
83
|
return (
|
|
77
84
|
<RequestUserInputToolPart
|
|
@@ -84,29 +91,5 @@ export function ToolMessagePart(props: ToolMessagePartProps) {
|
|
|
84
91
|
);
|
|
85
92
|
}
|
|
86
93
|
|
|
87
|
-
return
|
|
88
|
-
<ToolInvocationCard
|
|
89
|
-
toolUIPart={part}
|
|
90
|
-
toolCallId={toolCallId}
|
|
91
|
-
needsConfirmation={false}
|
|
92
|
-
onSubmit={({ toolCallId, result }) => {
|
|
93
|
-
toolsUsedInSession.current.add(toolName);
|
|
94
|
-
|
|
95
|
-
onAddToolResult({
|
|
96
|
-
tool: toolName,
|
|
97
|
-
toolCallId,
|
|
98
|
-
output: result,
|
|
99
|
-
});
|
|
100
|
-
}}
|
|
101
|
-
addToolResult={(toolCallId, result) => {
|
|
102
|
-
toolsUsedInSession.current.add(toolName);
|
|
103
|
-
|
|
104
|
-
onAddToolResult({
|
|
105
|
-
tool: toolName,
|
|
106
|
-
toolCallId,
|
|
107
|
-
output: result,
|
|
108
|
-
});
|
|
109
|
-
}}
|
|
110
|
-
/>
|
|
111
|
-
);
|
|
94
|
+
return <ToolInvocationCard toolUIPart={part} />;
|
|
112
95
|
}
|
|
@@ -17,7 +17,7 @@ function isFailedRequestUserInputPart(part: UIMessage["parts"][number]): boolean
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function isStandaloneToolPart(part: ToolUIPart): boolean {
|
|
20
|
-
return standaloneToolPartTypes.has(part.type);
|
|
20
|
+
return standaloneToolPartTypes.has(part.type) || part.state === "approval-requested";
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export function isPendingRequestUserInputPart(part: UIMessage["parts"][number]): boolean {
|
|
@@ -107,6 +107,7 @@ export function Sidebar(props: SidebarProps) {
|
|
|
107
107
|
const canManageTeam = authContext?.canManageTeam ?? false;
|
|
108
108
|
const canManageCustomApps = authContext?.canManageCustomApps ?? false;
|
|
109
109
|
const canDisconnectCustomApps = authContext?.canDisconnectCustomApps ?? false;
|
|
110
|
+
const hasProfilePage = authContext?.hasProfilePage ?? false;
|
|
110
111
|
const canManageStreams = authContext?.canManageStreams ?? isOperator ?? false;
|
|
111
112
|
|
|
112
113
|
const { state: sidebarState, isMobile, toggleSidebar, setOpenMobile } = useSidebar();
|
|
@@ -467,11 +468,11 @@ export function Sidebar(props: SidebarProps) {
|
|
|
467
468
|
}
|
|
468
469
|
onCloseAutoFocus={handleProfileMenuCloseAutoFocus}
|
|
469
470
|
onNavigateToAccount={() => {
|
|
470
|
-
|
|
471
|
+
if (hasProfilePage) pendingMenuNavigateRef.current = "/profile";
|
|
471
472
|
}}
|
|
472
473
|
onLogout={onLogout}
|
|
473
474
|
>
|
|
474
|
-
<DropdownGroup className="
|
|
475
|
+
<DropdownGroup className="px-2 pb-2">
|
|
475
476
|
<ThemeSelectorDropdown />
|
|
476
477
|
{canManageDatastores && (
|
|
477
478
|
<ManageDatastoresMenuItem onSelect={() => (pendingMenuNavigateRef.current = "/datastores")} />
|
|
@@ -25,6 +25,7 @@ export type ActionPlanDetailViewProps = {
|
|
|
25
25
|
issueCount?: number;
|
|
26
26
|
isValidating?: boolean;
|
|
27
27
|
isDryRun?: boolean;
|
|
28
|
+
pendingApprovalCount?: number;
|
|
28
29
|
hideTopBar?: boolean;
|
|
29
30
|
hideChanges?: boolean;
|
|
30
31
|
fitContent?: boolean;
|
|
@@ -51,6 +52,7 @@ export function ActionPlanDetailView(props: ActionPlanDetailViewProps) {
|
|
|
51
52
|
issueCount = 0,
|
|
52
53
|
isValidating = false,
|
|
53
54
|
isDryRun = false,
|
|
55
|
+
pendingApprovalCount,
|
|
54
56
|
hideTopBar = false,
|
|
55
57
|
hideChanges = false,
|
|
56
58
|
fitContent = false,
|
|
@@ -122,6 +124,7 @@ export function ActionPlanDetailView(props: ActionPlanDetailViewProps) {
|
|
|
122
124
|
issueCount={issueCount}
|
|
123
125
|
isValidating={isValidating}
|
|
124
126
|
isDryRun={isDryRun}
|
|
127
|
+
pendingApprovalCount={pendingApprovalCount}
|
|
125
128
|
planName={planName}
|
|
126
129
|
status={status}
|
|
127
130
|
hasCurrentFacts={plan.hasCurrentFacts}
|
|
@@ -24,6 +24,7 @@ export type ConnectedPlanDetailViewProps = {
|
|
|
24
24
|
viewMode?: PlanViewMode;
|
|
25
25
|
stateOverride?: GQLActionPlanState;
|
|
26
26
|
isDryRun?: boolean;
|
|
27
|
+
pendingApprovalCount?: number;
|
|
27
28
|
ref?: Ref<ConnectedPlanDetailViewHandle>;
|
|
28
29
|
onViewModeChange?: (viewMode: PlanViewMode) => void;
|
|
29
30
|
onExecute?: (planIri: string) => void;
|
|
@@ -3,7 +3,7 @@ import { CircleStop, Eye, History, List, Play, RotateCcw } from "lucide-react";
|
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
4
|
import { match } from "ts-pattern";
|
|
5
5
|
import { ChatToggleButton } from "../layouts/ChatToggleButton";
|
|
6
|
-
import { isTerminalActionPlanState, type ActionPlanStatus } from "./plan-detail-utils";
|
|
6
|
+
import { isTerminalActionPlanState, needsApproval, type ActionPlanStatus } from "./plan-detail-utils";
|
|
7
7
|
import { Button } from "#components/button/index";
|
|
8
8
|
import { Tabs, TabsList, TabsTrigger } from "#components/tabs/index";
|
|
9
9
|
|
|
@@ -32,13 +32,23 @@ export type TopBarProps = {
|
|
|
32
32
|
onModify?: (planIri: string) => void;
|
|
33
33
|
topBarActions?: ReactNode;
|
|
34
34
|
leadingAction?: ReactNode;
|
|
35
|
+
pendingApprovalCount?: number;
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
function isPlanViewMode(value: string): value is PlanViewMode {
|
|
38
39
|
return value === "overview" || value === "visual" || value === "history";
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function statusSubtitle(
|
|
42
|
+
function statusSubtitle(
|
|
43
|
+
status: ActionPlanStatus,
|
|
44
|
+
isDryRun: boolean,
|
|
45
|
+
hasCurrentFacts: boolean,
|
|
46
|
+
pendingApprovalCount: number,
|
|
47
|
+
): string | null {
|
|
48
|
+
if (!isDryRun && needsApproval(status, pendingApprovalCount)) {
|
|
49
|
+
return "Needs approval";
|
|
50
|
+
}
|
|
51
|
+
|
|
42
52
|
return match(status)
|
|
43
53
|
.with(GQLActionPlanState.Draft, () => (hasCurrentFacts ? "Plan is ready to run" : "Not currently executable"))
|
|
44
54
|
.with(GQLActionPlanState.Running, () => (isDryRun ? "Dry run in progress" : "Plan is running"))
|
|
@@ -65,6 +75,7 @@ export function TopBar(props: TopBarProps) {
|
|
|
65
75
|
onRerun,
|
|
66
76
|
topBarActions,
|
|
67
77
|
leadingAction,
|
|
78
|
+
pendingApprovalCount = 0,
|
|
68
79
|
} = props;
|
|
69
80
|
const isDraft = status === GQLActionPlanState.Draft;
|
|
70
81
|
const isRunning = status === GQLActionPlanState.Running;
|
|
@@ -77,7 +88,7 @@ export function TopBar(props: TopBarProps) {
|
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
const subtitle = statusSubtitle(status, isDryRun, hasCurrentFacts);
|
|
91
|
+
const subtitle = statusSubtitle(status, isDryRun, hasCurrentFacts, pendingApprovalCount);
|
|
81
92
|
|
|
82
93
|
return (
|
|
83
94
|
<div className="shrink-0 flex h-16 items-center justify-between gap-2 border-b border-default-border px-3 sm:px-6">
|
|
@@ -77,8 +77,12 @@ export function statusLabel(status: ActionPlanStatus): string {
|
|
|
77
77
|
.exhaustive();
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
export function canNeedApproval(state: ActionPlanStatus): boolean {
|
|
81
|
+
return state === GQLActionPlanState.Running || state === GQLActionPlanState.Completed;
|
|
82
|
+
}
|
|
83
|
+
|
|
80
84
|
export function needsApproval(state: ActionPlanStatus, pendingApprovalCount: number): boolean {
|
|
81
|
-
return pendingApprovalCount > 0 && (state
|
|
85
|
+
return pendingApprovalCount > 0 && canNeedApproval(state);
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
export function planStateVariant(state: ActionPlanStatus, pendingApprovalCount = 0): BadgeVariant {
|
|
@@ -83,7 +83,7 @@ export function NavUser(args: NavUserArgs) {
|
|
|
83
83
|
{pickerSlot}
|
|
84
84
|
{topSlot}
|
|
85
85
|
{(pickerSlot || topSlot) && <DropdownSeparator />}
|
|
86
|
-
<DropdownGroup className="p-2">
|
|
86
|
+
<DropdownGroup className={children ? "px-2 pt-2" : "p-2"}>
|
|
87
87
|
<DropdownItem
|
|
88
88
|
className="p-1.5 flex flex-row items-center gap-1.5 typography-caption h-11"
|
|
89
89
|
onClick={onNavigateToAccount}
|
|
@@ -1,30 +1,19 @@
|
|
|
1
|
-
import { APPROVAL } from "@manta-eu/client/shared/index";
|
|
2
|
-
import { type ToolInvocationCardProps } from "./ToolInvocationCard";
|
|
3
1
|
import { Button } from "#components/button/index";
|
|
4
2
|
|
|
5
3
|
type ToolActionsProps = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
onSubmit: ToolInvocationCardProps["onSubmit"];
|
|
4
|
+
onApprove: () => void;
|
|
5
|
+
onReject: () => void;
|
|
9
6
|
};
|
|
10
7
|
|
|
11
8
|
export function ToolActions(props: ToolActionsProps) {
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
function handleReject() {
|
|
15
|
-
onSubmit({ toolCallId, result: APPROVAL.NO });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function handleApprove() {
|
|
19
|
-
onSubmit({ toolCallId, result: APPROVAL.YES });
|
|
20
|
-
}
|
|
9
|
+
const { onApprove, onReject } = props;
|
|
21
10
|
|
|
22
11
|
return (
|
|
23
12
|
<div className="flex gap-2 justify-end">
|
|
24
|
-
<Button variant="destructive" size="sm" onClick={
|
|
13
|
+
<Button variant="destructive" size="sm" onClick={onReject}>
|
|
25
14
|
Reject
|
|
26
15
|
</Button>
|
|
27
|
-
<Button size="sm" onClick={
|
|
16
|
+
<Button size="sm" onClick={onApprove}>
|
|
28
17
|
Approve
|
|
29
18
|
</Button>
|
|
30
19
|
</div>
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { getToolUIPartLabel } from "@manta-eu/client/shared/tools";
|
|
2
2
|
import type { ToolUIPart } from "ai";
|
|
3
|
-
import { CheckCircle2, ChevronDown, Loader2 } from "lucide-react";
|
|
3
|
+
import { AlertTriangle, CheckCircle2, ChevronDown, Loader2, XCircle } from "lucide-react";
|
|
4
|
+
import { match } from "ts-pattern";
|
|
4
5
|
|
|
5
6
|
type ToolHeaderProps = {
|
|
6
7
|
toolUIPart: ToolUIPart;
|
|
7
|
-
needsConfirmation: boolean;
|
|
8
8
|
isExpanded: boolean;
|
|
9
9
|
onToggle: () => void;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
function StateIcon(props: { state: ToolUIPart["state"] }) {
|
|
13
|
+
return match(props.state)
|
|
14
|
+
.with("output-available", () => <CheckCircle2 size={16} />)
|
|
15
|
+
.with("output-denied", () => <XCircle size={16} />)
|
|
16
|
+
.with("output-error", () => <AlertTriangle size={16} />)
|
|
17
|
+
.otherwise(() => <Loader2 size={16} className="animate-spin" />);
|
|
18
|
+
}
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
export function ToolHeader(props: ToolHeaderProps) {
|
|
21
|
+
const { toolUIPart, isExpanded, onToggle } = props;
|
|
16
22
|
|
|
17
23
|
const humanToolName = getToolUIPartLabel(toolUIPart);
|
|
18
24
|
|
|
@@ -23,8 +29,9 @@ export function ToolHeader(props: ToolHeaderProps) {
|
|
|
23
29
|
return (
|
|
24
30
|
<button type="button" onClick={handleToggle} className="w-full flex items-center gap-2 cursor-pointer">
|
|
25
31
|
<h4 className="typography-small-medium flex items-center gap-2 flex-1 text-left">
|
|
26
|
-
|
|
32
|
+
<StateIcon state={toolUIPart.state} />
|
|
27
33
|
{humanToolName}
|
|
34
|
+
{toolUIPart.state === "output-denied" && <span className="text-muted-foreground">Rejected</span>}
|
|
28
35
|
</h4>
|
|
29
36
|
<ChevronDown
|
|
30
37
|
size={16}
|
|
@@ -2,7 +2,6 @@ import { getToolUIPartLabel } from "@manta-eu/client/shared/tools";
|
|
|
2
2
|
import type { ToolUIPart } from "ai";
|
|
3
3
|
import { useCallback, useState } from "react";
|
|
4
4
|
import { match } from "ts-pattern";
|
|
5
|
-
import { ToolActions } from "./ToolActions";
|
|
6
5
|
import { ToolArgumentsDisplay } from "./ToolArgumentsDisplay";
|
|
7
6
|
import { ToolHeader } from "./ToolHeader";
|
|
8
7
|
import { ToolLoadingState } from "./ToolLoadingState";
|
|
@@ -12,30 +11,21 @@ import { cn } from "#lib/utils";
|
|
|
12
11
|
|
|
13
12
|
export type ToolInvocationCardProps = {
|
|
14
13
|
toolUIPart: ToolUIPart;
|
|
15
|
-
toolCallId: string;
|
|
16
|
-
needsConfirmation: boolean;
|
|
17
|
-
onSubmit: ({ toolCallId, result }: { toolCallId: string; result: string }) => void;
|
|
18
|
-
addToolResult: (toolCallId: string, result: string) => void;
|
|
19
14
|
};
|
|
20
15
|
|
|
21
16
|
export function ToolInvocationCard(props: ToolInvocationCardProps) {
|
|
22
|
-
const { toolUIPart
|
|
17
|
+
const { toolUIPart } = props;
|
|
23
18
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
24
19
|
const loadingLabel = getToolUIPartLabel(toolUIPart);
|
|
25
20
|
const [executionStartTime] = useState(Date.now());
|
|
26
21
|
|
|
27
22
|
const handleToggle = useCallback(() => {
|
|
28
|
-
setIsExpanded(!
|
|
23
|
+
setIsExpanded((expanded) => !expanded);
|
|
29
24
|
}, []);
|
|
30
25
|
|
|
31
26
|
return (
|
|
32
27
|
<Card className="my-3 w-full max-w-2xl card-base overflow-hidden">
|
|
33
|
-
<ToolHeader
|
|
34
|
-
toolUIPart={toolUIPart}
|
|
35
|
-
needsConfirmation={needsConfirmation}
|
|
36
|
-
isExpanded={isExpanded}
|
|
37
|
-
onToggle={handleToggle}
|
|
38
|
-
/>
|
|
28
|
+
<ToolHeader toolUIPart={toolUIPart} isExpanded={isExpanded} onToggle={handleToggle} />
|
|
39
29
|
|
|
40
30
|
<div
|
|
41
31
|
className={cn([
|
|
@@ -46,22 +36,15 @@ export function ToolInvocationCard(props: ToolInvocationCardProps) {
|
|
|
46
36
|
<div className="overflow-y-auto" style={{ maxHeight: isExpanded ? "580px" : "0px" }}>
|
|
47
37
|
<ToolArgumentsDisplay toolUIPart={toolUIPart} />
|
|
48
38
|
|
|
49
|
-
{match(
|
|
50
|
-
.with(
|
|
51
|
-
<ToolActions
|
|
52
|
-
toolCallId={toolCallId}
|
|
53
|
-
toolName={toolUIPart.type.replace("tool-", "")}
|
|
54
|
-
onSubmit={onSubmit}
|
|
55
|
-
/>
|
|
56
|
-
))
|
|
57
|
-
.with({ needsConfirmation: false, state: "output-available" }, () => (
|
|
39
|
+
{match(toolUIPart.state)
|
|
40
|
+
.with("output-available", () => (
|
|
58
41
|
<ToolResultDisplay
|
|
59
42
|
result={toolUIPart.output}
|
|
60
43
|
executionStartTime={executionStartTime}
|
|
61
44
|
toolUIPart={toolUIPart}
|
|
62
45
|
/>
|
|
63
46
|
))
|
|
64
|
-
.with(
|
|
47
|
+
.with("input-streaming", () => <ToolLoadingState label={loadingLabel} />)
|
|
65
48
|
.otherwise(() => null)}
|
|
66
49
|
</div>
|
|
67
50
|
</div>
|
|
@@ -20,6 +20,7 @@ import { isPresent } from "@manta-eu/lib/present";
|
|
|
20
20
|
import { match, P } from "ts-pattern";
|
|
21
21
|
import { parseDatalogRuleCached, parseGroundedDatalogRuleCached } from "../rules/datalog-cache";
|
|
22
22
|
import {
|
|
23
|
+
explainRuleAst,
|
|
23
24
|
getRuleBodyTerms,
|
|
24
25
|
kindForBodyFormula,
|
|
25
26
|
kindForSyntheticTerm,
|
|
@@ -362,12 +363,6 @@ function ruleNameTermFromHeadText(headText: string): string {
|
|
|
362
363
|
return predicate.trim();
|
|
363
364
|
}
|
|
364
365
|
|
|
365
|
-
// Explain payloads echo rules as the datastore serializes them (bare ON, atom-free aggregates),
|
|
366
|
-
// so authored text falls through to the expanded grammar when the strict parse fails.
|
|
367
|
-
function explainRuleAst(rule: string): DatalogRule | undefined {
|
|
368
|
-
return parseDatalogRuleCached(rule).ast ?? parseGroundedDatalogRuleCached(rule).ast ?? undefined;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
366
|
function collectBindings(ast: DatalogRule, groundedAst: DatalogRule): VariableBinding[] {
|
|
372
367
|
const bindings = new Map<string, GroundedBinding>();
|
|
373
368
|
const predicates = new Map<string, string>();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
classAssertionFromAtom,
|
|
3
|
+
formulaSourceTexts,
|
|
3
4
|
isDatalogAtom,
|
|
4
5
|
isLiteralTerm,
|
|
5
6
|
isVariableTerm,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
type DatalogAtom,
|
|
12
13
|
type DatalogBodyFormula,
|
|
13
14
|
type DatalogExpression,
|
|
15
|
+
type DatalogRule,
|
|
14
16
|
} from "@manta-eu/datalog-parser";
|
|
15
17
|
import { isPresent } from "@manta-eu/lib/present";
|
|
16
18
|
import { countBy } from "ramda";
|
|
@@ -472,13 +474,33 @@ export function ruleHeadText(rule: string): string {
|
|
|
472
474
|
);
|
|
473
475
|
}
|
|
474
476
|
|
|
477
|
+
// Explain payloads echo rules as the datastore serializes them (bare ON, atom-free aggregates),
|
|
478
|
+
// so authored text falls through to the expanded grammar when the strict parse fails.
|
|
479
|
+
export function explainRuleAst(rule: string): DatalogRule | undefined {
|
|
480
|
+
return parseDatalogRuleCached(rule).ast ?? parseGroundedDatalogRuleCached(rule).ast ?? undefined;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function parsedSourceTexts(rule: string) {
|
|
484
|
+
const parsed = explainRuleAst(rule);
|
|
485
|
+
|
|
486
|
+
return parsed ? formulaSourceTexts(parsed) : undefined;
|
|
487
|
+
}
|
|
488
|
+
|
|
475
489
|
export function ruleHeadTerms(rule: string): string[] {
|
|
490
|
+
const parsed = parsedSourceTexts(rule);
|
|
491
|
+
|
|
492
|
+
if (parsed) return parsed.head;
|
|
493
|
+
|
|
476
494
|
return splitTopLevel(ruleHeadText(rule), ",")
|
|
477
495
|
.map((part) => part.trim())
|
|
478
496
|
.filter(isPresent);
|
|
479
497
|
}
|
|
480
498
|
|
|
481
499
|
export function getRuleBodyTerms(rule: string): string[] {
|
|
500
|
+
const parsed = parsedSourceTexts(rule);
|
|
501
|
+
|
|
502
|
+
if (parsed) return parsed.body;
|
|
503
|
+
|
|
482
504
|
const body = bodyText(rule);
|
|
483
505
|
|
|
484
506
|
if (!body) return [];
|
|
@@ -624,7 +646,7 @@ function aggregateOutputsOf(
|
|
|
624
646
|
}
|
|
625
647
|
|
|
626
648
|
function ruleBodyFormulas(rule: string, expectedCount: number): DatalogBodyFormula[] | undefined {
|
|
627
|
-
const ast =
|
|
649
|
+
const ast = explainRuleAst(rule);
|
|
628
650
|
|
|
629
651
|
if (!ast || ast.body.length !== expectedCount) return undefined;
|
|
630
652
|
|
|
@@ -74,7 +74,7 @@ export function toManualAction(entity: ManualActionEntity, options: ManualAction
|
|
|
74
74
|
assignees: entity.assignee.map(String),
|
|
75
75
|
label: entity.label || null,
|
|
76
76
|
description: entity.description || null,
|
|
77
|
-
observedValue: entity.observedValue || null,
|
|
77
|
+
observedValue: typeof entity.observedValue === "string" ? entity.observedValue || null : null,
|
|
78
78
|
aboutIri: entity.about ?? null,
|
|
79
79
|
retryable: String(entity.retryable) === "true",
|
|
80
80
|
defaultResolution: normalizeResolution(entity.defaultResolution ?? undefined),
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runActionGraph,
|
|
3
|
+
type ActionGraphql,
|
|
4
|
+
type GraphOp,
|
|
5
|
+
type GraphRun,
|
|
6
|
+
type GraphRunOptions,
|
|
7
|
+
} from "@manta-eu/client/shared/action-graph";
|
|
8
|
+
import { useMutation, useQueryClient, type UseMutationResult } from "@tanstack/react-query";
|
|
9
|
+
import { uniq } from "ramda";
|
|
10
|
+
import { useSparqlOptions } from "#hooks/use-sparql-options";
|
|
11
|
+
import { useMantaClient } from "#providers/MantaClientProvider";
|
|
12
|
+
import { invalidateActionPlanQueries } from "#shared/query-keys";
|
|
13
|
+
|
|
14
|
+
export type UseActionGraphOptions = Omit<GraphRunOptions, "graphql" | "readOptions">;
|
|
15
|
+
|
|
16
|
+
export const actionGraphMutationKey = ["ExecuteActionGraph"] as const;
|
|
17
|
+
|
|
18
|
+
const planGraphIris = ({ results }: GraphRun): string[] =>
|
|
19
|
+
uniq(results.flatMap((result) => ("planGraphIri" in result ? [result.planGraphIri] : [])));
|
|
20
|
+
|
|
21
|
+
/** Sends a change of several entities as one plan and reads back what each op did; `results[i]` is `ops[i]`. */
|
|
22
|
+
export function useActionGraph(
|
|
23
|
+
options: UseActionGraphOptions = {},
|
|
24
|
+
): UseMutationResult<GraphRun, Error, readonly GraphOp[]> {
|
|
25
|
+
const { mantaClient } = useMantaClient();
|
|
26
|
+
const readOptions = useSparqlOptions();
|
|
27
|
+
const queryClient = useQueryClient();
|
|
28
|
+
|
|
29
|
+
const graphql: ActionGraphql = (document, variables, request) =>
|
|
30
|
+
mantaClient.request({ document, variables, signal: request?.signal });
|
|
31
|
+
|
|
32
|
+
return useMutation<GraphRun, Error, readonly GraphOp[]>({
|
|
33
|
+
mutationKey: actionGraphMutationKey,
|
|
34
|
+
mutationFn: (ops) => runActionGraph(ops, { ...options, graphql, readOptions }),
|
|
35
|
+
onSuccess: async (run) => {
|
|
36
|
+
await Promise.all(
|
|
37
|
+
planGraphIris(run).map((planGraphIri) => invalidateActionPlanQueries(queryClient, planGraphIri)),
|
|
38
|
+
);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
package/src/hooks/useAuth.ts
CHANGED
|
@@ -65,6 +65,8 @@ const orgRoleSchema = z.object({
|
|
|
65
65
|
capabilities: z.array(z.string()).transform((values) => values.filter(isCapability)),
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
export const userContextQueryKey = ["user-context"] as const;
|
|
69
|
+
|
|
68
70
|
export const contextResponseSchema = z.object({
|
|
69
71
|
datastoreAccessGranted: z.boolean().default(true),
|
|
70
72
|
user: z.object({
|
|
@@ -197,7 +199,7 @@ export function useAuth(): UseAuthResult {
|
|
|
197
199
|
isLoading: isContextLoading,
|
|
198
200
|
error: contextError,
|
|
199
201
|
} = useQuery({
|
|
200
|
-
queryKey: [
|
|
202
|
+
queryKey: [...userContextQueryKey, datastoreId],
|
|
201
203
|
queryFn: async ({ signal }) => {
|
|
202
204
|
const res = await authorizedFetch(new URL("/graphql", mantaBaseUrl), {
|
|
203
205
|
method: "POST",
|
|
@@ -19,6 +19,7 @@ export type RouteElements = {
|
|
|
19
19
|
Team?: ReactNode;
|
|
20
20
|
CustomApps?: ReactNode;
|
|
21
21
|
CustomAppUsers?: ReactNode;
|
|
22
|
+
Profile?: ReactNode;
|
|
22
23
|
DataExplorerLayout?: ReactNode;
|
|
23
24
|
DataExplorerIndex?: ReactNode;
|
|
24
25
|
DataExplorerRecent?: ReactNode;
|
|
@@ -48,6 +49,7 @@ export const globalRoutes: RouteDefinition[] = [
|
|
|
48
49
|
{ path: "/shopify/connect", render: (e) => e.ShopifyConnect || notFoundElement(e) },
|
|
49
50
|
{ path: "/custom-apps", render: (e) => e.CustomApps || notFoundElement(e) },
|
|
50
51
|
{ path: "/custom-apps/:clientId/users", render: (e) => e.CustomAppUsers || notFoundElement(e) },
|
|
52
|
+
{ path: "/profile", render: (e) => e.Profile || notFoundElement(e) },
|
|
51
53
|
];
|
|
52
54
|
|
|
53
55
|
export const datastoreRoutes: RouteDefinition[] = [
|