@stigmer/react 3.12.2 → 3.12.3
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/access/ManageAccessDialog.d.ts.map +1 -1
- package/access/ManageAccessDialog.js +3 -19
- package/access/ManageAccessDialog.js.map +1 -1
- package/agent-instance/CreateAgentInstanceDialog.d.ts.map +1 -1
- package/agent-instance/CreateAgentInstanceDialog.js +7 -20
- package/agent-instance/CreateAgentInstanceDialog.js.map +1 -1
- package/attachment/AttachmentImageLightbox.d.ts.map +1 -1
- package/attachment/AttachmentImageLightbox.js +8 -23
- package/attachment/AttachmentImageLightbox.js.map +1 -1
- package/channel/ChannelConversationsDialog.d.ts.map +1 -1
- package/channel/ChannelConversationsDialog.js +3 -19
- package/channel/ChannelConversationsDialog.js.map +1 -1
- package/channel/ChannelCredentialsDialog.d.ts.map +1 -1
- package/channel/ChannelCredentialsDialog.js +3 -19
- package/channel/ChannelCredentialsDialog.js.map +1 -1
- package/channel/ChannelTemplatesDialog.d.ts.map +1 -1
- package/channel/ChannelTemplatesDialog.js +4 -20
- package/channel/ChannelTemplatesDialog.js.map +1 -1
- package/channel/ConnectSlackDialog.d.ts.map +1 -1
- package/channel/ConnectSlackDialog.js +3 -21
- package/channel/ConnectSlackDialog.js.map +1 -1
- package/channel/ConnectWhatsAppDialog.d.ts.map +1 -1
- package/channel/ConnectWhatsAppDialog.js +3 -21
- package/channel/ConnectWhatsAppDialog.js.map +1 -1
- package/conversation/ConversationAttentionBanner.d.ts +23 -6
- package/conversation/ConversationAttentionBanner.d.ts.map +1 -1
- package/conversation/ConversationAttentionBanner.js +25 -10
- package/conversation/ConversationAttentionBanner.js.map +1 -1
- package/conversation/ConversationListPane.d.ts.map +1 -1
- package/conversation/ConversationListPane.js +25 -6
- package/conversation/ConversationListPane.js.map +1 -1
- package/conversation/ConversationsWorkbench.d.ts.map +1 -1
- package/conversation/ConversationsWorkbench.js +1 -1
- package/conversation/ConversationsWorkbench.js.map +1 -1
- package/execution/ApprovalCard.d.ts.map +1 -1
- package/execution/ApprovalCard.js +13 -3
- package/execution/ApprovalCard.js.map +1 -1
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +11 -17
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ToolCallItem.d.ts +3 -1
- package/execution/ToolCallItem.d.ts.map +1 -1
- package/execution/ToolCallItem.js +19 -7
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/index.d.ts +1 -1
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +1 -1
- package/execution/index.js.map +1 -1
- package/execution/tool-categories.d.ts +51 -0
- package/execution/tool-categories.d.ts.map +1 -1
- package/execution/tool-categories.js +103 -1
- package/execution/tool-categories.js.map +1 -1
- package/execution/tool-presenter.d.ts +15 -0
- package/execution/tool-presenter.d.ts.map +1 -1
- package/execution/tool-presenter.js +7 -2
- package/execution/tool-presenter.js.map +1 -1
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/internal/DialogShell.d.ts +77 -0
- package/internal/DialogShell.d.ts.map +1 -0
- package/internal/DialogShell.js +77 -0
- package/internal/DialogShell.js.map +1 -0
- package/manifest/ApplyManifestDialog.d.ts.map +1 -1
- package/manifest/ApplyManifestDialog.js +7 -17
- package/manifest/ApplyManifestDialog.js.map +1 -1
- package/manifest/EditResourceYamlDialog.d.ts.map +1 -1
- package/manifest/EditResourceYamlDialog.js +3 -19
- package/manifest/EditResourceYamlDialog.js.map +1 -1
- package/mcp-server/McpServerConnectDialog.d.ts.map +1 -1
- package/mcp-server/McpServerConnectDialog.js +8 -20
- package/mcp-server/McpServerConnectDialog.js.map +1 -1
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +4 -15
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/package.json +4 -4
- package/resource-detail/ConfirmDialog.d.ts +2 -3
- package/resource-detail/ConfirmDialog.d.ts.map +1 -1
- package/resource-detail/ConfirmDialog.js +8 -20
- package/resource-detail/ConfirmDialog.js.map +1 -1
- package/sharing/ShareAgentDialog.d.ts.map +1 -1
- package/sharing/ShareAgentDialog.js +2 -20
- package/sharing/ShareAgentDialog.js.map +1 -1
- package/skill/SkillDiffDialog.d.ts.map +1 -1
- package/skill/SkillDiffDialog.js +5 -21
- package/skill/SkillDiffDialog.js.map +1 -1
- package/src/access/ManageAccessDialog.tsx +7 -27
- package/src/agent-instance/CreateAgentInstanceDialog.tsx +14 -27
- package/src/attachment/AttachmentImageLightbox.tsx +13 -36
- package/src/channel/ChannelConversationsDialog.tsx +8 -27
- package/src/channel/ChannelCredentialsDialog.tsx +8 -28
- package/src/channel/ChannelTemplatesDialog.tsx +10 -29
- package/src/channel/ConnectSlackDialog.tsx +8 -30
- package/src/channel/ConnectWhatsAppDialog.tsx +8 -30
- package/src/conversation/ConversationAttentionBanner.tsx +64 -19
- package/src/conversation/ConversationListPane.tsx +42 -17
- package/src/conversation/ConversationsWorkbench.tsx +1 -0
- package/src/conversation/__tests__/ConversationAttentionBanner.test.tsx +76 -2
- package/src/conversation/__tests__/ConversationListPane.test.tsx +15 -4
- package/src/execution/ApprovalCard.tsx +23 -2
- package/src/execution/ArtifactPreviewModal.tsx +15 -27
- package/src/execution/ToolCallItem.tsx +19 -8
- package/src/execution/__tests__/intent-title.test.tsx +221 -0
- package/src/execution/index.ts +3 -0
- package/src/execution/tool-categories.ts +123 -1
- package/src/execution/tool-presenter.ts +22 -1
- package/src/index.ts +3 -0
- package/src/internal/DialogShell.tsx +169 -0
- package/src/internal/__tests__/DialogShell.test.tsx +223 -0
- package/src/manifest/ApplyManifestDialog.tsx +8 -26
- package/src/manifest/EditResourceYamlDialog.tsx +4 -31
- package/src/mcp-server/McpServerConnectDialog.tsx +15 -29
- package/src/mcp-server/McpServerDetailView.tsx +12 -24
- package/src/resource-detail/ConfirmDialog.tsx +9 -30
- package/src/sharing/ShareAgentDialog.tsx +7 -29
- package/src/skill/SkillDiffDialog.tsx +13 -36
- package/src/workflow/ViewYamlDialog.tsx +18 -37
- package/src/workflow/WorkflowArchitectDialog.tsx +11 -43
- package/src/workflow/WorkflowExplainDialog.tsx +11 -37
- package/src/workflow/WorkflowGraphFullscreenDialog.tsx +14 -26
- package/src/workflow/WorkflowRunDialog.tsx +21 -48
- package/src/workflow/execution-comparison/ExecutionComparisonPicker.tsx +14 -31
- package/src/workflow/instance/CreateWorkflowInstanceDialog.tsx +14 -27
- package/src/workflow/templates/WorkflowTemplatePreview.tsx +18 -28
- package/styles.css +1 -1
- package/workflow/ViewYamlDialog.d.ts.map +1 -1
- package/workflow/ViewYamlDialog.js +10 -29
- package/workflow/ViewYamlDialog.js.map +1 -1
- package/workflow/WorkflowArchitectDialog.d.ts.map +1 -1
- package/workflow/WorkflowArchitectDialog.js +5 -24
- package/workflow/WorkflowArchitectDialog.js.map +1 -1
- package/workflow/WorkflowExplainDialog.d.ts.map +1 -1
- package/workflow/WorkflowExplainDialog.js +5 -21
- package/workflow/WorkflowExplainDialog.js.map +1 -1
- package/workflow/WorkflowGraphFullscreenDialog.d.ts.map +1 -1
- package/workflow/WorkflowGraphFullscreenDialog.js +11 -17
- package/workflow/WorkflowGraphFullscreenDialog.js.map +1 -1
- package/workflow/WorkflowRunDialog.d.ts.map +1 -1
- package/workflow/WorkflowRunDialog.js +13 -29
- package/workflow/WorkflowRunDialog.js.map +1 -1
- package/workflow/execution-comparison/ExecutionComparisonPicker.d.ts.map +1 -1
- package/workflow/execution-comparison/ExecutionComparisonPicker.js +8 -26
- package/workflow/execution-comparison/ExecutionComparisonPicker.js.map +1 -1
- package/workflow/instance/CreateWorkflowInstanceDialog.d.ts.map +1 -1
- package/workflow/instance/CreateWorkflowInstanceDialog.js +7 -20
- package/workflow/instance/CreateWorkflowInstanceDialog.js.map +1 -1
- package/workflow/templates/WorkflowTemplatePreview.d.ts +1 -1
- package/workflow/templates/WorkflowTemplatePreview.d.ts.map +1 -1
- package/workflow/templates/WorkflowTemplatePreview.js +10 -22
- package/workflow/templates/WorkflowTemplatePreview.js.map +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useCallback, useId,
|
|
3
|
+
import { useCallback, useId, useState } from "react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { DialogShell } from "../internal/DialogShell.js";
|
|
5
6
|
import { getUserMessage, type ResourceRef } from "@stigmer/sdk";
|
|
6
7
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
7
8
|
import type { AgentChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
|
|
@@ -51,8 +52,6 @@ export function ChannelCredentialsDialog({
|
|
|
51
52
|
onSaved,
|
|
52
53
|
modal = true,
|
|
53
54
|
}: ChannelCredentialsDialogProps) {
|
|
54
|
-
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
55
|
-
|
|
56
55
|
// Instance-scoped title id (oss#593): a reusable component must not
|
|
57
56
|
// hardcode DOM ids — hosts legitimately mount this dialog more than once
|
|
58
57
|
// per page (e.g. zone-cached detail pages), and duplicate ids break the
|
|
@@ -60,34 +59,15 @@ export function ChannelCredentialsDialog({
|
|
|
60
59
|
const titleId = useId();
|
|
61
60
|
|
|
62
61
|
const handleClose = useCallback(() => {
|
|
63
|
-
dialogRef.current?.close();
|
|
64
62
|
onOpenChange(false);
|
|
65
63
|
}, [onOpenChange]);
|
|
66
64
|
|
|
67
|
-
// Sync native dialog open state (matches the SDK dialog convention).
|
|
68
|
-
const prevOpenRef = useRef(false);
|
|
69
|
-
if (modal && open !== prevOpenRef.current) {
|
|
70
|
-
prevOpenRef.current = open;
|
|
71
|
-
if (open) {
|
|
72
|
-
requestAnimationFrame(() => {
|
|
73
|
-
if (dialogRef.current && !dialogRef.current.open) {
|
|
74
|
-
dialogRef.current.showModal();
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
} else if (dialogRef.current?.open) {
|
|
78
|
-
dialogRef.current.close();
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
65
|
return (
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"stg:w-full stg:max-w-md stg:rounded-xl stg:border stg:border-border stg:bg-popover stg:p-0 stg:shadow-xl",
|
|
89
|
-
modal ? "stg:fixed stg:inset-0 stg:m-auto stg:backdrop:bg-backdrop" : "stg:relative",
|
|
90
|
-
)}
|
|
66
|
+
<DialogShell
|
|
67
|
+
open={open}
|
|
68
|
+
onOpenChange={onOpenChange}
|
|
69
|
+
modal={modal}
|
|
70
|
+
width="md"
|
|
91
71
|
aria-labelledby={titleId}
|
|
92
72
|
>
|
|
93
73
|
{/* Body mounts only while open so its draft resets per session —
|
|
@@ -101,7 +81,7 @@ export function ChannelCredentialsDialog({
|
|
|
101
81
|
titleId={titleId}
|
|
102
82
|
/>
|
|
103
83
|
)}
|
|
104
|
-
</
|
|
84
|
+
</DialogShell>
|
|
105
85
|
);
|
|
106
86
|
}
|
|
107
87
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useCallback, useId,
|
|
3
|
+
import { useCallback, useId, type ReactNode } from "react";
|
|
4
4
|
import { ExternalLink, LayoutTemplate, X } from "lucide-react";
|
|
5
5
|
import { cn } from "@stigmer/theme";
|
|
6
|
+
import { DialogShell } from "../internal/DialogShell.js";
|
|
6
7
|
import { getUserMessage } from "@stigmer/sdk";
|
|
7
8
|
import type { AgentChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
|
|
8
9
|
import type { ChannelTemplate } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
|
|
@@ -89,40 +90,20 @@ export function ChannelTemplatesDialog({
|
|
|
89
90
|
onEditYaml,
|
|
90
91
|
modal = true,
|
|
91
92
|
}: ChannelTemplatesDialogProps) {
|
|
92
|
-
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
93
93
|
const titleId = useId();
|
|
94
94
|
|
|
95
95
|
const handleClose = useCallback(() => {
|
|
96
|
-
dialogRef.current?.close();
|
|
97
96
|
onOpenChange(false);
|
|
98
97
|
}, [onOpenChange]);
|
|
99
98
|
|
|
100
|
-
// Sync native dialog open state (matches the SDK dialog convention).
|
|
101
|
-
const prevOpenRef = useRef(false);
|
|
102
|
-
if (modal && open !== prevOpenRef.current) {
|
|
103
|
-
prevOpenRef.current = open;
|
|
104
|
-
if (open) {
|
|
105
|
-
requestAnimationFrame(() => {
|
|
106
|
-
if (dialogRef.current && !dialogRef.current.open) {
|
|
107
|
-
dialogRef.current.showModal();
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
} else if (dialogRef.current?.open) {
|
|
111
|
-
dialogRef.current.close();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
99
|
return (
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"stg:w-full stg:max-w-2xl stg:rounded-xl stg:border stg:border-border stg:bg-popover stg:p-0 stg:shadow-xl",
|
|
124
|
-
modal ? "stg:fixed stg:inset-0 stg:m-auto stg:backdrop:bg-backdrop" : "stg:relative",
|
|
125
|
-
)}
|
|
100
|
+
<DialogShell
|
|
101
|
+
open={open}
|
|
102
|
+
onOpenChange={onOpenChange}
|
|
103
|
+
modal={modal}
|
|
104
|
+
// Wider than the conversations dialog on purpose: template
|
|
105
|
+
// bodies are the content, and a narrow column shreds them.
|
|
106
|
+
width="2xl"
|
|
126
107
|
aria-labelledby={titleId}
|
|
127
108
|
>
|
|
128
109
|
{/* Body mounts only while open so each opening fetches fresh —
|
|
@@ -135,7 +116,7 @@ export function ChannelTemplatesDialog({
|
|
|
135
116
|
onClose={handleClose}
|
|
136
117
|
/>
|
|
137
118
|
)}
|
|
138
|
-
</
|
|
119
|
+
</DialogShell>
|
|
139
120
|
);
|
|
140
121
|
}
|
|
141
122
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useCallback, useId, useMemo,
|
|
3
|
+
import { useCallback, useId, useMemo, useState } from "react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { DialogShell } from "../internal/DialogShell.js";
|
|
5
6
|
import { getErrorReason, type ResourceRef } from "@stigmer/sdk";
|
|
6
7
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
7
8
|
import type { AgentChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
|
|
@@ -89,8 +90,6 @@ export function ConnectSlackDialog({
|
|
|
89
90
|
modal = true,
|
|
90
91
|
channelAppsHref,
|
|
91
92
|
}: ConnectSlackDialogProps) {
|
|
92
|
-
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
93
|
-
|
|
94
93
|
// Instance-scoped title id (oss#593): a reusable component must not
|
|
95
94
|
// hardcode DOM ids — hosts legitimately mount this dialog more than once
|
|
96
95
|
// per page (e.g. zone-cached detail pages), and duplicate ids break the
|
|
@@ -98,36 +97,15 @@ export function ConnectSlackDialog({
|
|
|
98
97
|
const titleId = useId();
|
|
99
98
|
|
|
100
99
|
const handleClose = useCallback(() => {
|
|
101
|
-
dialogRef.current?.close();
|
|
102
100
|
onOpenChange(false);
|
|
103
101
|
}, [onOpenChange]);
|
|
104
102
|
|
|
105
|
-
// Sync native dialog open state (matches the SDK dialog convention).
|
|
106
|
-
// Non-modal hosts pass `open` as a plain attribute instead — the dialog
|
|
107
|
-
// renders in-flow with no top layer to manage.
|
|
108
|
-
const prevOpenRef = useRef(false);
|
|
109
|
-
if (modal && open !== prevOpenRef.current) {
|
|
110
|
-
prevOpenRef.current = open;
|
|
111
|
-
if (open) {
|
|
112
|
-
requestAnimationFrame(() => {
|
|
113
|
-
if (dialogRef.current && !dialogRef.current.open) {
|
|
114
|
-
dialogRef.current.showModal();
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
} else if (dialogRef.current?.open) {
|
|
118
|
-
dialogRef.current.close();
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
103
|
return (
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
"stg:w-full stg:max-w-md stg:rounded-xl stg:border stg:border-border stg:bg-popover stg:p-0 stg:shadow-xl",
|
|
129
|
-
modal ? "stg:fixed stg:inset-0 stg:m-auto stg:backdrop:bg-backdrop" : "stg:relative",
|
|
130
|
-
)}
|
|
104
|
+
<DialogShell
|
|
105
|
+
open={open}
|
|
106
|
+
onOpenChange={onOpenChange}
|
|
107
|
+
modal={modal}
|
|
108
|
+
width="md"
|
|
131
109
|
aria-labelledby={titleId}
|
|
132
110
|
>
|
|
133
111
|
{/* Body mounts only while open so its flow state resets per
|
|
@@ -142,7 +120,7 @@ export function ConnectSlackDialog({
|
|
|
142
120
|
channelAppsHref={channelAppsHref}
|
|
143
121
|
/>
|
|
144
122
|
)}
|
|
145
|
-
</
|
|
123
|
+
</DialogShell>
|
|
146
124
|
);
|
|
147
125
|
}
|
|
148
126
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useCallback, useId, useMemo,
|
|
3
|
+
import { useCallback, useId, useMemo, useState } from "react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { DialogShell } from "../internal/DialogShell.js";
|
|
5
6
|
import { getErrorReason, type ResourceRef } from "@stigmer/sdk";
|
|
6
7
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
7
8
|
import type { AgentChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
|
|
@@ -100,8 +101,6 @@ export function ConnectWhatsAppDialog({
|
|
|
100
101
|
modal = true,
|
|
101
102
|
channelAppsHref,
|
|
102
103
|
}: ConnectWhatsAppDialogProps) {
|
|
103
|
-
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
104
|
-
|
|
105
104
|
// Instance-scoped title id (oss#593): a reusable component must not
|
|
106
105
|
// hardcode DOM ids — hosts legitimately mount this dialog more than once
|
|
107
106
|
// per page (e.g. zone-cached detail pages), and duplicate ids break the
|
|
@@ -109,36 +108,15 @@ export function ConnectWhatsAppDialog({
|
|
|
109
108
|
const titleId = useId();
|
|
110
109
|
|
|
111
110
|
const handleClose = useCallback(() => {
|
|
112
|
-
dialogRef.current?.close();
|
|
113
111
|
onOpenChange(false);
|
|
114
112
|
}, [onOpenChange]);
|
|
115
113
|
|
|
116
|
-
// Sync native dialog open state (matches the SDK dialog convention).
|
|
117
|
-
// Non-modal hosts pass `open` as a plain attribute instead — the dialog
|
|
118
|
-
// renders in-flow with no top layer to manage.
|
|
119
|
-
const prevOpenRef = useRef(false);
|
|
120
|
-
if (modal && open !== prevOpenRef.current) {
|
|
121
|
-
prevOpenRef.current = open;
|
|
122
|
-
if (open) {
|
|
123
|
-
requestAnimationFrame(() => {
|
|
124
|
-
if (dialogRef.current && !dialogRef.current.open) {
|
|
125
|
-
dialogRef.current.showModal();
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
} else if (dialogRef.current?.open) {
|
|
129
|
-
dialogRef.current.close();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
114
|
return (
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
"stg:w-full stg:max-w-md stg:rounded-xl stg:border stg:border-border stg:bg-popover stg:p-0 stg:shadow-xl",
|
|
140
|
-
modal ? "stg:fixed stg:inset-0 stg:m-auto stg:backdrop:bg-backdrop" : "stg:relative",
|
|
141
|
-
)}
|
|
115
|
+
<DialogShell
|
|
116
|
+
open={open}
|
|
117
|
+
onOpenChange={onOpenChange}
|
|
118
|
+
modal={modal}
|
|
119
|
+
width="md"
|
|
142
120
|
aria-labelledby={titleId}
|
|
143
121
|
>
|
|
144
122
|
{/* Body mounts only while open so its flow state resets per
|
|
@@ -153,7 +131,7 @@ export function ConnectWhatsAppDialog({
|
|
|
153
131
|
titleId={titleId}
|
|
154
132
|
/>
|
|
155
133
|
)}
|
|
156
|
-
</
|
|
134
|
+
</DialogShell>
|
|
157
135
|
);
|
|
158
136
|
}
|
|
159
137
|
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
import { TriangleAlert } from "lucide-react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
5
|
import { getUserMessage } from "@stigmer/sdk";
|
|
6
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
ConversationControl,
|
|
8
|
+
type ChannelConversation,
|
|
9
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
|
|
7
10
|
import { Button } from "../button/Button.js";
|
|
8
11
|
import type { UseConversationParticipationReturn } from "./useConversationParticipation.js";
|
|
9
12
|
|
|
@@ -13,28 +16,51 @@ export interface ConversationAttentionBannerProps {
|
|
|
13
16
|
readonly conversation: ChannelConversation;
|
|
14
17
|
/** The participation commands (from `useConversationParticipation`). */
|
|
15
18
|
readonly participation: UseConversationParticipationReturn;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the channel's provider has a staff send lane — the same
|
|
21
|
+
* fact `ConversationControlBanner` takes. When `false` the banner
|
|
22
|
+
* offers only Dismiss: a takeover would silence the agent with no way
|
|
23
|
+
* to reply, and the control banner beside this one already explains
|
|
24
|
+
* the missing lane, so a disabled Take over here would duplicate it.
|
|
25
|
+
* Defaults to `true` for hosts that only wire providers with staff
|
|
26
|
+
* lanes.
|
|
27
|
+
*/
|
|
28
|
+
readonly supportsStaffReplies?: boolean;
|
|
16
29
|
/** Additional classes for the banner container. */
|
|
17
30
|
readonly className?: string;
|
|
18
31
|
}
|
|
19
32
|
|
|
20
33
|
/**
|
|
21
34
|
* The needs-attention banner (channel-conversations DD-008): the
|
|
22
|
-
* escalating agent's reason, verbatim, with the
|
|
35
|
+
* escalating agent's reason, verbatim, with the escalation's ANSWER
|
|
36
|
+
* beside its false-alarm dismissal.
|
|
37
|
+
*
|
|
38
|
+
* Take over is the primary action because the human arriving IS the
|
|
39
|
+
* answer to an escalation — taking over clears attention structurally
|
|
40
|
+
* (cloud#266 / F-20: the answer used to live one banner away in
|
|
41
|
+
* equal-weight chrome, so staff read the plea and missed the response).
|
|
42
|
+
* It renders only while the agent holds the conversation on a channel
|
|
43
|
+
* with a staff lane; a human already holding it IS the attention
|
|
44
|
+
* answered, so only Dismiss remains.
|
|
23
45
|
*
|
|
24
46
|
* Dismiss is `clearAttention` — attention clears in place and control
|
|
25
|
-
* never moves
|
|
26
|
-
* human arriving IS the answer to an escalation). Renders nothing while
|
|
27
|
-
* the conversation is unflagged.
|
|
47
|
+
* never moves. Renders nothing while the conversation is unflagged.
|
|
28
48
|
*/
|
|
29
49
|
export function ConversationAttentionBanner({
|
|
30
50
|
conversation,
|
|
31
51
|
participation,
|
|
52
|
+
supportsStaffReplies = true,
|
|
32
53
|
className,
|
|
33
54
|
}: ConversationAttentionBannerProps) {
|
|
34
55
|
if (!conversation.needsAttention) return null;
|
|
35
56
|
|
|
36
|
-
const { clearAttention, pendingCommands, commandErrors } = participation;
|
|
37
|
-
|
|
57
|
+
const { takeOver, clearAttention, pendingCommands, commandErrors } = participation;
|
|
58
|
+
// Take over's failure renders on the control banner too — same
|
|
59
|
+
// command, same truth; a rare failure showing twice on the stacked
|
|
60
|
+
// banners beats it hiding beside the button the staffer pressed.
|
|
61
|
+
const error = commandErrors.get("clearAttention") ?? commandErrors.get("takeOver");
|
|
62
|
+
const humanHeld = conversation.control === ConversationControl.control_human;
|
|
63
|
+
const offersTakeOver = !humanHeld && supportsStaffReplies;
|
|
38
64
|
|
|
39
65
|
return (
|
|
40
66
|
<div
|
|
@@ -56,18 +82,37 @@ export function ConversationAttentionBanner({
|
|
|
56
82
|
)}
|
|
57
83
|
</span>
|
|
58
84
|
</p>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
{/* One shrink-proof group, right-aligned even after a wrap
|
|
86
|
+
(ml-auto): under a long reason the actions used to wrap to
|
|
87
|
+
the bottom-LEFT and read as part of the message (F-20). */}
|
|
88
|
+
<div className="stg:ml-auto stg:flex stg:shrink-0 stg:items-center stg:gap-2">
|
|
89
|
+
<Button
|
|
90
|
+
variant="outline"
|
|
91
|
+
size="xs"
|
|
92
|
+
onClick={() =>
|
|
93
|
+
void clearAttention().catch(() => {
|
|
94
|
+
// Recorded in commandErrors and rendered below.
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
disabled={pendingCommands.has("clearAttention")}
|
|
98
|
+
>
|
|
99
|
+
{pendingCommands.has("clearAttention") ? "Dismissing…" : "Dismiss"}
|
|
100
|
+
</Button>
|
|
101
|
+
{offersTakeOver && (
|
|
102
|
+
<Button
|
|
103
|
+
variant="primary"
|
|
104
|
+
size="xs"
|
|
105
|
+
onClick={() =>
|
|
106
|
+
void takeOver().catch(() => {
|
|
107
|
+
// Recorded in commandErrors and rendered below.
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
disabled={pendingCommands.has("takeOver")}
|
|
111
|
+
>
|
|
112
|
+
{pendingCommands.has("takeOver") ? "Taking over…" : "Take over"}
|
|
113
|
+
</Button>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
71
116
|
</div>
|
|
72
117
|
{error && (
|
|
73
118
|
<p className="stg:mt-1 stg:text-xs stg:text-destructive">{getUserMessage(error)}</p>
|
|
@@ -193,7 +193,11 @@ export function ConversationListPane({
|
|
|
193
193
|
{/* One context-only provider arms every row's attention
|
|
194
194
|
tooltip (the WorkspaceSidebar recents-list precedent). */}
|
|
195
195
|
<TooltipProvider>
|
|
196
|
-
|
|
196
|
+
{/* The SDK's own list reset (the MemberKeysPanel
|
|
197
|
+
convention): hosts without a global preflight would
|
|
198
|
+
otherwise render inbox rows with UA bullets and
|
|
199
|
+
indent. */}
|
|
200
|
+
<ul className="stg:m-0 stg:list-none stg:space-y-0.5 stg:p-0">
|
|
197
201
|
{conversations.map((conversation) => (
|
|
198
202
|
<ConversationRow
|
|
199
203
|
key={`${conversation.agentChannelId}:${conversation.conversationKey}`}
|
|
@@ -333,6 +337,20 @@ function ConversationFilterToggle({
|
|
|
333
337
|
);
|
|
334
338
|
}
|
|
335
339
|
|
|
340
|
+
/**
|
|
341
|
+
* The awaiting dot's meaning, shared verbatim by its tooltip and its
|
|
342
|
+
* sr-only text — one string per strength so sighted-hover and
|
|
343
|
+
* screen-reader users hear the identical fact. The copy carries the
|
|
344
|
+
* DISTINCTION, not just the fact (cloud#266): who holds the
|
|
345
|
+
* conversation is exactly what the two strengths encode, and it names
|
|
346
|
+
* the stake without promising an outcome — a muted dot survives a dead
|
|
347
|
+
* agent turn on purpose (DD-011 D-b's recovery path).
|
|
348
|
+
*/
|
|
349
|
+
const AWAITING_COPY: Record<"strong" | "muted", string> = {
|
|
350
|
+
strong: "Customer awaiting reply — a human has this conversation; the agent will not answer",
|
|
351
|
+
muted: "Customer awaiting reply — the agent has this conversation",
|
|
352
|
+
};
|
|
353
|
+
|
|
336
354
|
const ConversationRow = memo(function ConversationRow({
|
|
337
355
|
conversation,
|
|
338
356
|
label,
|
|
@@ -425,22 +443,29 @@ const ConversationRow = memo(function ConversationRow({
|
|
|
425
443
|
// carries a dot under its timestamp. Strength maps the
|
|
426
444
|
// holder (DD-011 D-a): strong when a human holds it — the
|
|
427
445
|
// agent will not answer, a person must — muted when the
|
|
428
|
-
// agent does.
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
/>
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
446
|
+
// agent does. Strong is FILLED and muted is a hollow RING
|
|
447
|
+
// (cloud#266): fill-vs-outline reads as "act vs watching"
|
|
448
|
+
// without decoding color, and color alone inverted in dark
|
|
449
|
+
// mode, where the muted gray outshone the strong primary.
|
|
450
|
+
// The meaning rides the house tooltip (the attention
|
|
451
|
+
// icon's exact pattern above — span trigger inside the row
|
|
452
|
+
// button, out of the tab order, sr-only as the accessible
|
|
453
|
+
// name); a bare dot told a first-time viewer nothing.
|
|
454
|
+
<Tooltip>
|
|
455
|
+
<TooltipTrigger render={<span className="stg:flex" />}>
|
|
456
|
+
<span
|
|
457
|
+
aria-hidden="true"
|
|
458
|
+
className={cn(
|
|
459
|
+
"stg:size-2 stg:rounded-full",
|
|
460
|
+
awaiting === "strong"
|
|
461
|
+
? "stg:bg-primary"
|
|
462
|
+
: "stg:border-2 stg:border-muted-foreground",
|
|
463
|
+
)}
|
|
464
|
+
/>
|
|
465
|
+
<span className="stg:sr-only">{AWAITING_COPY[awaiting]}</span>
|
|
466
|
+
</TooltipTrigger>
|
|
467
|
+
<TooltipContent side="top">{AWAITING_COPY[awaiting]}</TooltipContent>
|
|
468
|
+
</Tooltip>
|
|
444
469
|
)}
|
|
445
470
|
</span>
|
|
446
471
|
)}
|
|
@@ -340,6 +340,7 @@ export function ConversationsWorkbench({
|
|
|
340
340
|
<ConversationAttentionBanner
|
|
341
341
|
conversation={detail.conversation}
|
|
342
342
|
participation={participation}
|
|
343
|
+
supportsStaffReplies={descriptor?.supportsStaffReplies ?? true}
|
|
343
344
|
/>
|
|
344
345
|
)}
|
|
345
346
|
{detail.conversation && (
|
|
@@ -2,7 +2,10 @@ import { describe, it, expect, vi, afterEach } from "vitest";
|
|
|
2
2
|
import { cleanup, render, screen } from "@testing-library/react";
|
|
3
3
|
import userEvent from "@testing-library/user-event";
|
|
4
4
|
import { create } from "@bufbuild/protobuf";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ChannelConversationSchema,
|
|
7
|
+
ConversationControl,
|
|
8
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
|
|
6
9
|
import { ConversationAttentionBanner } from "../ConversationAttentionBanner";
|
|
7
10
|
import type {
|
|
8
11
|
ConversationCommand,
|
|
@@ -23,7 +26,7 @@ function participation(
|
|
|
23
26
|
): UseConversationParticipationReturn {
|
|
24
27
|
return {
|
|
25
28
|
reply: vi.fn(),
|
|
26
|
-
takeOver: vi.fn(),
|
|
29
|
+
takeOver: vi.fn().mockResolvedValue(conversation()),
|
|
27
30
|
handBack: vi.fn(),
|
|
28
31
|
clearAttention: vi.fn().mockResolvedValue(conversation()),
|
|
29
32
|
pendingCommands: new Set<ConversationCommand>(),
|
|
@@ -79,4 +82,75 @@ describe("ConversationAttentionBanner", () => {
|
|
|
79
82
|
);
|
|
80
83
|
expect(screen.getByText("agent channel ach_1 not found")).toBeDefined();
|
|
81
84
|
});
|
|
85
|
+
|
|
86
|
+
it("offers Take over beside Dismiss while the agent holds it — the escalation's answer lives where the escalation speaks (cloud#266, F-20)", async () => {
|
|
87
|
+
const user = userEvent.setup();
|
|
88
|
+
const p = participation();
|
|
89
|
+
render(
|
|
90
|
+
<ConversationAttentionBanner
|
|
91
|
+
conversation={conversation({ needsAttention: true })}
|
|
92
|
+
participation={p}
|
|
93
|
+
/>,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
await user.click(screen.getByRole("button", { name: "Take over" }));
|
|
97
|
+
expect(p.takeOver).toHaveBeenCalled();
|
|
98
|
+
// Dismiss stays — the false-alarm path never disappears.
|
|
99
|
+
expect(screen.getByRole("button", { name: "Dismiss" })).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("offers only Dismiss when a human already holds it — the human arriving IS the attention answered", () => {
|
|
103
|
+
render(
|
|
104
|
+
<ConversationAttentionBanner
|
|
105
|
+
conversation={conversation({
|
|
106
|
+
needsAttention: true,
|
|
107
|
+
control: ConversationControl.control_human,
|
|
108
|
+
controlledBy: "idt_staff",
|
|
109
|
+
})}
|
|
110
|
+
participation={participation()}
|
|
111
|
+
/>,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(screen.queryByRole("button", { name: "Take over" })).toBeNull();
|
|
115
|
+
expect(screen.getByRole("button", { name: "Dismiss" })).toBeDefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("offers only Dismiss on senderless providers — the control banner already explains the missing lane", () => {
|
|
119
|
+
render(
|
|
120
|
+
<ConversationAttentionBanner
|
|
121
|
+
conversation={conversation({ needsAttention: true })}
|
|
122
|
+
participation={participation()}
|
|
123
|
+
supportsStaffReplies={false}
|
|
124
|
+
/>,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(screen.queryByRole("button", { name: "Take over" })).toBeNull();
|
|
128
|
+
expect(screen.getByRole("button", { name: "Dismiss" })).toBeDefined();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("shows the takeover in flight and renders its failure verbatim", () => {
|
|
132
|
+
const { rerender } = render(
|
|
133
|
+
<ConversationAttentionBanner
|
|
134
|
+
conversation={conversation({ needsAttention: true })}
|
|
135
|
+
participation={participation({
|
|
136
|
+
pendingCommands: new Set<ConversationCommand>(["takeOver"]),
|
|
137
|
+
})}
|
|
138
|
+
/>,
|
|
139
|
+
);
|
|
140
|
+
expect(screen.getByRole("button", { name: "Taking over…" })).toBeDefined();
|
|
141
|
+
|
|
142
|
+
rerender(
|
|
143
|
+
<ConversationAttentionBanner
|
|
144
|
+
conversation={conversation({ needsAttention: true })}
|
|
145
|
+
participation={participation({
|
|
146
|
+
commandErrors: new Map([
|
|
147
|
+
["takeOver", new Error("this channel is disabled: set spec.enabled to resume messaging on it")],
|
|
148
|
+
]),
|
|
149
|
+
})}
|
|
150
|
+
/>,
|
|
151
|
+
);
|
|
152
|
+
expect(
|
|
153
|
+
screen.getByText("this channel is disabled: set spec.enabled to resume messaging on it"),
|
|
154
|
+
).toBeDefined();
|
|
155
|
+
});
|
|
82
156
|
});
|
|
@@ -145,7 +145,7 @@ describe("ConversationListPane", () => {
|
|
|
145
145
|
).toBeDefined();
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
it("uses the house tooltip for the attention badge and no native titles anywhere (F-18)", () => {
|
|
148
|
+
it("uses the house tooltip for the attention badge and awaiting dot, no native titles anywhere (F-18)", () => {
|
|
149
149
|
const { container } = render(
|
|
150
150
|
<ConversationListPane
|
|
151
151
|
{...baseProps()}
|
|
@@ -154,6 +154,9 @@ describe("ConversationListPane", () => {
|
|
|
154
154
|
displayName: "Pat with a very long name that will truncate",
|
|
155
155
|
needsAttention: true,
|
|
156
156
|
attentionReason: "refund I cannot process",
|
|
157
|
+
// The awaiting dot renders too, so its tooltip trigger is
|
|
158
|
+
// under the same no-title / no-tab-stop assertions below.
|
|
159
|
+
awaitingReply: true,
|
|
157
160
|
}),
|
|
158
161
|
]}
|
|
159
162
|
/>,
|
|
@@ -204,8 +207,12 @@ describe("ConversationListPane", () => {
|
|
|
204
207
|
/>,
|
|
205
208
|
);
|
|
206
209
|
|
|
210
|
+
// The copy names the stake, not just the fact (cloud#266): a
|
|
211
|
+
// human-held wait is one only a person can end.
|
|
207
212
|
expect(
|
|
208
|
-
screen.getByText(
|
|
213
|
+
screen.getByText(
|
|
214
|
+
"Customer awaiting reply — a human has this conversation; the agent will not answer",
|
|
215
|
+
),
|
|
209
216
|
).toBeDefined();
|
|
210
217
|
});
|
|
211
218
|
|
|
@@ -217,9 +224,13 @@ describe("ConversationListPane", () => {
|
|
|
217
224
|
/>,
|
|
218
225
|
);
|
|
219
226
|
|
|
220
|
-
expect(screen.getByText("Customer awaiting reply")).toBeDefined();
|
|
221
227
|
expect(
|
|
222
|
-
screen.
|
|
228
|
+
screen.getByText("Customer awaiting reply — the agent has this conversation"),
|
|
229
|
+
).toBeDefined();
|
|
230
|
+
expect(
|
|
231
|
+
screen.queryByText(
|
|
232
|
+
"Customer awaiting reply — a human has this conversation; the agent will not answer",
|
|
233
|
+
),
|
|
223
234
|
).toBeNull();
|
|
224
235
|
});
|
|
225
236
|
|