Add company settings invite fallback snippet
This commit is contained in:
@@ -40,7 +40,16 @@ type AgentJoinRequestAccepted = JoinRequest & {
|
|||||||
|
|
||||||
type InviteOnboardingManifest = {
|
type InviteOnboardingManifest = {
|
||||||
invite: InviteSummary;
|
invite: InviteSummary;
|
||||||
onboarding: Record<string, unknown>;
|
onboarding: {
|
||||||
|
inviteMessage?: string | null;
|
||||||
|
connectivity?: {
|
||||||
|
guidance?: string;
|
||||||
|
connectionCandidates?: string[];
|
||||||
|
};
|
||||||
|
textInstructions?: {
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type BoardClaimStatus = {
|
type BoardClaimStatus = {
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ import { Settings, Check, Copy } from "lucide-react";
|
|||||||
import { CompanyPatternIcon } from "../components/CompanyPatternIcon";
|
import { CompanyPatternIcon } from "../components/CompanyPatternIcon";
|
||||||
import { Field, ToggleField, HintIcon } from "../components/agent-config-primitives";
|
import { Field, ToggleField, HintIcon } from "../components/agent-config-primitives";
|
||||||
|
|
||||||
|
type AgentFallbackSnippetInput = {
|
||||||
|
onboardingTextUrl: string;
|
||||||
|
inviteMessage?: string | null;
|
||||||
|
guidance?: string | null;
|
||||||
|
connectionCandidates?: string[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
export function CompanySettings() {
|
export function CompanySettings() {
|
||||||
const { companies, selectedCompany, selectedCompanyId, setSelectedCompanyId } = useCompany();
|
const { companies, selectedCompany, selectedCompanyId, setSelectedCompanyId } = useCompany();
|
||||||
const { setBreadcrumbs } = useBreadcrumbs();
|
const { setBreadcrumbs } = useBreadcrumbs();
|
||||||
@@ -34,6 +41,9 @@ export function CompanySettings() {
|
|||||||
const [frozenInviteMessage, setFrozenInviteMessage] = useState<string | null>(null);
|
const [frozenInviteMessage, setFrozenInviteMessage] = useState<string | null>(null);
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [copyDelightId, setCopyDelightId] = useState(0);
|
const [copyDelightId, setCopyDelightId] = useState(0);
|
||||||
|
const [inviteSnippet, setInviteSnippet] = useState<string | null>(null);
|
||||||
|
const [snippetCopied, setSnippetCopied] = useState(false);
|
||||||
|
const [snippetCopyDelightId, setSnippetCopyDelightId] = useState(0);
|
||||||
|
|
||||||
const generalDirty =
|
const generalDirty =
|
||||||
!!selectedCompany &&
|
!!selectedCompany &&
|
||||||
@@ -77,8 +87,27 @@ export function CompanySettings() {
|
|||||||
: `${base}${onboardingTextLink}`;
|
: `${base}${onboardingTextLink}`;
|
||||||
setInviteLink(absoluteUrl);
|
setInviteLink(absoluteUrl);
|
||||||
const submittedMessage = inviteMessage.trim() || null;
|
const submittedMessage = inviteMessage.trim() || null;
|
||||||
|
const nextInviteMessage = invite.inviteMessage ?? submittedMessage;
|
||||||
setInviteMessage(submittedMessage ?? "");
|
setInviteMessage(submittedMessage ?? "");
|
||||||
setFrozenInviteMessage(invite.inviteMessage ?? submittedMessage);
|
setFrozenInviteMessage(nextInviteMessage);
|
||||||
|
setSnippetCopied(false);
|
||||||
|
setSnippetCopyDelightId(0);
|
||||||
|
try {
|
||||||
|
const manifest = await accessApi.getInviteOnboarding(invite.token);
|
||||||
|
setInviteSnippet(buildAgentFallbackSnippet({
|
||||||
|
onboardingTextUrl: absoluteUrl,
|
||||||
|
inviteMessage: nextInviteMessage,
|
||||||
|
guidance: manifest.onboarding.connectivity?.guidance ?? null,
|
||||||
|
connectionCandidates: manifest.onboarding.connectivity?.connectionCandidates ?? null,
|
||||||
|
}));
|
||||||
|
} catch {
|
||||||
|
setInviteSnippet(buildAgentFallbackSnippet({
|
||||||
|
onboardingTextUrl: absoluteUrl,
|
||||||
|
inviteMessage: nextInviteMessage,
|
||||||
|
guidance: null,
|
||||||
|
connectionCandidates: null,
|
||||||
|
}));
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(absoluteUrl);
|
await navigator.clipboard.writeText(absoluteUrl);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
@@ -99,6 +128,9 @@ export function CompanySettings() {
|
|||||||
setFrozenInviteMessage(null);
|
setFrozenInviteMessage(null);
|
||||||
setCopied(false);
|
setCopied(false);
|
||||||
setCopyDelightId(0);
|
setCopyDelightId(0);
|
||||||
|
setInviteSnippet(null);
|
||||||
|
setSnippetCopied(false);
|
||||||
|
setSnippetCopyDelightId(0);
|
||||||
}, [selectedCompanyId]);
|
}, [selectedCompanyId]);
|
||||||
const archiveMutation = useMutation({
|
const archiveMutation = useMutation({
|
||||||
mutationFn: ({
|
mutationFn: ({
|
||||||
@@ -299,6 +331,8 @@ export function CompanySettings() {
|
|||||||
setInviteLink(null);
|
setInviteLink(null);
|
||||||
setFrozenInviteMessage(null);
|
setFrozenInviteMessage(null);
|
||||||
setCopied(false);
|
setCopied(false);
|
||||||
|
setInviteSnippet(null);
|
||||||
|
setSnippetCopied(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
New message
|
New message
|
||||||
@@ -340,6 +374,42 @@ export function CompanySettings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{inviteSnippet && (
|
||||||
|
<div className="rounded-md border border-border bg-muted/30 p-2">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<div className="text-xs text-muted-foreground">Fallback snippet for agent chat</div>
|
||||||
|
{snippetCopied && (
|
||||||
|
<span key={snippetCopyDelightId} className="flex items-center gap-1 text-xs text-green-600 animate-pulse">
|
||||||
|
<Check className="h-3 w-3" />
|
||||||
|
Copied
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 space-y-1.5">
|
||||||
|
<textarea
|
||||||
|
className="min-h-[160px] w-full rounded-md border border-border bg-background px-2 py-1.5 font-mono text-xs outline-none"
|
||||||
|
value={inviteSnippet}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(inviteSnippet);
|
||||||
|
setSnippetCopied(true);
|
||||||
|
setSnippetCopyDelightId((prev) => prev + 1);
|
||||||
|
setTimeout(() => setSnippetCopied(false), 2000);
|
||||||
|
} catch { /* clipboard may not be available */ }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{snippetCopied ? "Copied snippet" : "Copy snippet"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -387,3 +457,43 @@ export function CompanySettings() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildAgentFallbackSnippet(input: AgentFallbackSnippetInput) {
|
||||||
|
const lines = [
|
||||||
|
"Paperclip onboarding fallback snippet",
|
||||||
|
"Use this if the agent cannot open the onboarding URL directly.",
|
||||||
|
"",
|
||||||
|
`Onboarding .txt URL: ${input.onboardingTextUrl}`,
|
||||||
|
"",
|
||||||
|
];
|
||||||
|
|
||||||
|
if (input.inviteMessage) {
|
||||||
|
lines.push("Message from inviter:", input.inviteMessage, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("Connectivity guidance:");
|
||||||
|
lines.push(input.guidance || "Try reachable Paperclip hosts, then continue with the onboarding URL.");
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
const candidates = (input.connectionCandidates ?? [])
|
||||||
|
.map((candidate) => candidate.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (candidates.length > 0) {
|
||||||
|
lines.push("Suggested Paperclip base URLs:");
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
lines.push(`- ${candidate}`);
|
||||||
|
}
|
||||||
|
lines.push("", "For each candidate, test: GET <candidate>/api/health");
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(
|
||||||
|
"",
|
||||||
|
"If none are reachable, ask the human operator for a reachable hostname/address.",
|
||||||
|
"In authenticated/private mode they may need:",
|
||||||
|
"- pnpm paperclipai allowed-hostname <host>",
|
||||||
|
"- restart Paperclip and retry onboarding.",
|
||||||
|
);
|
||||||
|
|
||||||
|
return `${lines.join("\n")}\n`;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user