@manta-eu/react 0.5.1 → 0.7.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/button/index.d.ts +1 -0
- package/dist/components/button/index.d.ts.map +1 -1
- package/dist/components/button/index.js +17 -1
- package/dist/components/button/index.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/modal/index.d.ts +4 -0
- package/dist/components/modal/index.d.ts.map +1 -1
- package/dist/components/modal/index.js +11 -3
- package/dist/components/modal/index.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/sheet/index.d.ts +13 -19
- package/dist/components/sheet/index.d.ts.map +1 -1
- package/dist/components/sheet/index.js +8 -2
- package/dist/components/sheet/index.js.map +1 -1
- package/dist/components/table/index.d.ts +7 -1
- package/dist/components/table/index.d.ts.map +1 -1
- package/dist/components/table/index.js +18 -12
- package/dist/components/table/index.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/globals.css +1526 -2772
- package/dist/hooks/use-connection-problems.d.ts.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/useValidateActionPlan.d.ts.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/routes/integrations/components/connect-forms/MetaAdsConnectForm.d.ts +1 -1
- package/dist/routes/integrations/components/connect-forms/MetaAdsConnectForm.d.ts.map +1 -1
- package/dist/routes/integrations/components/connect-forms/MetaAdsConnectForm.js +64 -11
- package/dist/routes/integrations/components/connect-forms/MetaAdsConnectForm.js.map +1 -1
- package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.d.ts.map +1 -1
- package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.js +8 -2
- package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.js.map +1 -1
- package/dist/routes/integrations/components/connect-forms/meta-page-tree.d.ts +5 -0
- package/dist/routes/integrations/components/connect-forms/meta-page-tree.d.ts.map +1 -0
- package/dist/routes/integrations/components/connect-forms/meta-page-tree.js +16 -0
- package/dist/routes/integrations/components/connect-forms/meta-page-tree.js.map +1 -0
- package/dist/routes/integrations/components/connect-forms/oauth-setup-phase.d.ts +2 -1
- package/dist/routes/integrations/components/connect-forms/oauth-setup-phase.d.ts.map +1 -1
- package/dist/routes/integrations/components/connect-forms/oauth-setup-phase.js +4 -1
- package/dist/routes/integrations/components/connect-forms/oauth-setup-phase.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/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 +20 -15
- package/src/app-subpaths.json +1 -0
- package/src/components/button/index.tsx +17 -1
- 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/modal/index.tsx +17 -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/sheet/index.tsx +29 -6
- package/src/components/table/index.tsx +42 -12
- 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/hooks/useActionGraph.ts +41 -0
- package/src/lib/tool-icons.ts +1 -0
- package/src/routes/integrations/components/connect-forms/MetaAdsConnectForm.tsx +131 -15
- package/src/routes/integrations/components/connect-forms/OAuthSetupConnectForm.tsx +8 -2
- package/src/routes/integrations/components/connect-forms/meta-page-tree.ts +17 -0
- package/src/routes/integrations/components/connect-forms/oauth-setup-phase.ts +6 -0
- package/src/routes/integrations/connection-view.ts +1 -0
- package/src/routes/plans/components/ActionPlansPage.tsx +4 -4
- package/src/routes/plans/components/PlanDetailRouteContent.tsx +3 -0
|
@@ -4,7 +4,7 @@ import type { Cell, Column, ColumnDef, Row, SortingState, Table as TableInstance
|
|
|
4
4
|
import { flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
|
|
5
5
|
import { ChevronDown, ChevronsUpDown, ChevronUp } from "lucide-react";
|
|
6
6
|
import type { ComponentProps, HTMLAttributes, KeyboardEvent, ReactNode, RefObject } from "react";
|
|
7
|
-
import { useMemo, useState } from "react";
|
|
7
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
8
8
|
import { match } from "ts-pattern";
|
|
9
9
|
import { getCommonPinnedColumnStyles } from "./table-utils";
|
|
10
10
|
import { Button } from "#components/button/index";
|
|
@@ -18,11 +18,26 @@ export { getCommonPinnedColumnStyles, getPinnedColumnInfo, type CombinedSortOpti
|
|
|
18
18
|
export { useMaxTableRows } from "./use-max-table-rows";
|
|
19
19
|
export { useTableState, type UseTableStateOptions } from "./use-table-state";
|
|
20
20
|
|
|
21
|
-
export
|
|
21
|
+
export type TableSize = "base" | "sm";
|
|
22
|
+
|
|
23
|
+
const TableSizeContext = createContext<TableSize>("base");
|
|
24
|
+
|
|
25
|
+
export type TableProps = ComponentProps<"table"> & {
|
|
26
|
+
size?: TableSize;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function Table({ className, size = "base", ...props }: TableProps) {
|
|
22
30
|
return (
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
|
|
31
|
+
<TableSizeContext.Provider value={size}>
|
|
32
|
+
<div data-slot="table-container" className="relative w-full overflow-x-auto">
|
|
33
|
+
<table
|
|
34
|
+
data-slot="table"
|
|
35
|
+
data-size={size}
|
|
36
|
+
className={cn("w-full caption-bottom typography-small", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
</TableSizeContext.Provider>
|
|
26
41
|
);
|
|
27
42
|
}
|
|
28
43
|
|
|
@@ -45,11 +60,13 @@ export function TableFooter({ className, ...props }: ComponentProps<"tfoot">) {
|
|
|
45
60
|
}
|
|
46
61
|
|
|
47
62
|
export function TableRow({ className, ...props }: ComponentProps<"tr">) {
|
|
63
|
+
const size = useContext(TableSizeContext);
|
|
48
64
|
return (
|
|
49
65
|
<tr
|
|
50
66
|
data-slot="table-row"
|
|
51
67
|
className={cn(
|
|
52
68
|
"bg-card hover:bg-muted data-[state=selected]:bg-muted border-b transition-colors text-default-foreground [&>td[data-is-pinned]]:bg-card [&>td[data-is-pinned]]:transition-colors [&>td[data-is-pinned]]:group-hover:bg-muted",
|
|
69
|
+
size === "sm" && "border-default-border",
|
|
53
70
|
className,
|
|
54
71
|
)}
|
|
55
72
|
{...props}
|
|
@@ -58,11 +75,13 @@ export function TableRow({ className, ...props }: ComponentProps<"tr">) {
|
|
|
58
75
|
}
|
|
59
76
|
|
|
60
77
|
export function TableHead({ className, ...props }: ComponentProps<"th">) {
|
|
78
|
+
const size = useContext(TableSizeContext);
|
|
61
79
|
return (
|
|
62
80
|
<th
|
|
63
81
|
data-slot="table-head"
|
|
64
82
|
className={cn(
|
|
65
|
-
"bg-faint text-muted-foreground h-10 px-2 text-left align-middle
|
|
83
|
+
"bg-faint text-muted-foreground h-10 px-2 text-left align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] data-is-pinned:bg-faint",
|
|
84
|
+
size === "sm" ? "h-9 px-4 py-2.5 typography-tiny-medium whitespace-normal" : "typography-small-medium",
|
|
66
85
|
className,
|
|
67
86
|
)}
|
|
68
87
|
{...props}
|
|
@@ -71,11 +90,13 @@ export function TableHead({ className, ...props }: ComponentProps<"th">) {
|
|
|
71
90
|
}
|
|
72
91
|
|
|
73
92
|
export function TableCell({ className, ...props }: ComponentProps<"td">) {
|
|
93
|
+
const size = useContext(TableSizeContext);
|
|
74
94
|
return (
|
|
75
95
|
<td
|
|
76
96
|
data-slot="table-cell"
|
|
77
97
|
className={cn(
|
|
78
98
|
"px-4 not-first:px-3 py-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
99
|
+
size === "sm" && "not-first:px-4 py-3 align-top text-[0.8125rem] leading-5 whitespace-normal break-words",
|
|
79
100
|
className,
|
|
80
101
|
)}
|
|
81
102
|
{...props}
|
|
@@ -117,13 +138,14 @@ declare module "@tanstack/react-table" {
|
|
|
117
138
|
|
|
118
139
|
export function CustomTableCell<TData, TValue>({ cell }: { cell: Cell<TData, TValue> }) {
|
|
119
140
|
const columnType = cell.column.columnDef.meta?.type;
|
|
141
|
+
const size = useContext(TableSizeContext);
|
|
120
142
|
|
|
121
143
|
const pinningStyles = getCommonPinnedColumnStyles(cell.column);
|
|
122
144
|
|
|
123
145
|
return (
|
|
124
146
|
<TableCell
|
|
125
147
|
className={cn(
|
|
126
|
-
"h-12",
|
|
148
|
+
size === "base" && "h-12",
|
|
127
149
|
cell.column.columnDef.meta?.wide ? "max-w-none" : "max-w-56",
|
|
128
150
|
cell.column.columnDef.meta?.className,
|
|
129
151
|
)}
|
|
@@ -159,7 +181,7 @@ export function ColumnHeader<TData, TValue>({ column, children }: TColumnHeaderP
|
|
|
159
181
|
const isSorted = column.getIsSorted();
|
|
160
182
|
|
|
161
183
|
const className = cn(
|
|
162
|
-
"
|
|
184
|
+
"flex gap-0.5 text-muted-foreground items-center group/sort-header",
|
|
163
185
|
isNumberColumn && "justify-end",
|
|
164
186
|
column.getCanSort() && "cursor-pointer",
|
|
165
187
|
);
|
|
@@ -203,6 +225,7 @@ export type DataTableBaseProps<TData> = HTMLAttributes<HTMLDivElement> & {
|
|
|
203
225
|
renderRow?: (row: Row<TData>) => ReactNode;
|
|
204
226
|
rowClassName?: (row: Row<TData>) => string | undefined;
|
|
205
227
|
isRowClickable?: (row: Row<TData>) => boolean;
|
|
228
|
+
size?: TableSize;
|
|
206
229
|
};
|
|
207
230
|
|
|
208
231
|
export type DataTableWithRowClick<TData> = DataTableBaseProps<TData> & {
|
|
@@ -258,6 +281,7 @@ export function DataTable<TData>(props: DataTableProps<TData>) {
|
|
|
258
281
|
renderRow,
|
|
259
282
|
rowClassName,
|
|
260
283
|
isRowClickable,
|
|
284
|
+
size = "base",
|
|
261
285
|
...restProps
|
|
262
286
|
} = props;
|
|
263
287
|
|
|
@@ -265,7 +289,9 @@ export function DataTable<TData>(props: DataTableProps<TData>) {
|
|
|
265
289
|
|
|
266
290
|
const hasClickHandler = !!onRowClick || !!rowHref;
|
|
267
291
|
|
|
268
|
-
const
|
|
292
|
+
const rowBorderClassName = size === "base" && "border-muted-border";
|
|
293
|
+
|
|
294
|
+
const tableBodyRowClassName = cn("group bg-card py-2 px-4", rowBorderClassName, hasClickHandler && "cursor-pointer");
|
|
269
295
|
|
|
270
296
|
const handleRowClick = (row: Row<TData>) => {
|
|
271
297
|
if (onRowClick) {
|
|
@@ -329,10 +355,10 @@ export function DataTable<TData>(props: DataTableProps<TData>) {
|
|
|
329
355
|
|
|
330
356
|
return (
|
|
331
357
|
<div className={cn("grid gap-2.5", className)} {...restProps} ref={ref}>
|
|
332
|
-
<Table>
|
|
358
|
+
<Table size={size}>
|
|
333
359
|
<TableHeader className={headerClassName}>
|
|
334
360
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
335
|
-
<TableRow key={headerGroup.id} className=
|
|
361
|
+
<TableRow key={headerGroup.id} className={cn(rowBorderClassName)}>
|
|
336
362
|
{headerGroup.headers.map((header) => {
|
|
337
363
|
const headerContent = header.isPlaceholder
|
|
338
364
|
? null
|
|
@@ -343,7 +369,10 @@ export function DataTable<TData>(props: DataTableProps<TData>) {
|
|
|
343
369
|
return (
|
|
344
370
|
<TableHead
|
|
345
371
|
key={header.id}
|
|
346
|
-
className={cn(
|
|
372
|
+
className={cn(
|
|
373
|
+
size === "base" && "h-13 px-4 not-first:px-3 py-2",
|
|
374
|
+
header.column.columnDef.meta?.className,
|
|
375
|
+
)}
|
|
347
376
|
data-is-pinned={header.column.getIsPinned() ? true : undefined}
|
|
348
377
|
style={pinningStyles}
|
|
349
378
|
ref={(thElement) => columnSizingHandler(thElement, table, header.column)}
|
|
@@ -390,6 +419,7 @@ export type SortableTableBaseProps<TRow> = {
|
|
|
390
419
|
noResultsMessage?: string;
|
|
391
420
|
className?: string;
|
|
392
421
|
isLoading?: boolean;
|
|
422
|
+
size?: TableSize;
|
|
393
423
|
};
|
|
394
424
|
|
|
395
425
|
export type SortableTableProps<TRow> =
|
|
@@ -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>
|
|
@@ -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/lib/tool-icons.ts
CHANGED
|
@@ -3,8 +3,11 @@ import {
|
|
|
3
3
|
GQLMetaSetupStateEnum,
|
|
4
4
|
useConnectedAccountsQuery,
|
|
5
5
|
useMetaSetupConnectMutation,
|
|
6
|
+
useMetaSetupIncludePagesMutation,
|
|
6
7
|
useMetaSetupProgressQuery,
|
|
7
8
|
type GQLMetaAdAccount,
|
|
9
|
+
type GQLMetaPage,
|
|
10
|
+
type GQLMetaSetupProgress,
|
|
8
11
|
} from "@manta-eu/client";
|
|
9
12
|
import { parseError } from "@manta-eu/client/shared/error-utils";
|
|
10
13
|
import { isPresent } from "@manta-eu/lib/present";
|
|
@@ -15,6 +18,7 @@ import { toast } from "sonner";
|
|
|
15
18
|
import { match } from "ts-pattern";
|
|
16
19
|
import { ConnectFormHeader } from "./ConnectFormHeader";
|
|
17
20
|
import { ImportStartedNotice } from "./ImportStartedNotice";
|
|
21
|
+
import { metaPageTreeItems, picksAPage } from "./meta-page-tree";
|
|
18
22
|
import { OAuthConnectLink } from "./OAuthConnectLink";
|
|
19
23
|
import { unavailableReasonLabel } from "./setup-account-unavailable-reason";
|
|
20
24
|
import type { ConnectFormProps, ImportNotice } from "./types";
|
|
@@ -24,12 +28,22 @@ import { Banner } from "#components/banner/index";
|
|
|
24
28
|
import { Button } from "#components/button/index";
|
|
25
29
|
import { Loader } from "#components/loader/index";
|
|
26
30
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "#components/select/index";
|
|
31
|
+
import { TreeMultiSelect } from "#components/tree-multi-select/index";
|
|
27
32
|
import { cn } from "#lib/utils";
|
|
28
33
|
import { useMantaClient } from "#providers/index";
|
|
29
34
|
|
|
30
35
|
const pollIntervalMs = 3000;
|
|
31
36
|
|
|
32
|
-
type Phase =
|
|
37
|
+
type Phase =
|
|
38
|
+
| "idle"
|
|
39
|
+
| "awaitingOAuth"
|
|
40
|
+
| "loadingAccounts"
|
|
41
|
+
| "pickingAccount"
|
|
42
|
+
| "loadingPages"
|
|
43
|
+
| "pickingPages"
|
|
44
|
+
| "submitting"
|
|
45
|
+
| "done"
|
|
46
|
+
| "failed";
|
|
33
47
|
|
|
34
48
|
type DerivePhaseInput = {
|
|
35
49
|
states: GQLMetaSetupStateEnum[] | undefined;
|
|
@@ -42,6 +56,8 @@ const statePriority: readonly GQLMetaSetupStateEnum[] = [
|
|
|
42
56
|
GQLMetaSetupStateEnum.FatallyFailed,
|
|
43
57
|
GQLMetaSetupStateEnum.TimedOut,
|
|
44
58
|
GQLMetaSetupStateEnum.ConnectingAccounts,
|
|
59
|
+
GQLMetaSetupStateEnum.SelectingPages,
|
|
60
|
+
GQLMetaSetupStateEnum.FetchingPages,
|
|
45
61
|
GQLMetaSetupStateEnum.SelectingAccounts,
|
|
46
62
|
GQLMetaSetupStateEnum.FetchingAccounts,
|
|
47
63
|
GQLMetaSetupStateEnum.Setup,
|
|
@@ -53,7 +69,14 @@ const terminalStates: readonly GQLMetaSetupStateEnum[] = [
|
|
|
53
69
|
GQLMetaSetupStateEnum.TimedOut,
|
|
54
70
|
];
|
|
55
71
|
|
|
56
|
-
const activePhases: readonly Phase[] = [
|
|
72
|
+
const activePhases: readonly Phase[] = [
|
|
73
|
+
"awaitingOAuth",
|
|
74
|
+
"loadingAccounts",
|
|
75
|
+
"pickingAccount",
|
|
76
|
+
"loadingPages",
|
|
77
|
+
"pickingPages",
|
|
78
|
+
"submitting",
|
|
79
|
+
];
|
|
57
80
|
|
|
58
81
|
export function derivePhase({ states, hasStartedOAuth, hasSeenActiveState }: DerivePhaseInput): Phase {
|
|
59
82
|
const defaultPhase: Phase = hasStartedOAuth ? "awaitingOAuth" : "idle";
|
|
@@ -64,6 +87,8 @@ export function derivePhase({ states, hasStartedOAuth, hasSeenActiveState }: Der
|
|
|
64
87
|
.with(GQLMetaSetupStateEnum.Finished, () => "done")
|
|
65
88
|
.with(GQLMetaSetupStateEnum.FatallyFailed, GQLMetaSetupStateEnum.TimedOut, () => "failed")
|
|
66
89
|
.with(GQLMetaSetupStateEnum.ConnectingAccounts, () => "submitting")
|
|
90
|
+
.with(GQLMetaSetupStateEnum.SelectingPages, () => "pickingPages")
|
|
91
|
+
.with(GQLMetaSetupStateEnum.FetchingPages, () => "loadingPages")
|
|
67
92
|
.with(GQLMetaSetupStateEnum.SelectingAccounts, () => "pickingAccount")
|
|
68
93
|
.with(GQLMetaSetupStateEnum.FetchingAccounts, GQLMetaSetupStateEnum.Setup, () => "loadingAccounts")
|
|
69
94
|
.otherwise(() => defaultPhase);
|
|
@@ -114,11 +139,20 @@ export function MetaAdsConnectForm(props: ConnectFormProps) {
|
|
|
114
139
|
const [hasStartedOAuth, setHasStartedOAuth] = useState(resumedFromOAuth);
|
|
115
140
|
const [hasSeenActiveState, setHasSeenActiveState] = useState(resumedFromOAuth);
|
|
116
141
|
const [selectedAccountId, setSelectedAccountId] = useState<string>("");
|
|
142
|
+
const [selectedPageIds, setSelectedPageIds] = useState<ReadonlySet<string>>(new Set());
|
|
117
143
|
const hasDismissedRef = useRef(false);
|
|
118
144
|
|
|
119
145
|
const progressQuery = useMetaSetupProgressQuery(mantaClient, undefined, { staleTime: 0 });
|
|
120
146
|
const progress = progressQuery.data?.metaSetupProgress;
|
|
121
|
-
const
|
|
147
|
+
const connectMutation = useMetaSetupConnectMutation(mantaClient);
|
|
148
|
+
const includePagesMutation = useMetaSetupIncludePagesMutation(mantaClient);
|
|
149
|
+
// Progress reads the old step until a mutation's step has run, so a pending one shows where it is heading.
|
|
150
|
+
const phase = connectMutation.isPending
|
|
151
|
+
? "loadingPages"
|
|
152
|
+
: includePagesMutation.isPending
|
|
153
|
+
? "submitting"
|
|
154
|
+
: derivePhase({ states: progress?.state, hasStartedOAuth, hasSeenActiveState });
|
|
155
|
+
const pages = progress?.pages ?? [];
|
|
122
156
|
|
|
123
157
|
useWindowFocusPoll(progressQuery.refetch, { enabled: activePhases.includes(phase), intervalMs: pollIntervalMs });
|
|
124
158
|
|
|
@@ -139,8 +173,6 @@ export function MetaAdsConnectForm(props: ConnectFormProps) {
|
|
|
139
173
|
);
|
|
140
174
|
const reconnectableExternalIds = useReconnectableExternalIds(GQLIntegration.Meta);
|
|
141
175
|
|
|
142
|
-
const connectMutation = useMetaSetupConnectMutation(mantaClient);
|
|
143
|
-
|
|
144
176
|
useEffect(() => {
|
|
145
177
|
if (phase !== "done" || hasDismissedRef.current) return;
|
|
146
178
|
hasDismissedRef.current = true;
|
|
@@ -149,10 +181,12 @@ export function MetaAdsConnectForm(props: ConnectFormProps) {
|
|
|
149
181
|
if (!importNotice) onDismiss();
|
|
150
182
|
}, [phase, onChanged, onDismiss, importNotice]);
|
|
151
183
|
|
|
184
|
+
function setProgress(metaSetupProgress: GQLMetaSetupProgress) {
|
|
185
|
+
queryClient.setQueryData(useMetaSetupProgressQuery.getKey(), { metaSetupProgress });
|
|
186
|
+
}
|
|
187
|
+
|
|
152
188
|
function resetProgressCache() {
|
|
153
|
-
|
|
154
|
-
metaSetupProgress: { state: [], accounts: null, lastError: null },
|
|
155
|
-
});
|
|
189
|
+
setProgress({ state: [], accounts: null, pages: null, lastError: null });
|
|
156
190
|
}
|
|
157
191
|
|
|
158
192
|
function handleOpened() {
|
|
@@ -164,16 +198,34 @@ export function MetaAdsConnectForm(props: ConnectFormProps) {
|
|
|
164
198
|
async function handleSubmit() {
|
|
165
199
|
if (!selectedAccountId) return;
|
|
166
200
|
try {
|
|
167
|
-
await connectMutation.mutateAsync({ accountIds: [selectedAccountId] });
|
|
168
|
-
await progressQuery.refetch();
|
|
201
|
+
setProgress((await connectMutation.mutateAsync({ accountIds: [selectedAccountId] })).metaSetupConnect);
|
|
169
202
|
} catch (err) {
|
|
170
203
|
const info = parseError(err, "Failed to connect Meta");
|
|
171
204
|
toast.error(info.message, { description: info.description });
|
|
172
205
|
}
|
|
173
206
|
}
|
|
174
207
|
|
|
208
|
+
async function handleIncludePages() {
|
|
209
|
+
try {
|
|
210
|
+
setProgress((await includePagesMutation.mutateAsync({ pageIds: [...selectedPageIds] })).metaSetupIncludePages);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
const info = parseError(err, "Failed to connect Meta");
|
|
213
|
+
toast.error(info.message, { description: info.description });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function togglePage(id: string, checked: boolean) {
|
|
218
|
+
setSelectedPageIds((current) => {
|
|
219
|
+
const next = new Set(current);
|
|
220
|
+
if (checked) next.add(id);
|
|
221
|
+
else next.delete(id);
|
|
222
|
+
return next;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
175
226
|
function handleRetry() {
|
|
176
227
|
setSelectedAccountId("");
|
|
228
|
+
setSelectedPageIds(new Set());
|
|
177
229
|
hasDismissedRef.current = false;
|
|
178
230
|
resetProgressCache();
|
|
179
231
|
setHasStartedOAuth(false);
|
|
@@ -198,6 +250,10 @@ export function MetaAdsConnectForm(props: ConnectFormProps) {
|
|
|
198
250
|
onSelect={setSelectedAccountId}
|
|
199
251
|
/>
|
|
200
252
|
))
|
|
253
|
+
.with("loadingPages", () => <LoadingPagesView />)
|
|
254
|
+
.with("pickingPages", () => (
|
|
255
|
+
<PickingPagesView pages={pages} selectedIds={selectedPageIds} onToggle={togglePage} />
|
|
256
|
+
))
|
|
201
257
|
.with("submitting", () => <SubmittingView />)
|
|
202
258
|
.with("done", () => <DoneView providerName={provider.name} importNotice={importNotice} />)
|
|
203
259
|
.with("failed", () => (
|
|
@@ -220,12 +276,16 @@ export function MetaAdsConnectForm(props: ConnectFormProps) {
|
|
|
220
276
|
<OAuthConnectLink href="/oauth/meta/start" label="Continue with Meta" onOpened={handleOpened} />
|
|
221
277
|
)}
|
|
222
278
|
{phase === "pickingAccount" && (
|
|
279
|
+
<Button onClick={() => void handleSubmit()} disabled={!selectedAccountId}>
|
|
280
|
+
Continue
|
|
281
|
+
</Button>
|
|
282
|
+
)}
|
|
283
|
+
{phase === "pickingPages" && (
|
|
223
284
|
<Button
|
|
224
|
-
onClick={() => void
|
|
225
|
-
disabled={!
|
|
226
|
-
isLoading={connectMutation.isPending}
|
|
285
|
+
onClick={() => void handleIncludePages()}
|
|
286
|
+
disabled={isPresent(pages) && !picksAPage(pages, selectedPageIds)}
|
|
227
287
|
>
|
|
228
|
-
Connect
|
|
288
|
+
{isPresent(pages) ? "Connect" : "Connect without a Page"}
|
|
229
289
|
</Button>
|
|
230
290
|
)}
|
|
231
291
|
{phase === "done" && importNotice && <Button onClick={onDismiss}>Done</Button>}
|
|
@@ -348,12 +408,68 @@ function PickingAccountView(props: PickingAccountViewProps) {
|
|
|
348
408
|
);
|
|
349
409
|
}
|
|
350
410
|
|
|
411
|
+
function LoadingPagesView() {
|
|
412
|
+
return (
|
|
413
|
+
<div className="grid gap-4">
|
|
414
|
+
<div className="grid gap-1">
|
|
415
|
+
<h2 className="typography-large-body-semibold text-default-foreground">Loading your Facebook Pages</h2>
|
|
416
|
+
<p className="typography-caption text-muted-foreground">
|
|
417
|
+
The Pages this ad account can advertise as, and their Instagram accounts.
|
|
418
|
+
</p>
|
|
419
|
+
</div>
|
|
420
|
+
<div
|
|
421
|
+
className="flex items-center gap-2 text-muted-foreground typography-caption"
|
|
422
|
+
role="status"
|
|
423
|
+
aria-live="polite"
|
|
424
|
+
>
|
|
425
|
+
<Loader className="size-4" aria-hidden /> Fetching Pages…
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
type PickingPagesViewProps = {
|
|
432
|
+
pages: GQLMetaPage[];
|
|
433
|
+
selectedIds: ReadonlySet<string>;
|
|
434
|
+
onToggle: (id: string, checked: boolean) => void;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
function PickingPagesView(props: PickingPagesViewProps) {
|
|
438
|
+
const { pages, selectedIds, onToggle } = props;
|
|
439
|
+
|
|
440
|
+
if (!isPresent(pages)) {
|
|
441
|
+
return (
|
|
442
|
+
<Banner
|
|
443
|
+
type="warning"
|
|
444
|
+
label="This ad account can't advertise as any Facebook Page yet. Connect it anyway, and add a Page to it in Meta Business Suite before you run ads."
|
|
445
|
+
/>
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return (
|
|
450
|
+
<div className="grid gap-4">
|
|
451
|
+
<div className="grid gap-1">
|
|
452
|
+
<h2 className="typography-large-body-semibold text-default-foreground">Pick the Facebook Pages to use</h2>
|
|
453
|
+
<p className="typography-caption text-muted-foreground">
|
|
454
|
+
Manta runs ads only as the Pages and Instagram accounts you pick. Pick at least one Page.
|
|
455
|
+
</p>
|
|
456
|
+
</div>
|
|
457
|
+
<TreeMultiSelect
|
|
458
|
+
items={metaPageTreeItems(pages)}
|
|
459
|
+
selectedIds={selectedIds}
|
|
460
|
+
onToggle={onToggle}
|
|
461
|
+
searchPlaceholder="Search Pages..."
|
|
462
|
+
/>
|
|
463
|
+
</div>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
351
467
|
function SubmittingView() {
|
|
352
468
|
return (
|
|
353
469
|
<div className="grid gap-4">
|
|
354
470
|
<div className="grid gap-1">
|
|
355
471
|
<h2 className="typography-large-body-semibold text-default-foreground">Connecting your account</h2>
|
|
356
|
-
<p className="typography-caption text-muted-foreground">
|
|
472
|
+
<p className="typography-caption text-muted-foreground">This can take a minute. Keep this window open.</p>
|
|
357
473
|
</div>
|
|
358
474
|
<div
|
|
359
475
|
className="flex items-center gap-2 text-muted-foreground typography-caption"
|