@stigmer/react 3.1.13 → 3.1.15
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/agent/AgentDetailView.d.ts +9 -9
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +23 -31
- package/agent/AgentDetailView.js.map +1 -1
- package/agent-instance/AgentInstanceEmptyState.d.ts +4 -1
- package/agent-instance/AgentInstanceEmptyState.d.ts.map +1 -1
- package/agent-instance/AgentInstanceEmptyState.js +12 -5
- package/agent-instance/AgentInstanceEmptyState.js.map +1 -1
- package/agent-instance/AgentInstanceList.d.ts +13 -2
- package/agent-instance/AgentInstanceList.d.ts.map +1 -1
- package/agent-instance/AgentInstanceList.js +8 -4
- package/agent-instance/AgentInstanceList.js.map +1 -1
- package/agent-instance/useAgentInstances.d.ts +10 -3
- package/agent-instance/useAgentInstances.d.ts.map +1 -1
- package/agent-instance/useAgentInstances.js +12 -4
- package/agent-instance/useAgentInstances.js.map +1 -1
- package/button/Button.d.ts +54 -0
- package/button/Button.d.ts.map +1 -0
- package/button/Button.js +42 -0
- package/button/Button.js.map +1 -0
- package/button/index.d.ts +3 -0
- package/button/index.d.ts.map +1 -0
- package/button/index.js +2 -0
- package/button/index.js.map +1 -0
- package/empty-state/EmptyState.d.ts.map +1 -1
- package/empty-state/EmptyState.js +8 -3
- package/empty-state/EmptyState.js.map +1 -1
- package/empty-state/types.d.ts +2 -0
- package/empty-state/types.d.ts.map +1 -1
- package/execution/index.d.ts +1 -1
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +81 -9
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +42 -4
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +5 -3
- package/index.d.ts.map +1 -1
- package/index.js +4 -2
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/session/useNewSessionFlow.d.ts +6 -3
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +34 -35
- package/session/useNewSessionFlow.js.map +1 -1
- package/sharing/AgentShareList.d.ts +46 -0
- package/sharing/AgentShareList.d.ts.map +1 -0
- package/sharing/AgentShareList.js +159 -0
- package/sharing/AgentShareList.js.map +1 -0
- package/sharing/ShareAgentDialog.d.ts +38 -25
- package/sharing/ShareAgentDialog.d.ts.map +1 -1
- package/sharing/ShareAgentDialog.js +131 -62
- package/sharing/ShareAgentDialog.js.map +1 -1
- package/sharing/index.d.ts +10 -8
- package/sharing/index.d.ts.map +1 -1
- package/sharing/index.js +5 -4
- package/sharing/index.js.map +1 -1
- package/sharing/useAgentShares.d.ts +48 -0
- package/sharing/useAgentShares.d.ts.map +1 -0
- package/sharing/useAgentShares.js +44 -0
- package/sharing/useAgentShares.js.map +1 -0
- package/sharing/useCanCreateAgentShare.d.ts +43 -0
- package/sharing/useCanCreateAgentShare.d.ts.map +1 -0
- package/sharing/useCanCreateAgentShare.js +44 -0
- package/sharing/useCanCreateAgentShare.js.map +1 -0
- package/sharing/useDeleteAgentShare.d.ts +42 -0
- package/sharing/useDeleteAgentShare.d.ts.map +1 -0
- package/sharing/useDeleteAgentShare.js +45 -0
- package/sharing/useDeleteAgentShare.js.map +1 -0
- package/sharing/useSaveAgentShare.d.ts +43 -18
- package/sharing/useSaveAgentShare.d.ts.map +1 -1
- package/sharing/useSaveAgentShare.js +55 -22
- package/sharing/useSaveAgentShare.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +37 -43
- package/src/agent-instance/AgentInstanceEmptyState.tsx +24 -43
- package/src/agent-instance/AgentInstanceList.tsx +29 -16
- package/src/agent-instance/__tests__/agent-instance-hooks.test.tsx +19 -0
- package/src/agent-instance/useAgentInstances.ts +12 -3
- package/src/button/Button.tsx +104 -0
- package/src/button/__tests__/Button.test.tsx +82 -0
- package/src/button/index.ts +2 -0
- package/src/empty-state/EmptyState.tsx +8 -10
- package/src/empty-state/types.ts +2 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +92 -0
- package/src/execution/index.ts +2 -0
- package/src/execution/useCreateAgentExecution.ts +116 -10
- package/src/index.ts +17 -9
- package/src/session/__tests__/useNewSessionFlow.test.tsx +121 -52
- package/src/session/useNewSessionFlow.ts +38 -37
- package/src/sharing/AgentShareList.tsx +502 -0
- package/src/sharing/ShareAgentDialog.tsx +310 -112
- package/src/sharing/__tests__/AgentShareList.test.tsx +388 -0
- package/src/sharing/__tests__/ShareAgentDialog.test.tsx +311 -312
- package/src/sharing/__tests__/useAgentShares.test.tsx +186 -0
- package/src/sharing/__tests__/useCanCreateAgentShare.test.tsx +190 -0
- package/src/sharing/index.ts +10 -12
- package/src/sharing/useAgentShares.ts +74 -0
- package/src/sharing/useCanCreateAgentShare.ts +74 -0
- package/src/sharing/useDeleteAgentShare.ts +73 -0
- package/src/sharing/useSaveAgentShare.ts +73 -23
- package/src/workflow/WorkflowDetailView.tsx +9 -0
- package/src/workflow/instance/WorkflowInstanceEmptyState.tsx +24 -43
- package/src/workflow/instance/WorkflowInstanceList.tsx +29 -16
- package/src/workflow/useWorkflowInstances.ts +10 -3
- package/styles.css +1 -1
- package/workflow/WorkflowDetailView.d.ts +8 -1
- package/workflow/WorkflowDetailView.d.ts.map +1 -1
- package/workflow/WorkflowDetailView.js +2 -2
- package/workflow/WorkflowDetailView.js.map +1 -1
- package/workflow/instance/WorkflowInstanceEmptyState.d.ts +4 -1
- package/workflow/instance/WorkflowInstanceEmptyState.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceEmptyState.js +12 -5
- package/workflow/instance/WorkflowInstanceEmptyState.js.map +1 -1
- package/workflow/instance/WorkflowInstanceList.d.ts +13 -2
- package/workflow/instance/WorkflowInstanceList.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceList.js +8 -4
- package/workflow/instance/WorkflowInstanceList.js.map +1 -1
- package/workflow/useWorkflowInstances.d.ts +8 -3
- package/workflow/useWorkflowInstances.d.ts.map +1 -1
- package/workflow/useWorkflowInstances.js +10 -4
- package/workflow/useWorkflowInstances.js.map +1 -1
- package/sharing/useAgentShare.d.ts +0 -49
- package/sharing/useAgentShare.d.ts.map +0 -1
- package/sharing/useAgentShare.js +0 -64
- package/sharing/useAgentShare.js.map +0 -1
- package/sharing/useCreateExternalShareLink.d.ts +0 -90
- package/sharing/useCreateExternalShareLink.d.ts.map +0 -1
- package/sharing/useCreateExternalShareLink.js +0 -65
- package/sharing/useCreateExternalShareLink.js.map +0 -1
- package/sharing/useShareAgent.d.ts +0 -69
- package/sharing/useShareAgent.d.ts.map +0 -1
- package/sharing/useShareAgent.js +0 -42
- package/sharing/useShareAgent.js.map +0 -1
- package/src/sharing/__tests__/useAgentShare.test.tsx +0 -210
- package/src/sharing/__tests__/useCreateExternalShareLink.test.tsx +0 -194
- package/src/sharing/__tests__/useShareAgent.test.tsx +0 -131
- package/src/sharing/useAgentShare.ts +0 -105
- package/src/sharing/useCreateExternalShareLink.tsx +0 -141
- package/src/sharing/useShareAgent.tsx +0 -107
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
import {
|
|
6
|
+
appendLinkToken,
|
|
7
|
+
chatPath,
|
|
8
|
+
getUserMessage,
|
|
9
|
+
} from "@stigmer/sdk";
|
|
10
|
+
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
11
|
+
import type { AgentShare } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/api_pb";
|
|
12
|
+
import { toast } from "../feedback/toast.js";
|
|
13
|
+
import { Button } from "../button/Button.js";
|
|
14
|
+
import { EmptyState } from "../empty-state/EmptyState.js";
|
|
15
|
+
import { PermissionGate } from "../iam-policy/PermissionGate.js";
|
|
16
|
+
import { ConfirmDialog } from "../resource-detail/ConfirmDialog.js";
|
|
17
|
+
import { useConfirmAction } from "../resource-detail/useConfirmAction.js";
|
|
18
|
+
import { useCopyResource } from "../resource-detail/useCopyResource.js";
|
|
19
|
+
import { ShareAgentDialog } from "./ShareAgentDialog.js";
|
|
20
|
+
import { useAgentShares } from "./useAgentShares.js";
|
|
21
|
+
import { useCanCreateAgentShare } from "./useCanCreateAgentShare.js";
|
|
22
|
+
import { useDeleteAgentShare } from "./useDeleteAgentShare.js";
|
|
23
|
+
import { useRotateShareLink } from "./useRotateShareLink.js";
|
|
24
|
+
import {
|
|
25
|
+
draftFromShare,
|
|
26
|
+
sharingAudienceFromProto,
|
|
27
|
+
useSaveAgentShare,
|
|
28
|
+
} from "./useSaveAgentShare.js";
|
|
29
|
+
|
|
30
|
+
/** Props for {@link AgentShareList}. */
|
|
31
|
+
export interface AgentShareListProps {
|
|
32
|
+
/** The agent whose shares are managed. */
|
|
33
|
+
readonly agent: Agent;
|
|
34
|
+
/**
|
|
35
|
+
* The viewer's active organization slug. Scopes the list to this
|
|
36
|
+
* org's channels of the agent, and a share created from this list
|
|
37
|
+
* lands in this org — its URL, billing, and credentials belong to it
|
|
38
|
+
* (a **cross-org share** when it differs from the agent's org,
|
|
39
|
+
* decision 013). Omit to default to the agent's own org.
|
|
40
|
+
*/
|
|
41
|
+
readonly viewerOrg?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Builds the absolute public chat URL for a share. The host
|
|
44
|
+
* application owns URL construction (its configured public origin may
|
|
45
|
+
* differ from the rendering origin — e.g. the desktop app). When
|
|
46
|
+
* omitted, links fall back to the relative `/chat/<org>/<slug>`.
|
|
47
|
+
*/
|
|
48
|
+
readonly buildShareUrl?: (org: string, slug: string) => string;
|
|
49
|
+
/** Additional CSS class names. */
|
|
50
|
+
readonly className?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Management surface for an agent's {@link AgentShare} channels — the
|
|
55
|
+
* agent analog of {@link AgentInstanceList}, rendered in the agent
|
|
56
|
+
* detail view's Shares tab.
|
|
57
|
+
*
|
|
58
|
+
* Lists the viewer's active org's channels of the agent: `viewerOrg`
|
|
59
|
+
* scopes the `getByAgent` call server-side, so a member of several orgs
|
|
60
|
+
* sees exactly the current org context's channels — never a merged list
|
|
61
|
+
* of every org's (decision 013 amendment). Per-row actions: copy link,
|
|
62
|
+
* edit, pause/resume, reset link, delete. Creation goes through the same
|
|
63
|
+
* {@link ShareAgentDialog} and always lands in the viewer's active org;
|
|
64
|
+
* the create bar mirrors the server's via
|
|
65
|
+
* {@link useCanCreateAgentShare}, so the button never appears to a user
|
|
66
|
+
* whose create would be refused.
|
|
67
|
+
*
|
|
68
|
+
* Self-contained: owns its dialog, its confirmation prompts, and its
|
|
69
|
+
* refetch-after-mutation — hosts render it with just the agent and the
|
|
70
|
+
* URL builder.
|
|
71
|
+
*
|
|
72
|
+
* This is an SDK component (DD-001) — embeddable by platform builders.
|
|
73
|
+
*/
|
|
74
|
+
export function AgentShareList({
|
|
75
|
+
agent,
|
|
76
|
+
viewerOrg,
|
|
77
|
+
buildShareUrl,
|
|
78
|
+
className,
|
|
79
|
+
}: AgentShareListProps) {
|
|
80
|
+
const agentId = agent.metadata?.id ?? "";
|
|
81
|
+
// Scope the list to the org whose context the viewer is in; when the
|
|
82
|
+
// host passes no viewerOrg the scope falls back to the agent's own org
|
|
83
|
+
// (the same-org owner flow), matching where creation would land.
|
|
84
|
+
const { shares, isLoading, error, refetch } = useAgentShares(
|
|
85
|
+
agentId,
|
|
86
|
+
viewerOrg || (agent.metadata?.org ?? ""),
|
|
87
|
+
);
|
|
88
|
+
const { allowed: canCreate, shareOrg } = useCanCreateAgentShare(
|
|
89
|
+
agent,
|
|
90
|
+
viewerOrg,
|
|
91
|
+
);
|
|
92
|
+
const { deleteShare } = useDeleteAgentShare();
|
|
93
|
+
const { confirmState, confirm, handleConfirm, handleCancel } =
|
|
94
|
+
useConfirmAction();
|
|
95
|
+
|
|
96
|
+
// One dialog instance serves both flows: editing the chosen share, or
|
|
97
|
+
// creating a new one in the viewer's org.
|
|
98
|
+
const [editor, setEditor] = useState<
|
|
99
|
+
| { readonly mode: "create" }
|
|
100
|
+
| { readonly mode: "edit"; readonly share: AgentShare }
|
|
101
|
+
| null
|
|
102
|
+
>(null);
|
|
103
|
+
|
|
104
|
+
const handleDelete = useCallback(
|
|
105
|
+
async (share: AgentShare) => {
|
|
106
|
+
const slug = share.metadata?.slug ?? "";
|
|
107
|
+
const confirmed = await confirm({
|
|
108
|
+
title: "Delete share?",
|
|
109
|
+
description:
|
|
110
|
+
`The link /chat/${share.metadata?.org}/${slug} stops working ` +
|
|
111
|
+
"immediately — including for visitors mid-conversation — and its " +
|
|
112
|
+
"configuration (origins, messages, credential bindings) is gone. " +
|
|
113
|
+
"To stop serving while keeping the configuration, pause it instead.",
|
|
114
|
+
confirmLabel: "Delete",
|
|
115
|
+
variant: "destructive",
|
|
116
|
+
});
|
|
117
|
+
if (!confirmed) return;
|
|
118
|
+
try {
|
|
119
|
+
await deleteShare(share.metadata?.id ?? "");
|
|
120
|
+
toast.success("Share deleted");
|
|
121
|
+
refetch();
|
|
122
|
+
} catch (err) {
|
|
123
|
+
toast.error(getUserMessage(err));
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
[confirm, deleteShare, refetch],
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (isLoading) {
|
|
130
|
+
return <LoadingSkeleton />;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (error) {
|
|
134
|
+
return (
|
|
135
|
+
<div className="py-8 text-center text-sm text-destructive">
|
|
136
|
+
Failed to load shares
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div className={cn("space-y-3", className)}>
|
|
143
|
+
{shares.length === 0 ? (
|
|
144
|
+
<ShareEmptyState
|
|
145
|
+
canCreate={canCreate}
|
|
146
|
+
onCreateClick={() => setEditor({ mode: "create" })}
|
|
147
|
+
/>
|
|
148
|
+
) : (
|
|
149
|
+
<>
|
|
150
|
+
<div className="flex items-center justify-between">
|
|
151
|
+
<h3 className="text-sm font-medium text-foreground">
|
|
152
|
+
{shares.length} {shares.length === 1 ? "share" : "shares"}
|
|
153
|
+
</h3>
|
|
154
|
+
{canCreate && (
|
|
155
|
+
<Button
|
|
156
|
+
variant="outline"
|
|
157
|
+
size="xs"
|
|
158
|
+
icon={<PlusIcon />}
|
|
159
|
+
onClick={() => setEditor({ mode: "create" })}
|
|
160
|
+
>
|
|
161
|
+
Create share
|
|
162
|
+
</Button>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<div className="overflow-hidden rounded-lg border border-border">
|
|
167
|
+
<table className="w-full text-sm">
|
|
168
|
+
<thead>
|
|
169
|
+
<tr className="border-b border-border bg-muted-subtle">
|
|
170
|
+
<th className="px-4 py-2 text-left font-medium text-muted-foreground">Name</th>
|
|
171
|
+
<th className="px-4 py-2 text-left font-medium text-muted-foreground">Link</th>
|
|
172
|
+
<th className="px-4 py-2 text-left font-medium text-muted-foreground">Audience</th>
|
|
173
|
+
<th className="px-4 py-2 text-left font-medium text-muted-foreground">Status</th>
|
|
174
|
+
<th className="px-4 py-2 text-right font-medium text-muted-foreground">Actions</th>
|
|
175
|
+
</tr>
|
|
176
|
+
</thead>
|
|
177
|
+
<tbody className="divide-y divide-border">
|
|
178
|
+
{shares.map((share) => (
|
|
179
|
+
<ShareRow
|
|
180
|
+
key={share.metadata?.id}
|
|
181
|
+
share={share}
|
|
182
|
+
agent={agent}
|
|
183
|
+
buildShareUrl={buildShareUrl}
|
|
184
|
+
onEditClick={(s) => setEditor({ mode: "edit", share: s })}
|
|
185
|
+
onDeleteClick={handleDelete}
|
|
186
|
+
refetch={refetch}
|
|
187
|
+
/>
|
|
188
|
+
))}
|
|
189
|
+
</tbody>
|
|
190
|
+
</table>
|
|
191
|
+
</div>
|
|
192
|
+
</>
|
|
193
|
+
)}
|
|
194
|
+
|
|
195
|
+
{editor && (
|
|
196
|
+
<ShareAgentDialog
|
|
197
|
+
open
|
|
198
|
+
onOpenChange={(open) => {
|
|
199
|
+
if (!open) setEditor(null);
|
|
200
|
+
}}
|
|
201
|
+
agent={agent}
|
|
202
|
+
share={editor.mode === "edit" ? editor.share : undefined}
|
|
203
|
+
shareOrg={shareOrg}
|
|
204
|
+
buildShareUrl={buildShareUrl}
|
|
205
|
+
onSharingChanged={refetch}
|
|
206
|
+
/>
|
|
207
|
+
)}
|
|
208
|
+
|
|
209
|
+
<ConfirmDialog
|
|
210
|
+
state={confirmState}
|
|
211
|
+
onConfirm={handleConfirm}
|
|
212
|
+
onCancel={handleCancel}
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Row — one share channel
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
interface ShareRowProps {
|
|
223
|
+
readonly share: AgentShare;
|
|
224
|
+
readonly agent: Agent;
|
|
225
|
+
readonly buildShareUrl?: (org: string, slug: string) => string;
|
|
226
|
+
readonly onEditClick: (share: AgentShare) => void;
|
|
227
|
+
readonly onDeleteClick: (share: AgentShare) => void;
|
|
228
|
+
readonly refetch: () => void;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function ShareRow({
|
|
232
|
+
share,
|
|
233
|
+
agent,
|
|
234
|
+
buildShareUrl,
|
|
235
|
+
onEditClick,
|
|
236
|
+
onDeleteClick,
|
|
237
|
+
refetch,
|
|
238
|
+
}: ShareRowProps) {
|
|
239
|
+
const meta = share.metadata;
|
|
240
|
+
const id = meta?.id ?? "";
|
|
241
|
+
const org = meta?.org ?? "";
|
|
242
|
+
const slug = meta?.slug ?? "";
|
|
243
|
+
const enabled = share.spec?.enabled ?? false;
|
|
244
|
+
const audience = sharingAudienceFromProto(share.spec?.audience);
|
|
245
|
+
const isCrossOrg = org !== (agent.metadata?.org ?? "");
|
|
246
|
+
|
|
247
|
+
const { copy } = useCopyResource();
|
|
248
|
+
const { save, isPending } = useSaveAgentShare(agent);
|
|
249
|
+
const { rotateShareLink, isPending: isRotating } = useRotateShareLink(id);
|
|
250
|
+
|
|
251
|
+
// The copyable URL carries the link token on public shares (org
|
|
252
|
+
// audience is gated by membership, not the token). The display stays
|
|
253
|
+
// the bare path — the token is a secret, not an address.
|
|
254
|
+
const displayPath = chatPath(org, slug);
|
|
255
|
+
const copyUrl = (() => {
|
|
256
|
+
const base = buildShareUrl ? buildShareUrl(org, slug) : displayPath;
|
|
257
|
+
return audience === "org"
|
|
258
|
+
? base
|
|
259
|
+
: appendLinkToken(base, share.status?.shareLinkToken ?? "");
|
|
260
|
+
})();
|
|
261
|
+
|
|
262
|
+
const handleCopyLink = useCallback(() => {
|
|
263
|
+
void copy(copyUrl, "Link");
|
|
264
|
+
}, [copy, copyUrl]);
|
|
265
|
+
|
|
266
|
+
// Pause/resume is a full-spec save with only `enabled` flipped —
|
|
267
|
+
// draftFromShare guarantees a toggle can never wipe origins, messages,
|
|
268
|
+
// or credential bindings (the fails-closed posture the CLI shares).
|
|
269
|
+
const handleToggleEnabled = useCallback(async () => {
|
|
270
|
+
try {
|
|
271
|
+
await save({ ...draftFromShare(share), enabled: !enabled }, share);
|
|
272
|
+
toast.success(enabled ? "Share paused" : "Share resumed");
|
|
273
|
+
refetch();
|
|
274
|
+
} catch (err) {
|
|
275
|
+
toast.error(getUserMessage(err));
|
|
276
|
+
}
|
|
277
|
+
}, [save, share, enabled, refetch]);
|
|
278
|
+
|
|
279
|
+
const handleResetLink = useCallback(async () => {
|
|
280
|
+
try {
|
|
281
|
+
await rotateShareLink();
|
|
282
|
+
toast.success("Link reset — the old link no longer works");
|
|
283
|
+
refetch();
|
|
284
|
+
} catch (err) {
|
|
285
|
+
toast.error(getUserMessage(err));
|
|
286
|
+
}
|
|
287
|
+
}, [rotateShareLink, refetch]);
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<tr
|
|
291
|
+
className="cursor-pointer transition-colors hover:bg-accent-hover"
|
|
292
|
+
onClick={() => onEditClick(share)}
|
|
293
|
+
>
|
|
294
|
+
<td className="px-4 py-2.5">
|
|
295
|
+
<div className="flex items-center gap-2">
|
|
296
|
+
<span className="font-medium text-foreground">
|
|
297
|
+
{meta?.name || slug || "\u2014"}
|
|
298
|
+
</span>
|
|
299
|
+
{isCrossOrg && (
|
|
300
|
+
<span
|
|
301
|
+
className={cn(
|
|
302
|
+
"inline-flex items-center rounded-md px-1.5 py-0.5",
|
|
303
|
+
"text-[0.6rem] font-medium uppercase tracking-wide",
|
|
304
|
+
"bg-muted text-muted-foreground border border-border",
|
|
305
|
+
)}
|
|
306
|
+
title={`This share lives in ${org}; the agent lives in ${agent.metadata?.org}`}
|
|
307
|
+
>
|
|
308
|
+
Cross-org
|
|
309
|
+
</span>
|
|
310
|
+
)}
|
|
311
|
+
</div>
|
|
312
|
+
</td>
|
|
313
|
+
|
|
314
|
+
<td className="px-4 py-2.5" onClick={(e) => e.stopPropagation()}>
|
|
315
|
+
<div className="flex items-center gap-1.5">
|
|
316
|
+
<code
|
|
317
|
+
className="max-w-56 truncate font-mono text-xs text-muted-foreground"
|
|
318
|
+
title={displayPath}
|
|
319
|
+
>
|
|
320
|
+
{displayPath}
|
|
321
|
+
</code>
|
|
322
|
+
<button
|
|
323
|
+
type="button"
|
|
324
|
+
onClick={handleCopyLink}
|
|
325
|
+
aria-label={`Copy link for ${meta?.name || slug}`}
|
|
326
|
+
className={cn(
|
|
327
|
+
"rounded p-1 text-muted-foreground",
|
|
328
|
+
"hover:bg-accent-hover hover:text-foreground",
|
|
329
|
+
"focus:outline-none focus:ring-1 focus:ring-ring",
|
|
330
|
+
)}
|
|
331
|
+
>
|
|
332
|
+
<CopyIcon />
|
|
333
|
+
</button>
|
|
334
|
+
</div>
|
|
335
|
+
</td>
|
|
336
|
+
|
|
337
|
+
<td className="px-4 py-2.5">
|
|
338
|
+
<span className="text-xs text-muted-foreground">
|
|
339
|
+
{audience === "org" ? "Org members" : "Public"}
|
|
340
|
+
</span>
|
|
341
|
+
</td>
|
|
342
|
+
|
|
343
|
+
<td className="px-4 py-2.5">
|
|
344
|
+
<span
|
|
345
|
+
className={cn(
|
|
346
|
+
"inline-flex items-center gap-1.5 text-xs",
|
|
347
|
+
enabled ? "text-foreground" : "text-muted-foreground",
|
|
348
|
+
)}
|
|
349
|
+
>
|
|
350
|
+
<span
|
|
351
|
+
aria-hidden="true"
|
|
352
|
+
className={cn(
|
|
353
|
+
"size-1.5 rounded-full",
|
|
354
|
+
enabled ? "bg-success" : "bg-muted-foreground",
|
|
355
|
+
)}
|
|
356
|
+
/>
|
|
357
|
+
{enabled ? "Active" : "Paused"}
|
|
358
|
+
</span>
|
|
359
|
+
</td>
|
|
360
|
+
|
|
361
|
+
<td className="px-4 py-2.5 text-right" onClick={(e) => e.stopPropagation()}>
|
|
362
|
+
<div className="flex items-center justify-end gap-1">
|
|
363
|
+
<PermissionGate resource={{ kind: "agent_share", id }} relation="can_edit">
|
|
364
|
+
<button
|
|
365
|
+
type="button"
|
|
366
|
+
onClick={() => onEditClick(share)}
|
|
367
|
+
className={cn(
|
|
368
|
+
"rounded px-2 py-1 text-xs font-medium",
|
|
369
|
+
"text-foreground hover:bg-accent-hover",
|
|
370
|
+
"focus:outline-none focus:ring-1 focus:ring-ring",
|
|
371
|
+
)}
|
|
372
|
+
>
|
|
373
|
+
Edit
|
|
374
|
+
</button>
|
|
375
|
+
<button
|
|
376
|
+
type="button"
|
|
377
|
+
onClick={() => void handleToggleEnabled()}
|
|
378
|
+
disabled={isPending}
|
|
379
|
+
className={cn(
|
|
380
|
+
"rounded px-2 py-1 text-xs font-medium",
|
|
381
|
+
"text-foreground hover:bg-accent-hover",
|
|
382
|
+
"focus:outline-none focus:ring-1 focus:ring-ring",
|
|
383
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
384
|
+
)}
|
|
385
|
+
>
|
|
386
|
+
{enabled ? "Pause" : "Resume"}
|
|
387
|
+
</button>
|
|
388
|
+
{audience === "public" && (
|
|
389
|
+
<button
|
|
390
|
+
type="button"
|
|
391
|
+
onClick={() => void handleResetLink()}
|
|
392
|
+
disabled={isRotating}
|
|
393
|
+
className={cn(
|
|
394
|
+
"rounded px-2 py-1 text-xs font-medium",
|
|
395
|
+
"text-foreground hover:bg-accent-hover",
|
|
396
|
+
"focus:outline-none focus:ring-1 focus:ring-ring",
|
|
397
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
398
|
+
)}
|
|
399
|
+
>
|
|
400
|
+
{isRotating ? "Resetting…" : "Reset link"}
|
|
401
|
+
</button>
|
|
402
|
+
)}
|
|
403
|
+
</PermissionGate>
|
|
404
|
+
<PermissionGate resource={{ kind: "agent_share", id }} relation="can_delete">
|
|
405
|
+
<button
|
|
406
|
+
type="button"
|
|
407
|
+
onClick={() => onDeleteClick(share)}
|
|
408
|
+
className={cn(
|
|
409
|
+
"rounded px-2 py-1 text-xs font-medium",
|
|
410
|
+
"text-destructive hover:bg-destructive-subtle",
|
|
411
|
+
"focus:outline-none focus:ring-1 focus:ring-ring",
|
|
412
|
+
)}
|
|
413
|
+
>
|
|
414
|
+
Delete
|
|
415
|
+
</button>
|
|
416
|
+
</PermissionGate>
|
|
417
|
+
</div>
|
|
418
|
+
</td>
|
|
419
|
+
</tr>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ---------------------------------------------------------------------------
|
|
424
|
+
// Empty state
|
|
425
|
+
// ---------------------------------------------------------------------------
|
|
426
|
+
|
|
427
|
+
function ShareEmptyState({
|
|
428
|
+
canCreate,
|
|
429
|
+
onCreateClick,
|
|
430
|
+
}: {
|
|
431
|
+
readonly canCreate: boolean;
|
|
432
|
+
readonly onCreateClick: () => void;
|
|
433
|
+
}) {
|
|
434
|
+
return (
|
|
435
|
+
<EmptyState
|
|
436
|
+
variant="first-use"
|
|
437
|
+
icon={<ShareIcon className="size-10" />}
|
|
438
|
+
title="No shares yet"
|
|
439
|
+
description={
|
|
440
|
+
"A share gives this agent a hosted chat link you can send or embed — " +
|
|
441
|
+
"each share is its own channel with its own audience, embed origins, " +
|
|
442
|
+
"and tool credentials."
|
|
443
|
+
}
|
|
444
|
+
action={
|
|
445
|
+
canCreate
|
|
446
|
+
? { label: "Create share", onClick: onCreateClick, icon: <PlusIcon /> }
|
|
447
|
+
: undefined
|
|
448
|
+
}
|
|
449
|
+
/>
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
// Non-happy states + icons
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
|
|
457
|
+
function LoadingSkeleton() {
|
|
458
|
+
return (
|
|
459
|
+
<div className="space-y-2 py-4">
|
|
460
|
+
{[1, 2, 3].map((i) => (
|
|
461
|
+
<div key={i} className="h-12 animate-pulse rounded-md bg-muted-faint" />
|
|
462
|
+
))}
|
|
463
|
+
</div>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function PlusIcon() {
|
|
468
|
+
return (
|
|
469
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
470
|
+
<path d="M6 2v8M2 6h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
471
|
+
</svg>
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function CopyIcon() {
|
|
476
|
+
return (
|
|
477
|
+
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
478
|
+
<rect x="5.5" y="5.5" width="8" height="8" rx="1.5" />
|
|
479
|
+
<path d="M10.5 5.5v-2a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2" />
|
|
480
|
+
</svg>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function ShareIcon({ className }: { readonly className?: string }) {
|
|
485
|
+
return (
|
|
486
|
+
<svg
|
|
487
|
+
className={className}
|
|
488
|
+
viewBox="0 0 16 16"
|
|
489
|
+
fill="none"
|
|
490
|
+
stroke="currentColor"
|
|
491
|
+
strokeWidth="1.5"
|
|
492
|
+
strokeLinecap="round"
|
|
493
|
+
strokeLinejoin="round"
|
|
494
|
+
aria-hidden="true"
|
|
495
|
+
>
|
|
496
|
+
<circle cx="4" cy="8" r="2" />
|
|
497
|
+
<circle cx="12" cy="4" r="2" />
|
|
498
|
+
<circle cx="12" cy="12" r="2" />
|
|
499
|
+
<path d="m5.8 7.1 4.4-2.2M5.8 8.9l4.4 2.2" />
|
|
500
|
+
</svg>
|
|
501
|
+
);
|
|
502
|
+
}
|