@stigmer/react 3.40.1 → 3.41.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/README.md +1 -0
- package/access/ManageAccessDialog.d.ts +1 -1
- package/access/ManageAccessDialog.js +1 -1
- package/access/types.d.ts +1 -1
- package/billing/ChangePlanDialog.d.ts.map +1 -1
- package/billing/ChangePlanDialog.js +13 -1
- package/billing/ChangePlanDialog.js.map +1 -1
- package/billing/ProviderKeysSection.d.ts +33 -0
- package/billing/ProviderKeysSection.d.ts.map +1 -0
- package/billing/ProviderKeysSection.js +143 -0
- package/billing/ProviderKeysSection.js.map +1 -0
- package/billing/UpgradeNotice.d.ts.map +1 -1
- package/billing/UpgradeNotice.js +1 -0
- package/billing/UpgradeNotice.js.map +1 -1
- package/billing/index.d.ts +4 -0
- package/billing/index.d.ts.map +1 -1
- package/billing/index.js +2 -0
- package/billing/index.js.map +1 -1
- package/billing/plan-features.d.ts +3 -2
- package/billing/plan-features.d.ts.map +1 -1
- package/billing/plan-features.js +4 -3
- package/billing/plan-features.js.map +1 -1
- package/billing/useProviderKeys.d.ts +67 -0
- package/billing/useProviderKeys.d.ts.map +1 -0
- package/billing/useProviderKeys.js +61 -0
- package/billing/useProviderKeys.js.map +1 -0
- package/channel/AgentChannelsPanel.d.ts.map +1 -1
- package/channel/AgentChannelsPanel.js +11 -9
- package/channel/AgentChannelsPanel.js.map +1 -1
- package/channel/connect/ServingAppSection.js +1 -1
- package/channel/connect/ServingAppSection.js.map +1 -1
- package/channel-app/ChannelAppDetailPanel.d.ts +2 -2
- package/channel-app/ChannelAppDetailPanel.js +17 -10
- package/channel-app/ChannelAppDetailPanel.js.map +1 -1
- package/channel-app/ChannelAppListPanel.d.ts +2 -2
- package/channel-app/CreateChannelAppForm.js +1 -1
- package/channel-app/CreateChannelAppForm.js.map +1 -1
- package/channel-app/copy.d.ts +3 -3
- package/channel-app/copy.js +3 -3
- package/channel-app/internal.d.ts +9 -0
- package/channel-app/internal.d.ts.map +1 -1
- package/channel-app/internal.js +9 -0
- package/channel-app/internal.js.map +1 -1
- package/channel-app/slackAppSetup.d.ts +3 -1
- package/channel-app/slackAppSetup.d.ts.map +1 -1
- package/channel-app/slackAppSetup.js +3 -1
- package/channel-app/slackAppSetup.js.map +1 -1
- package/channel-app/whatsappAppSetup.d.ts +3 -1
- package/channel-app/whatsappAppSetup.d.ts.map +1 -1
- package/channel-app/whatsappAppSetup.js +3 -1
- package/channel-app/whatsappAppSetup.js.map +1 -1
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +5 -9
- package/composer/SessionComposer.js.map +1 -1
- package/environment/EnvironmentVariableEditor.js +1 -1
- package/environment/EnvironmentVariableEditor.js.map +1 -1
- package/environment/index.d.ts +1 -1
- package/environment/index.d.ts.map +1 -1
- package/environment/index.js +1 -1
- package/environment/index.js.map +1 -1
- package/environment/systemEnvVars.d.ts +4 -63
- package/environment/systemEnvVars.d.ts.map +1 -1
- package/environment/systemEnvVars.js +21 -113
- package/environment/systemEnvVars.js.map +1 -1
- package/execution/RecalledMemoriesCard.js +1 -1
- package/iam-policy/OrgMembersPanel.js +1 -1
- package/iam-policy/OrgMembersPanel.js.map +1 -1
- package/iam-policy/useCheckPermission.d.ts +4 -1
- package/iam-policy/useCheckPermission.d.ts.map +1 -1
- package/iam-policy/useCheckPermission.js +4 -1
- package/iam-policy/useCheckPermission.js.map +1 -1
- package/iam-policy/useGrantableRoles.d.ts +1 -1
- package/iam-policy/useGrantableRoles.js +1 -1
- package/iam-policy/useRevokeOrgAccess.d.ts +10 -8
- package/iam-policy/useRevokeOrgAccess.d.ts.map +1 -1
- package/iam-policy/useRevokeOrgAccess.js +4 -4
- package/iam-policy/useRevokeOrgAccess.js.map +1 -1
- package/identity-account/AccountPreferencesPanel.d.ts +6 -0
- package/identity-account/AccountPreferencesPanel.d.ts.map +1 -1
- package/identity-account/AccountPreferencesPanel.js +33 -2
- package/identity-account/AccountPreferencesPanel.js.map +1 -1
- package/identity-account/useAccountExecutionDefaults.d.ts +1 -1
- package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -1
- package/identity-account/useAccountExecutionDefaults.js +2 -3
- package/identity-account/useAccountExecutionDefaults.js.map +1 -1
- package/index.d.ts +3 -3
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/internal/UnknownPublicAddressNotice.d.ts +20 -0
- package/internal/UnknownPublicAddressNotice.d.ts.map +1 -0
- package/internal/UnknownPublicAddressNotice.js +19 -0
- package/internal/UnknownPublicAddressNotice.js.map +1 -0
- package/internal/features.js +2 -2
- package/internal/features.js.map +1 -1
- package/library/InstanceVisibilitySelector.d.ts +1 -1
- package/library/InstanceVisibilitySelector.d.ts.map +1 -1
- package/library/ResourceVisibilityControl.d.ts +6 -3
- package/library/ResourceVisibilityControl.d.ts.map +1 -1
- package/library/ResourceVisibilityControl.js +7 -4
- package/library/ResourceVisibilityControl.js.map +1 -1
- package/library/VisibilitySelector.d.ts +2 -2
- package/library/VisibilitySelector.d.ts.map +1 -1
- package/library/VisibilitySelector.js +1 -1
- package/licenses/LicenseIssueForm.js +1 -1
- package/licenses/LicenseIssueForm.js.map +1 -1
- package/licenses/license-format.d.ts +7 -5
- package/licenses/license-format.d.ts.map +1 -1
- package/licenses/license-format.js +1 -3
- package/licenses/license-format.js.map +1 -1
- package/mcp-server/McpServerConnectDialog.js +4 -5
- package/mcp-server/McpServerConnectDialog.js.map +1 -1
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +3 -6
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/McpServerPicker.d.ts.map +1 -1
- package/mcp-server/McpServerPicker.js +2 -6
- package/mcp-server/McpServerPicker.js.map +1 -1
- package/mcp-server/McpToolSelector.js +1 -1
- package/mcp-server/McpToolSelector.js.map +1 -1
- package/mcp-server/useMcpServerConnect.d.ts +14 -25
- package/mcp-server/useMcpServerConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerConnect.js +7 -19
- package/mcp-server/useMcpServerConnect.js.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.d.ts +1 -3
- package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.js +6 -19
- package/mcp-server/useMcpServerOAuthConnect.js.map +1 -1
- package/models/HarnessSelector.d.ts.map +1 -1
- package/models/HarnessSelector.js +1 -2
- package/models/HarnessSelector.js.map +1 -1
- package/models/ModelSelector.d.ts +1 -1
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +1 -7
- package/models/ModelSelector.js.map +1 -1
- package/models/harness.d.ts +10 -6
- package/models/harness.d.ts.map +1 -1
- package/models/harness.js +35 -28
- package/models/harness.js.map +1 -1
- package/models/index.d.ts +1 -1
- package/models/index.d.ts.map +1 -1
- package/models/index.js +1 -1
- package/models/index.js.map +1 -1
- package/organization/OrgPreferencesPanel.d.ts.map +1 -1
- package/organization/OrgPreferencesPanel.js +13 -1
- package/organization/OrgPreferencesPanel.js.map +1 -1
- package/package.json +5 -5
- package/plugin/useMcpServerReadiness.js +2 -2
- package/plugin/useMcpServerReadiness.js.map +1 -1
- package/provider-standing/ProviderStandingConsole.js +1 -1
- package/provider.d.ts +27 -1
- package/provider.d.ts.map +1 -1
- package/provider.js +3 -2
- package/provider.js.map +1 -1
- package/public-base-url-context.d.ts +35 -0
- package/public-base-url-context.d.ts.map +1 -0
- package/public-base-url-context.js +62 -0
- package/public-base-url-context.js.map +1 -0
- package/resource-workbench/components/SelectionCheckbox.js +1 -1
- package/resource-workbench/components/SelectionCheckbox.js.map +1 -1
- package/session/useLocalSessionWorker.d.ts +19 -3
- package/session/useLocalSessionWorker.d.ts.map +1 -1
- package/session/useLocalSessionWorker.js +37 -4
- package/session/useLocalSessionWorker.js.map +1 -1
- package/settings/ChannelAppsSection.d.ts +4 -4
- package/settings/ChannelAppsSection.js +4 -4
- package/settings/ProviderKeysSection.d.ts +3 -0
- package/settings/ProviderKeysSection.d.ts.map +1 -0
- package/settings/ProviderKeysSection.js +3 -0
- package/settings/ProviderKeysSection.js.map +1 -0
- package/settings/index.d.ts +2 -0
- package/settings/index.d.ts.map +1 -1
- package/settings/index.js +1 -0
- package/settings/index.js.map +1 -1
- package/settings/settings-nav.d.ts.map +1 -1
- package/settings/settings-nav.js +2 -1
- package/settings/settings-nav.js.map +1 -1
- package/sharing/ShareAgentDialog.d.ts.map +1 -1
- package/sharing/ShareAgentDialog.js +12 -8
- package/sharing/ShareAgentDialog.js.map +1 -1
- package/sharing/useCanCreateAgentShare.d.ts +3 -1
- package/sharing/useCanCreateAgentShare.d.ts.map +1 -1
- package/sharing/useCanCreateAgentShare.js +5 -3
- package/sharing/useCanCreateAgentShare.js.map +1 -1
- package/sharing/useSaveAgentShare.d.ts +1 -1
- package/sharing/useSaveAgentShare.js +1 -1
- package/src/access/ManageAccessDialog.tsx +1 -1
- package/src/access/types.ts +1 -1
- package/src/billing/ChangePlanDialog.tsx +14 -2
- package/src/billing/ProviderKeysSection.tsx +324 -0
- package/src/billing/UpgradeNotice.tsx +1 -0
- package/src/billing/index.ts +9 -0
- package/src/billing/plan-features.ts +4 -3
- package/src/billing/useProviderKeys.ts +129 -0
- package/src/channel/AgentChannelsPanel.tsx +11 -9
- package/src/channel/connect/ServingAppSection.tsx +1 -1
- package/src/channel-app/ChannelAppDetailPanel.tsx +37 -20
- package/src/channel-app/ChannelAppListPanel.tsx +2 -2
- package/src/channel-app/CreateChannelAppForm.tsx +0 -1
- package/src/channel-app/copy.ts +3 -3
- package/src/channel-app/internal.tsx +21 -0
- package/src/channel-app/slackAppSetup.ts +3 -1
- package/src/channel-app/whatsappAppSetup.ts +3 -1
- package/src/composer/SessionComposer.tsx +5 -14
- package/src/environment/EnvironmentVariableEditor.tsx +1 -1
- package/src/environment/index.ts +1 -7
- package/src/environment/systemEnvVars.ts +21 -133
- package/src/execution/RecalledMemoriesCard.tsx +1 -1
- package/src/iam-policy/OrgMembersPanel.tsx +3 -1
- package/src/iam-policy/useCheckPermission.ts +4 -1
- package/src/iam-policy/useGrantableRoles.ts +1 -1
- package/src/iam-policy/useRevokeOrgAccess.ts +10 -8
- package/src/identity-account/AccountPreferencesPanel.tsx +56 -9
- package/src/identity-account/useAccountExecutionDefaults.ts +2 -5
- package/src/index.ts +9 -5
- package/src/internal/UnknownPublicAddressNotice.tsx +31 -0
- package/src/internal/features.ts +2 -2
- package/src/library/InstanceVisibilitySelector.tsx +1 -1
- package/src/library/ResourceVisibilityControl.tsx +7 -4
- package/src/library/VisibilitySelector.tsx +2 -2
- package/src/licenses/LicenseIssueForm.tsx +1 -1
- package/src/licenses/license-format.ts +11 -7
- package/src/mcp-server/McpServerConnectDialog.tsx +4 -6
- package/src/mcp-server/McpServerDetailView.tsx +3 -6
- package/src/mcp-server/McpServerPicker.tsx +2 -12
- package/src/mcp-server/McpToolSelector.tsx +1 -1
- package/src/mcp-server/useMcpServerConnect.ts +14 -33
- package/src/mcp-server/useMcpServerOAuthConnect.ts +7 -23
- package/src/models/HarnessSelector.tsx +1 -3
- package/src/models/ModelSelector.tsx +2 -7
- package/src/models/harness.ts +41 -39
- package/src/models/index.ts +1 -1
- package/src/organization/OrgPreferencesPanel.tsx +19 -1
- package/src/plugin/useMcpServerReadiness.ts +2 -2
- package/src/provider-standing/ProviderStandingConsole.tsx +1 -1
- package/src/provider.tsx +56 -26
- package/src/public-base-url-context.ts +70 -0
- package/src/resource-workbench/components/SelectionCheckbox.tsx +1 -1
- package/src/session/useLocalSessionWorker.ts +35 -4
- package/src/settings/ChannelAppsSection.tsx +4 -4
- package/src/settings/ProviderKeysSection.tsx +4 -0
- package/src/settings/index.ts +2 -0
- package/src/settings/settings-nav.ts +2 -0
- package/src/sharing/ShareAgentDialog.tsx +30 -14
- package/src/sharing/useCanCreateAgentShare.ts +6 -4
- package/src/sharing/useSaveAgentShare.ts +1 -1
- package/src/styles.css +14 -4
- package/src/workflow/WorkflowTaskApprovalSummary.tsx +14 -7
- package/src/workflow/inspector/tabs/BranchesTab.tsx +2 -2
- package/src/workflow/inspector/tabs/CatchTab.tsx +1 -1
- package/src/workflow/inspector/tabs/IterationTab.tsx +1 -1
- package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +7 -6
- package/src/workflow/task-detail/task-approval.ts +20 -13
- package/styles.css +1 -1
- package/workflow/WorkflowTaskApprovalSummary.d.ts +7 -4
- package/workflow/WorkflowTaskApprovalSummary.d.ts.map +1 -1
- package/workflow/WorkflowTaskApprovalSummary.js +8 -3
- package/workflow/WorkflowTaskApprovalSummary.js.map +1 -1
- package/workflow/inspector/tabs/BranchesTab.js +1 -1
- package/workflow/inspector/tabs/BranchesTab.js.map +1 -1
- package/workflow/inspector/tabs/CatchTab.js +1 -1
- package/workflow/inspector/tabs/CatchTab.js.map +1 -1
- package/workflow/inspector/tabs/IterationTab.js +1 -1
- package/workflow/inspector/tabs/IterationTab.js.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.js +6 -5
- package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
- package/workflow/task-detail/task-approval.d.ts +13 -11
- package/workflow/task-detail/task-approval.d.ts.map +1 -1
- package/workflow/task-detail/task-approval.js +8 -4
- package/workflow/task-detail/task-approval.js.map +1 -1
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useId, useState } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { getUserMessage } from "@stigmer/sdk";
|
|
6
|
+
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
7
|
+
import type { ProviderKey } from "@stigmer/protos/ai/stigmer/billing/providerkey/v1/api_pb";
|
|
8
|
+
import { Feature } from "@stigmer/protos/ai/stigmer/platform/v1/entitlement_pb";
|
|
9
|
+
import { Button } from "../button/index.js";
|
|
10
|
+
import { ApiResourceKind, useDeploymentMode, useResourceAvailable } from "../deployment-mode.js";
|
|
11
|
+
import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
|
|
12
|
+
import { CloudFeatureNotice } from "../internal/CloudFeatureNotice.js";
|
|
13
|
+
import { UNSTYLED_LIST } from "../internal/element-resets.js";
|
|
14
|
+
import { useOrg } from "../organization/OrgProvider.js";
|
|
15
|
+
import { lowestPlanWith } from "./plan-features.js";
|
|
16
|
+
import { planUpgradeFeature, UpgradeNotice } from "./UpgradeNotice.js";
|
|
17
|
+
import { useEntitlements } from "./useEntitlements.js";
|
|
18
|
+
import { usePlans } from "./usePlans.js";
|
|
19
|
+
import { useProviderKeyActions, useProviderKeys, type ProviderKeyProvider } from "./useProviderKeys.js";
|
|
20
|
+
|
|
21
|
+
/** The providers, in the order the section lists them. */
|
|
22
|
+
const PROVIDERS: ReadonlyArray<{ readonly id: ProviderKeyProvider; readonly name: string; readonly placeholder: string }> = [
|
|
23
|
+
{ id: "anthropic", name: "Anthropic", placeholder: "sk-ant-…" },
|
|
24
|
+
{ id: "openai", name: "OpenAI", placeholder: "sk-…" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/** Props for {@link ProviderKeysSection}. */
|
|
28
|
+
export interface ProviderKeysSectionProps {
|
|
29
|
+
/** Where the plans are, in the host's routing. Defaults to the billing settings route. */
|
|
30
|
+
readonly billingHref?: string;
|
|
31
|
+
/** Additional CSS class names. */
|
|
32
|
+
readonly className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Settings section for an organization's own LLM provider keys.
|
|
37
|
+
*
|
|
38
|
+
* On a plan that includes bring-your-own provider keys (Business and
|
|
39
|
+
* above), an admin saves an Anthropic or OpenAI key, and the organization's
|
|
40
|
+
* agents and workflows then run on it: the provider bills the organization
|
|
41
|
+
* directly, and those tokens carry no Stigmer commission and do not count
|
|
42
|
+
* toward the plan's usage share. A saved key is never shown again, only its
|
|
43
|
+
* last four characters. A managed organization is served by its
|
|
44
|
+
* integrator's key for a provider it holds none of, shown read-only.
|
|
45
|
+
*
|
|
46
|
+
* A key the plan no longer allows is kept and listed as not in use, and
|
|
47
|
+
* can be removed; the organization's calls run on Stigmer's keys again,
|
|
48
|
+
* billed as usage. Every member sees the list; admins
|
|
49
|
+
* (`can_manage_billing`) save, replace and remove.
|
|
50
|
+
*
|
|
51
|
+
* Cloud-only: in local mode the runner already uses the developer's own
|
|
52
|
+
* keys directly.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* <ProviderKeysSection />
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function ProviderKeysSection({ billingHref, className }: ProviderKeysSectionProps) {
|
|
60
|
+
const headingId = useId();
|
|
61
|
+
const { activeOrg } = useOrg();
|
|
62
|
+
const mode = useDeploymentMode();
|
|
63
|
+
const available = useResourceAvailable(ApiResourceKind.subscription);
|
|
64
|
+
const orgId = activeOrg?.metadata?.id ?? "";
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<section aria-labelledby={headingId} className={className}>
|
|
68
|
+
<h2 id={headingId} className="stg:text-foreground stg:mb-1 stg:text-sm stg:font-semibold">
|
|
69
|
+
Provider keys
|
|
70
|
+
</h2>
|
|
71
|
+
<p className="stg:text-muted-foreground stg:mb-4 stg:text-xs">
|
|
72
|
+
Run your agents on your organization's own Anthropic and OpenAI keys. The provider bills you directly;
|
|
73
|
+
those tokens carry no Stigmer commission and do not count toward your plan's usage share.
|
|
74
|
+
</p>
|
|
75
|
+
{mode === "local" || !available ? (
|
|
76
|
+
<CloudFeatureNotice>
|
|
77
|
+
Your own provider keys are a Stigmer Cloud plan feature. Local mode already calls the providers with your
|
|
78
|
+
own keys directly.
|
|
79
|
+
</CloudFeatureNotice>
|
|
80
|
+
) : !orgId ? (
|
|
81
|
+
<p className="stg:text-muted-foreground stg:py-4 stg:text-center stg:text-xs">
|
|
82
|
+
Select an organization to manage its provider keys.
|
|
83
|
+
</p>
|
|
84
|
+
) : (
|
|
85
|
+
<ProviderKeysContent orgId={orgId} billingHref={billingHref} />
|
|
86
|
+
)}
|
|
87
|
+
</section>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ProviderKeysContent({ orgId, billingHref }: { readonly orgId: string; readonly billingHref?: string }) {
|
|
92
|
+
const listed = useProviderKeys(orgId);
|
|
93
|
+
const entitlements = useEntitlements(orgId);
|
|
94
|
+
const plans = usePlans();
|
|
95
|
+
const manage = useCheckPermission({ kind: "organization", id: orgId }, "can_manage_billing");
|
|
96
|
+
const canManage = !manage.isLoading && manage.allowed;
|
|
97
|
+
const actions = useProviderKeyActions();
|
|
98
|
+
const [editing, setEditing] = useState<ProviderKeyProvider | null>(null);
|
|
99
|
+
|
|
100
|
+
const allowed = entitlements.allows(Feature.byo_provider_keys);
|
|
101
|
+
const unlockingPlanName = lowestPlanWith(plans.plans ?? [], Feature.byo_provider_keys)?.metadata?.name;
|
|
102
|
+
const keys = listed.keys ?? [];
|
|
103
|
+
const refused = planUpgradeFeature(actions.error);
|
|
104
|
+
|
|
105
|
+
const refresh = useCallback(() => {
|
|
106
|
+
listed.refetch();
|
|
107
|
+
entitlements.refetch();
|
|
108
|
+
}, [listed, entitlements]);
|
|
109
|
+
|
|
110
|
+
if (listed.error) {
|
|
111
|
+
return <p className="stg:text-destructive stg:text-xs">{getUserMessage(listed.error)}</p>;
|
|
112
|
+
}
|
|
113
|
+
if (listed.keys === null || allowed === null) {
|
|
114
|
+
return <p className="stg:text-muted-foreground stg:text-xs">Loading provider keys…</p>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className="stg:space-y-3">
|
|
119
|
+
{!allowed && (
|
|
120
|
+
<UpgradeNotice feature={Feature.byo_provider_keys} unlockingPlanName={unlockingPlanName} billingHref={billingHref} />
|
|
121
|
+
)}
|
|
122
|
+
{!allowed && keys.length > 0 && (
|
|
123
|
+
<p className="stg:text-muted-foreground stg:text-xs">
|
|
124
|
+
Your saved keys are kept but not used on this plan: your agents run on Stigmer's keys, billed as usage.
|
|
125
|
+
You can remove them at any time.
|
|
126
|
+
</p>
|
|
127
|
+
)}
|
|
128
|
+
<ul
|
|
129
|
+
aria-label="Provider keys"
|
|
130
|
+
className={cn(UNSTYLED_LIST, "stg:divide-y stg:divide-border stg:rounded-md stg:border stg:border-border")}
|
|
131
|
+
>
|
|
132
|
+
{PROVIDERS.map((provider) => (
|
|
133
|
+
<ProviderRow
|
|
134
|
+
key={provider.id}
|
|
135
|
+
orgId={orgId}
|
|
136
|
+
provider={provider}
|
|
137
|
+
stored={keys.find((key) => key.provider === provider.id)}
|
|
138
|
+
canSave={canManage && allowed}
|
|
139
|
+
canRemove={canManage}
|
|
140
|
+
editing={editing === provider.id}
|
|
141
|
+
onEdit={() => {
|
|
142
|
+
actions.clearError();
|
|
143
|
+
setEditing(provider.id);
|
|
144
|
+
}}
|
|
145
|
+
onDone={() => {
|
|
146
|
+
setEditing(null);
|
|
147
|
+
refresh();
|
|
148
|
+
}}
|
|
149
|
+
onCancel={() => setEditing(null)}
|
|
150
|
+
actions={actions}
|
|
151
|
+
/>
|
|
152
|
+
))}
|
|
153
|
+
</ul>
|
|
154
|
+
{refused !== null ? (
|
|
155
|
+
<UpgradeNotice feature={refused} error={actions.error} billingHref={billingHref} />
|
|
156
|
+
) : actions.error ? (
|
|
157
|
+
<p role="alert" className="stg:text-destructive stg:text-xs">
|
|
158
|
+
{getUserMessage(actions.error)}
|
|
159
|
+
</p>
|
|
160
|
+
) : null}
|
|
161
|
+
<p className="stg:text-muted-foreground stg:text-[0.65rem]">
|
|
162
|
+
A failure on your own key, such as a rejected key or an exhausted provider balance, is reported as your key's;
|
|
163
|
+
calls are never retried on Stigmer's keys.
|
|
164
|
+
</p>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function ProviderRow({
|
|
170
|
+
orgId,
|
|
171
|
+
provider,
|
|
172
|
+
stored,
|
|
173
|
+
canSave,
|
|
174
|
+
canRemove,
|
|
175
|
+
editing,
|
|
176
|
+
onEdit,
|
|
177
|
+
onDone,
|
|
178
|
+
onCancel,
|
|
179
|
+
actions,
|
|
180
|
+
}: {
|
|
181
|
+
readonly orgId: string;
|
|
182
|
+
readonly provider: (typeof PROVIDERS)[number];
|
|
183
|
+
readonly stored: ProviderKey | undefined;
|
|
184
|
+
readonly canSave: boolean;
|
|
185
|
+
readonly canRemove: boolean;
|
|
186
|
+
readonly editing: boolean;
|
|
187
|
+
readonly onEdit: () => void;
|
|
188
|
+
readonly onDone: () => void;
|
|
189
|
+
readonly onCancel: () => void;
|
|
190
|
+
readonly actions: ReturnType<typeof useProviderKeyActions>;
|
|
191
|
+
}) {
|
|
192
|
+
const inputId = useId();
|
|
193
|
+
const [value, setValue] = useState("");
|
|
194
|
+
const [confirming, setConfirming] = useState(false);
|
|
195
|
+
const inherited = stored !== undefined && stored.inheritedFromOrgId !== "";
|
|
196
|
+
const own = stored !== undefined && !inherited;
|
|
197
|
+
|
|
198
|
+
const save = async () => {
|
|
199
|
+
try {
|
|
200
|
+
await actions.setKey(orgId, provider.id, value.trim());
|
|
201
|
+
setValue("");
|
|
202
|
+
onDone();
|
|
203
|
+
} catch {
|
|
204
|
+
// The hook holds the error; the section renders it.
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const remove = async () => {
|
|
208
|
+
try {
|
|
209
|
+
await actions.removeKey(orgId, provider.id);
|
|
210
|
+
setConfirming(false);
|
|
211
|
+
onDone();
|
|
212
|
+
} catch {
|
|
213
|
+
// The hook holds the error; the section renders it.
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<li className="stg:space-y-2 stg:px-3 stg:py-2.5">
|
|
219
|
+
<div className="stg:flex stg:items-start stg:justify-between stg:gap-3">
|
|
220
|
+
<div className="stg:min-w-0">
|
|
221
|
+
<p className="stg:text-foreground stg:text-xs stg:font-medium">{provider.name}</p>
|
|
222
|
+
<p className="stg:text-muted-foreground stg:text-[0.7rem]">{describe(stored)}</p>
|
|
223
|
+
</div>
|
|
224
|
+
{!editing && !confirming && (
|
|
225
|
+
<div className="stg:flex stg:shrink-0 stg:gap-2">
|
|
226
|
+
{canSave && !inherited && (
|
|
227
|
+
<Button size="sm" variant="outline" onClick={onEdit} disabled={actions.isSubmitting}>
|
|
228
|
+
{own ? "Replace" : "Add key"}
|
|
229
|
+
</Button>
|
|
230
|
+
)}
|
|
231
|
+
{canRemove && own && (
|
|
232
|
+
<Button
|
|
233
|
+
size="sm"
|
|
234
|
+
variant="ghost"
|
|
235
|
+
onClick={() => {
|
|
236
|
+
actions.clearError();
|
|
237
|
+
setConfirming(true);
|
|
238
|
+
}}
|
|
239
|
+
disabled={actions.isSubmitting}
|
|
240
|
+
>
|
|
241
|
+
Remove
|
|
242
|
+
</Button>
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
247
|
+
{confirming && (
|
|
248
|
+
<div className="stg:flex stg:flex-wrap stg:items-center stg:justify-between stg:gap-2 stg:rounded-md stg:border stg:border-destructive/30 stg:bg-destructive-subtle stg:px-2.5 stg:py-2">
|
|
249
|
+
<p className="stg:text-foreground stg:min-w-0 stg:flex-1 stg:text-xs">
|
|
250
|
+
{stored?.inUse
|
|
251
|
+
? `Remove your ${provider.name} key? Your agents' ${provider.name} calls go back to Stigmer's keys, billed as usage.`
|
|
252
|
+
: `Remove your ${provider.name} key? It is not in use on this plan.`}
|
|
253
|
+
</p>
|
|
254
|
+
<div className="stg:flex stg:shrink-0 stg:gap-2">
|
|
255
|
+
<Button size="sm" variant="destructive" onClick={() => void remove()} disabled={actions.isSubmitting}>
|
|
256
|
+
{actions.isSubmitting ? "Removing…" : "Remove key"}
|
|
257
|
+
</Button>
|
|
258
|
+
<Button size="sm" variant="ghost" onClick={() => setConfirming(false)} disabled={actions.isSubmitting}>
|
|
259
|
+
Cancel
|
|
260
|
+
</Button>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
{editing && (
|
|
265
|
+
<form
|
|
266
|
+
className="stg:flex stg:flex-wrap stg:items-end stg:gap-2"
|
|
267
|
+
onSubmit={(event) => {
|
|
268
|
+
event.preventDefault();
|
|
269
|
+
void save();
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
<div className="stg:min-w-48 stg:flex-1 stg:space-y-1">
|
|
273
|
+
<label htmlFor={inputId} className="stg:text-foreground stg:text-xs stg:font-medium">
|
|
274
|
+
{provider.name} API key
|
|
275
|
+
</label>
|
|
276
|
+
<input
|
|
277
|
+
id={inputId}
|
|
278
|
+
type="password"
|
|
279
|
+
autoComplete="off"
|
|
280
|
+
value={value}
|
|
281
|
+
onChange={(event) => setValue(event.target.value)}
|
|
282
|
+
placeholder={provider.placeholder}
|
|
283
|
+
disabled={actions.isSubmitting}
|
|
284
|
+
className={cn(
|
|
285
|
+
"stg:w-full stg:rounded-md stg:border stg:border-input stg:bg-background stg:px-2.5 stg:py-1.5 stg:text-xs stg:text-foreground",
|
|
286
|
+
"stg:placeholder:text-muted-foreground",
|
|
287
|
+
"stg:focus-visible:outline-none stg:focus-visible:ring-1 stg:focus-visible:ring-ring",
|
|
288
|
+
"stg:disabled:pointer-events-none stg:disabled:opacity-50",
|
|
289
|
+
)}
|
|
290
|
+
/>
|
|
291
|
+
</div>
|
|
292
|
+
<Button size="sm" type="submit" disabled={actions.isSubmitting || value.trim() === ""}>
|
|
293
|
+
{actions.isSubmitting ? "Saving…" : "Save key"}
|
|
294
|
+
</Button>
|
|
295
|
+
<Button size="sm" variant="ghost" type="button" onClick={onCancel} disabled={actions.isSubmitting}>
|
|
296
|
+
Cancel
|
|
297
|
+
</Button>
|
|
298
|
+
</form>
|
|
299
|
+
)}
|
|
300
|
+
</li>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** One line about a provider's key: none, own, or inherited, with its use. */
|
|
305
|
+
function describe(key: ProviderKey | undefined): string {
|
|
306
|
+
if (key === undefined) {
|
|
307
|
+
return "Not set: calls run on Stigmer's keys, billed as usage.";
|
|
308
|
+
}
|
|
309
|
+
const parts = [`Key ending in ${key.keyHint}`];
|
|
310
|
+
if (key.inheritedFromOrgId !== "") {
|
|
311
|
+
parts.push(`provided by ${key.inheritedFromOrgId}`);
|
|
312
|
+
} else if (key.updatedAt !== undefined) {
|
|
313
|
+
parts.push(`saved ${formatDay(timestampDate(key.updatedAt))}`);
|
|
314
|
+
}
|
|
315
|
+
parts.push(key.lastUsedAt === undefined ? "not used yet" : `last used ${formatDay(timestampDate(key.lastUsedAt))}`);
|
|
316
|
+
if (!key.inUse) {
|
|
317
|
+
parts.push("not in use on this plan");
|
|
318
|
+
}
|
|
319
|
+
return parts.join(" · ");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function formatDay(date: Date): string {
|
|
323
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
|
|
324
|
+
}
|
|
@@ -18,6 +18,7 @@ export const DEFAULT_BILLING_HREF = "/settings/billing";
|
|
|
18
18
|
const FEATURE_BY_WIRE_NAME: Readonly<Record<string, Feature>> = {
|
|
19
19
|
teams: Feature.teams,
|
|
20
20
|
managed_organizations: Feature.managed_organizations,
|
|
21
|
+
byo_provider_keys: Feature.byo_provider_keys,
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
/**
|
package/src/billing/index.ts
CHANGED
|
@@ -20,6 +20,13 @@ export { useEntitlements } from "./useEntitlements.js";
|
|
|
20
20
|
export type { UseEntitlementsOptions, UseEntitlementsReturn } from "./useEntitlements.js";
|
|
21
21
|
export { usePeriodEstimate } from "./usePeriodEstimate.js";
|
|
22
22
|
export type { UsePeriodEstimateOptions, UsePeriodEstimateReturn } from "./usePeriodEstimate.js";
|
|
23
|
+
export { useProviderKeys, useProviderKeyActions } from "./useProviderKeys.js";
|
|
24
|
+
export type {
|
|
25
|
+
ProviderKeyProvider,
|
|
26
|
+
UseProviderKeysOptions,
|
|
27
|
+
UseProviderKeysReturn,
|
|
28
|
+
UseProviderKeyActionsReturn,
|
|
29
|
+
} from "./useProviderKeys.js";
|
|
23
30
|
|
|
24
31
|
// Behavior hooks
|
|
25
32
|
export { useCreateCheckoutSession } from "./useCreateCheckoutSession.js";
|
|
@@ -43,6 +50,8 @@ export type {
|
|
|
43
50
|
} from "./useSetAutoRechargeConfig.js";
|
|
44
51
|
|
|
45
52
|
// Styled components
|
|
53
|
+
export { ProviderKeysSection } from "./ProviderKeysSection.js";
|
|
54
|
+
export type { ProviderKeysSectionProps } from "./ProviderKeysSection.js";
|
|
46
55
|
export { BillingSection } from "./BillingSection.js";
|
|
47
56
|
export type { BillingSectionProps } from "./BillingSection.js";
|
|
48
57
|
export { CreditBalanceCard } from "./CreditBalanceCard.js";
|
|
@@ -24,10 +24,11 @@ const ZERO = BigInt(0);
|
|
|
24
24
|
/**
|
|
25
25
|
* Features a plan may list ahead of the gate that serves them, which
|
|
26
26
|
* Stigmer Cloud does not offer yet. The console leaves them out of every
|
|
27
|
-
* comparison.
|
|
28
|
-
*
|
|
27
|
+
* comparison. Empty while every listed feature is served; bring-your-own
|
|
28
|
+
* provider keys left it when the proxy began serving an organization's own
|
|
29
|
+
* key (stigmer/stigmer#1425).
|
|
29
30
|
*/
|
|
30
|
-
export const NOT_YET_OFFERED: ReadonlySet<Feature> = new Set(
|
|
31
|
+
export const NOT_YET_OFFERED: ReadonlySet<Feature> = new Set<Feature>();
|
|
31
32
|
|
|
32
33
|
/** One feature a plan offers today, with its words. */
|
|
33
34
|
export interface OfferedFeature {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import type { ProviderKey } from "@stigmer/protos/ai/stigmer/billing/providerkey/v1/api_pb";
|
|
6
|
+
import {
|
|
7
|
+
DeleteProviderKeyInputSchema,
|
|
8
|
+
ListProviderKeysInputSchema,
|
|
9
|
+
SetProviderKeyInputSchema,
|
|
10
|
+
} from "@stigmer/protos/ai/stigmer/billing/providerkey/v1/io_pb";
|
|
11
|
+
import { useStigmer } from "../hooks.js";
|
|
12
|
+
import { toError } from "../internal/toError.js";
|
|
13
|
+
import { useFetch } from "../internal/useFetch.js";
|
|
14
|
+
|
|
15
|
+
/** The providers an organization may bring its own key for, by their wire names. */
|
|
16
|
+
export type ProviderKeyProvider = "anthropic" | "openai";
|
|
17
|
+
|
|
18
|
+
/** Options for {@link useProviderKeys}. */
|
|
19
|
+
export interface UseProviderKeysOptions {
|
|
20
|
+
/** Skip fetching while `false` (stable no-op). Default `true`. */
|
|
21
|
+
readonly enabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Return value of {@link useProviderKeys}. */
|
|
25
|
+
export interface UseProviderKeysReturn {
|
|
26
|
+
/**
|
|
27
|
+
* The keys that serve the organization, its own and any it inherits from
|
|
28
|
+
* its integrator, without their secrets; `null` before the first
|
|
29
|
+
* successful fetch.
|
|
30
|
+
*/
|
|
31
|
+
readonly keys: readonly ProviderKey[] | null;
|
|
32
|
+
/** `true` while the initial fetch is in flight. */
|
|
33
|
+
readonly isLoading: boolean;
|
|
34
|
+
/** Error from the last failed request, or `null` when healthy. */
|
|
35
|
+
readonly error: Error | null;
|
|
36
|
+
/** Discard cached data and re-fetch from the server. */
|
|
37
|
+
readonly refetch: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Data hook that lists an organization's own LLM provider keys.
|
|
42
|
+
*
|
|
43
|
+
* No answer carries a key: each row names the provider, the key's last
|
|
44
|
+
* four characters, who saved it and when, when it last served a call, and
|
|
45
|
+
* whether the organization's plan still lets it be used. A managed
|
|
46
|
+
* organization's list includes its integrator's keys for the providers it
|
|
47
|
+
* holds none of (`inheritedFromOrgId`). Every member may read it
|
|
48
|
+
* (`can_view_billing`).
|
|
49
|
+
*
|
|
50
|
+
* Cloud-only.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* const { keys } = useProviderKeys(orgId);
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function useProviderKeys(orgId: string | null, options?: UseProviderKeysOptions): UseProviderKeysReturn {
|
|
58
|
+
const stigmer = useStigmer();
|
|
59
|
+
const enabled = (options?.enabled ?? true) && orgId !== null && orgId !== "";
|
|
60
|
+
const { data, isLoading, error, refetch } = useFetch(
|
|
61
|
+
enabled && orgId ? () => stigmer.providerkey.list(create(ListProviderKeysInputSchema, { orgId })) : null,
|
|
62
|
+
[enabled, orgId, stigmer],
|
|
63
|
+
null as { readonly keys: readonly ProviderKey[] } | null,
|
|
64
|
+
);
|
|
65
|
+
const keys = data?.keys ?? null;
|
|
66
|
+
return useMemo(() => ({ keys, isLoading, error, refetch }), [keys, isLoading, error, refetch]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Return value of {@link useProviderKeyActions}. */
|
|
70
|
+
export interface UseProviderKeyActionsReturn {
|
|
71
|
+
/**
|
|
72
|
+
* Save the organization's key for a provider, replacing any it holds.
|
|
73
|
+
* The provider is asked about the key once first, and a key it rejects is
|
|
74
|
+
* refused. Refused with the `PLAN_UPGRADE_REQUIRED` reason when the
|
|
75
|
+
* organization's plan does not include bring-your-own provider keys.
|
|
76
|
+
* Resolves with the saved key, without its secret.
|
|
77
|
+
*/
|
|
78
|
+
readonly setKey: (orgId: string, provider: ProviderKeyProvider, apiKey: string) => Promise<ProviderKey>;
|
|
79
|
+
/** Remove the organization's key for a provider; allowed on every plan. */
|
|
80
|
+
readonly removeKey: (orgId: string, provider: ProviderKeyProvider) => Promise<ProviderKey>;
|
|
81
|
+
/** `true` while a save or a removal is in flight. */
|
|
82
|
+
readonly isSubmitting: boolean;
|
|
83
|
+
/** Error from the last failed attempt, or `null` when healthy. */
|
|
84
|
+
readonly error: Error | null;
|
|
85
|
+
/** Reset `error` to `null`. */
|
|
86
|
+
readonly clearError: () => void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Behaviour hook that saves and removes an organization's own provider
|
|
91
|
+
* keys. The caller needs `can_manage_billing` on the organization (its
|
|
92
|
+
* admins). Refetch {@link useProviderKeys} after a change.
|
|
93
|
+
*/
|
|
94
|
+
export function useProviderKeyActions(): UseProviderKeyActionsReturn {
|
|
95
|
+
const stigmer = useStigmer();
|
|
96
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
97
|
+
const [error, setError] = useState<Error | null>(null);
|
|
98
|
+
|
|
99
|
+
const run = useCallback(async (call: () => Promise<ProviderKey>): Promise<ProviderKey> => {
|
|
100
|
+
setIsSubmitting(true);
|
|
101
|
+
setError(null);
|
|
102
|
+
try {
|
|
103
|
+
return await call();
|
|
104
|
+
} catch (e) {
|
|
105
|
+
const err = toError(e);
|
|
106
|
+
setError(err);
|
|
107
|
+
throw err;
|
|
108
|
+
} finally {
|
|
109
|
+
setIsSubmitting(false);
|
|
110
|
+
}
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
113
|
+
const setKey = useCallback(
|
|
114
|
+
(orgId: string, provider: ProviderKeyProvider, apiKey: string) =>
|
|
115
|
+
run(() => stigmer.providerkey.set(create(SetProviderKeyInputSchema, { orgId, provider, apiKey }))),
|
|
116
|
+
[run, stigmer.providerkey],
|
|
117
|
+
);
|
|
118
|
+
const removeKey = useCallback(
|
|
119
|
+
(orgId: string, provider: ProviderKeyProvider) =>
|
|
120
|
+
run(() => stigmer.providerkey.delete(create(DeleteProviderKeyInputSchema, { orgId, provider }))),
|
|
121
|
+
[run, stigmer.providerkey],
|
|
122
|
+
);
|
|
123
|
+
const clearError = useCallback(() => setError(null), []);
|
|
124
|
+
|
|
125
|
+
return useMemo(
|
|
126
|
+
() => ({ setKey, removeKey, isSubmitting, error, clearError }),
|
|
127
|
+
[setKey, removeKey, isSubmitting, error, clearError],
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -119,19 +119,21 @@ export function AgentChannelsPanel({
|
|
|
119
119
|
// label worth showing.
|
|
120
120
|
const { channelApps } = useChannelAppList(agent.metadata?.org || null);
|
|
121
121
|
|
|
122
|
-
// Mirrors the server's create bar (agent
|
|
123
|
-
// create/apply handlers enforce on the referenced agent
|
|
124
|
-
//
|
|
122
|
+
// Mirrors the server's create bar (agent can_manage_audience — the
|
|
123
|
+
// permission the create/apply handlers enforce on the referenced agent:
|
|
124
|
+
// a channel puts the agent in front of a wider audience, the owner's
|
|
125
|
+
// decision, not an editor's) so the connect affordance never appears to
|
|
126
|
+
// someone whose create would be refused.
|
|
125
127
|
const { allowed: canCreate } = useCheckPermission(
|
|
126
128
|
agentId ? { kind: "agent", id: agentId } : null,
|
|
127
|
-
"
|
|
129
|
+
"can_manage_audience",
|
|
128
130
|
);
|
|
129
131
|
|
|
130
|
-
// Installs
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
132
|
+
// Installs need a channel delivery runtime, and only Stigmer Cloud
|
|
133
|
+
// composes one today; CRUD needs none. Outside Cloud the cards render and
|
|
134
|
+
// toggles work, but connect affordances give way to the notice. When a
|
|
135
|
+
// self-hosted edition composes a runtime, this becomes a probe of that
|
|
136
|
+
// runtime rather than an edition compare.
|
|
135
137
|
const installsAvailable = useDeploymentMode() === "cloud";
|
|
136
138
|
|
|
137
139
|
const { deleteChannel } = useDeleteAgentChannel();
|
|
@@ -175,7 +175,7 @@ function ServingAppOption({
|
|
|
175
175
|
checked={checked}
|
|
176
176
|
onChange={onSelect}
|
|
177
177
|
disabled={disabled}
|
|
178
|
-
className="stg:mt-0.5
|
|
178
|
+
className="stg:mt-0.5"
|
|
179
179
|
/>
|
|
180
180
|
<span className="stg:min-w-0">
|
|
181
181
|
<span className="stg:block stg:text-xs stg:font-medium stg:text-foreground">{label}</span>
|
|
@@ -4,7 +4,7 @@ import { type FormEvent, useCallback, useId, useMemo, useState } from "react";
|
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
5
|
import { getUserMessage, toChannelAppUpdateInput } from "@stigmer/sdk";
|
|
6
6
|
import type { ChannelApp } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
|
|
7
|
-
import {
|
|
7
|
+
import { usePublicBaseUrl } from "../public-base-url-context.js";
|
|
8
8
|
import { useUpdateChannelApp } from "./useUpdateChannelApp.js";
|
|
9
9
|
import { useDeleteChannelApp } from "./useDeleteChannelApp.js";
|
|
10
10
|
import type { ChannelAppCreateHandoff } from "./CreateChannelAppForm.js";
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
WHATSAPP_CHANNEL_APP_WEBHOOK_FIELDS,
|
|
18
18
|
whatsappChannelAppWebhookUrl,
|
|
19
19
|
} from "./whatsappAppSetup.js";
|
|
20
|
-
import { CopyBlock, CopyRow, FormField } from "./internal.js";
|
|
20
|
+
import { CopyBlock, CopyRow, FormField, UnknownAddressRow } from "./internal.js";
|
|
21
21
|
import { PermissionGate } from "../iam-policy/PermissionGate.js";
|
|
22
22
|
import { SpinnerIcon } from "../internal/SpinnerIcon.js";
|
|
23
23
|
|
|
@@ -70,8 +70,8 @@ export interface ChannelAppDetailPanelProps {
|
|
|
70
70
|
*
|
|
71
71
|
* The credentials form is offered to a caller who may edit the app
|
|
72
72
|
* (`can_edit`) and the deletion section to one who may delete it
|
|
73
|
-
* (`can_delete`):
|
|
74
|
-
*
|
|
73
|
+
* (`can_delete`): the organization's admins. Someone granted viewer on an
|
|
74
|
+
* app sees its setup values with no actions.
|
|
75
75
|
*/
|
|
76
76
|
export function ChannelAppDetailPanel({
|
|
77
77
|
channelApp,
|
|
@@ -200,7 +200,7 @@ function SlackAppDetail({
|
|
|
200
200
|
readonly onUpdated?: (app: ChannelApp) => void;
|
|
201
201
|
}) {
|
|
202
202
|
const baseId = useId();
|
|
203
|
-
const
|
|
203
|
+
const publicBaseUrl = usePublicBaseUrl();
|
|
204
204
|
const { update, isUpdating, error: updateError, clearError } = useUpdateChannelApp();
|
|
205
205
|
|
|
206
206
|
const slack = channelApp.spec?.providerConfig?.case === "slack"
|
|
@@ -214,9 +214,14 @@ function SlackAppDetail({
|
|
|
214
214
|
const appId = channelApp.metadata?.id ?? "";
|
|
215
215
|
const name = channelApp.metadata?.name ?? "";
|
|
216
216
|
|
|
217
|
+
// `undefined` while the server's public address is unknown: the row
|
|
218
|
+
// says so, and the manifest leaves out its event subscriptions.
|
|
217
219
|
const webhookUrl = useMemo(
|
|
218
|
-
() =>
|
|
219
|
-
|
|
220
|
+
() =>
|
|
221
|
+
publicBaseUrl === null
|
|
222
|
+
? undefined
|
|
223
|
+
: slackChannelAppWebhookUrl(publicBaseUrl, appId),
|
|
224
|
+
[publicBaseUrl, appId],
|
|
220
225
|
);
|
|
221
226
|
const redirectUrl = useMemo(
|
|
222
227
|
() => slackChannelAppRedirectUrl(consoleOrigin),
|
|
@@ -271,11 +276,15 @@ function SlackAppDetail({
|
|
|
271
276
|
below (Slack verifies it immediately). The completed manifest is
|
|
272
277
|
also available for review or re-paste.
|
|
273
278
|
</p>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
+
{webhookUrl !== undefined ? (
|
|
280
|
+
<CopyRow
|
|
281
|
+
label="Events request URL"
|
|
282
|
+
value={webhookUrl}
|
|
283
|
+
copyTargetId="stgm-chapp-webhook-copy"
|
|
284
|
+
/>
|
|
285
|
+
) : (
|
|
286
|
+
<UnknownAddressRow label="Events request URL" subject="the events request URL" />
|
|
287
|
+
)}
|
|
279
288
|
<CopyRow label="OAuth redirect URL" value={redirectUrl} />
|
|
280
289
|
<CopyBlock label="Completed app manifest" value={manifest} />
|
|
281
290
|
</section>
|
|
@@ -361,7 +370,7 @@ function WhatsAppAppDetail({
|
|
|
361
370
|
readonly onUpdated?: (app: ChannelApp) => void;
|
|
362
371
|
}) {
|
|
363
372
|
const baseId = useId();
|
|
364
|
-
const
|
|
373
|
+
const publicBaseUrl = usePublicBaseUrl();
|
|
365
374
|
const { update, isUpdating, error: updateError, clearError } = useUpdateChannelApp();
|
|
366
375
|
|
|
367
376
|
const whatsapp = channelApp.spec?.providerConfig?.case === "whatsapp"
|
|
@@ -376,9 +385,13 @@ function WhatsAppAppDetail({
|
|
|
376
385
|
const appId = channelApp.metadata?.id ?? "";
|
|
377
386
|
const name = channelApp.metadata?.name ?? "";
|
|
378
387
|
|
|
388
|
+
// `undefined` while the server's public address is unknown: the row says so.
|
|
379
389
|
const webhookUrl = useMemo(
|
|
380
|
-
() =>
|
|
381
|
-
|
|
390
|
+
() =>
|
|
391
|
+
publicBaseUrl === null
|
|
392
|
+
? undefined
|
|
393
|
+
: whatsappChannelAppWebhookUrl(publicBaseUrl, appId),
|
|
394
|
+
[publicBaseUrl, appId],
|
|
382
395
|
);
|
|
383
396
|
|
|
384
397
|
const canSave =
|
|
@@ -432,11 +445,15 @@ function WhatsAppAppDetail({
|
|
|
432
445
|
</span>{" "}
|
|
433
446
|
webhook field.
|
|
434
447
|
</p>
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
448
|
+
{webhookUrl !== undefined ? (
|
|
449
|
+
<CopyRow
|
|
450
|
+
label="Callback URL"
|
|
451
|
+
value={webhookUrl}
|
|
452
|
+
copyTargetId="stgm-chapp-webhook-copy"
|
|
453
|
+
/>
|
|
454
|
+
) : (
|
|
455
|
+
<UnknownAddressRow label="Callback URL" subject="the callback URL" />
|
|
456
|
+
)}
|
|
440
457
|
{createHandoff?.verifyToken ? (
|
|
441
458
|
<CopyRow
|
|
442
459
|
label="Verify token"
|
|
@@ -25,8 +25,8 @@ export interface ChannelAppListPanelProps {
|
|
|
25
25
|
readonly onRefetchRef?: (refetch: () => void) => void;
|
|
26
26
|
/**
|
|
27
27
|
* Shown when the list is empty. Defaults to "No channel apps registered
|
|
28
|
-
* yet." The list holds only the apps the caller may view (
|
|
29
|
-
* anyone granted viewer on
|
|
28
|
+
* yet." The list holds only the apps the caller may view (the
|
|
29
|
+
* organization's admins, and anyone granted viewer on an app), so a host
|
|
30
30
|
* that knows the caller may not manage channel apps should say who does
|
|
31
31
|
* instead.
|
|
32
32
|
*/
|