mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-10 12:07:09 +02:00
Show custom GitHub App mentions and branding controls
This commit is contained in:
1 parent
e2f6ba33c4
commit
ed5e3b6ab5
12 files changed
+337
-63
No files matched your search
@@ -40,6 +40,23 @@ behavior, and narrower saved repository restrictions remain intact. Later
|
||||
repository additions require enablement in Access. Advanced review rules and
|
||||
prompts live in Settings.
|
||||
|
||||
The App belongs to the selected account or organization and uses its own bot
|
||||
identity. The editable App name determines GitHub's slug and `@mention`; there
|
||||
is no separate editable bot username. Paperclip shows and copies the verified
|
||||
mention in the connected bot's header. Type `@app-slug`, without the `[bot]`
|
||||
suffix shown on GitHub's API author records.
|
||||
|
||||
**GitHub App name and logo** is optional on the connected page and in Settings.
|
||||
Download the agent's avatar as a PNG, then follow the link to this App's GitHub
|
||||
settings to upload it under **Display information**. GitHub's
|
||||
[manifest parameters](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest)
|
||||
do not include an avatar; the
|
||||
[logo upload](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/creating-a-custom-badge-for-your-github-app)
|
||||
remains a GitHub settings action. After renaming the App on GitHub, use the
|
||||
existing **reconnect this App** flow to refresh its verified identity using its
|
||||
stored credentials. Legacy manual connections without recorded ownership link
|
||||
to the App settings list instead of assuming an owner.
|
||||
|
||||
Local instances receive public callbacks and signed events through an enrolled
|
||||
Paperclip Cloud connector, using outbound requests instead of a public tunnel.
|
||||
The Cloud gateway capability must be deployed before localhost onboarding.
|
||||
|
||||
@@ -325,3 +325,34 @@ copy edit and passes. Repository typecheck/build, production Storybook build,
|
||||
and token gates pass; the final discovery change also received focused server
|
||||
typecheck/build. These checks do not qualify a new provider or model run. The
|
||||
earlier full-suite failure and outstanding current-head CI/review still apply.
|
||||
|
||||
### Custom App identity and branding qualification
|
||||
|
||||
The live bot is the dedicated **Animal Bot E2E 20261007** App owned by
|
||||
`paperclipai`, not the shared Paperclip App. Its verified GitHub author is
|
||||
`animal-bot-e2e-20261007[bot]`; the human mention is
|
||||
`@animal-bot-e2e-20261007`. The published review's GitHub author and App fields
|
||||
agree with the saved endpoint identity.
|
||||
|
||||
The setup name field now explains GitHub's derived mention. Connected bots show
|
||||
the verified, copyable mention in the header. Settings and wizard completion
|
||||
share an optional App name/logo disclosure, the agent's PNG download, and the
|
||||
owning App's GitHub settings link. Personal accounts use their corresponding App
|
||||
settings path. Unknown legacy ownership uses the settings list, never an assumed
|
||||
organization or the installation settings page. A renamed App can refresh its
|
||||
identity through existing-App reconnect with stored credentials.
|
||||
|
||||
The preserved test drive was restarted after an interruption. The live header
|
||||
showed the correct mention; copying and pasting it into an unsaved field proved
|
||||
the exact text, then the original field was restored without saving. Downloaded
|
||||
avatar is a 512×512 PNG, 30,616 bytes. Desktop and 390px branding layouts were
|
||||
inspected. GitHub's App settings link reached its signed-in re-authentication
|
||||
gate; no name or logo was changed, and App rename/reconnect was not tested live.
|
||||
GitHub's manifest has no logo field, so provider upload remains an optional
|
||||
GitHub settings action. No configuration, credentials or permissions changed in
|
||||
this walkthrough.
|
||||
|
||||
All 59 focused management, setup, avatar-download and clipboard tests pass,
|
||||
including Slack's existing download/error/retry coverage for the shared
|
||||
downloader. UI typecheck/build, production Storybook build and token gates pass.
|
||||
The earlier full-suite failure and current-head CI/review limits still apply.
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useState } from "react";
|
||||
import { Download, Loader2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function agentAvatarFilename(name: string) {
|
||||
return `${name.replace(/[^a-zA-Z0-9_-]+/g, "-") || "agent"}-avatar.png`;
|
||||
}
|
||||
|
||||
/** Downloads the instance's generated PNG; provider upload remains explicit. */
|
||||
export function AgentAvatarDownload({
|
||||
avatarUrl,
|
||||
name,
|
||||
}: {
|
||||
avatarUrl: string;
|
||||
name: string;
|
||||
}) {
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [failed, setFailed] = useState(false);
|
||||
const download = async () => {
|
||||
if (downloading) return;
|
||||
setDownloading(true);
|
||||
setFailed(false);
|
||||
try {
|
||||
const response = await fetch(avatarUrl);
|
||||
if (
|
||||
!response.ok ||
|
||||
!response.headers.get("content-type")?.startsWith("image/png")
|
||||
)
|
||||
throw new Error("Avatar unavailable");
|
||||
const url = URL.createObjectURL(await response.blob());
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = agentAvatarFilename(name);
|
||||
document.body.append(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1_000);
|
||||
} catch {
|
||||
setFailed(true);
|
||||
} finally {
|
||||
setDownloading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Button variant="outline" asChild>
|
||||
<a
|
||||
href={avatarUrl}
|
||||
download={agentAvatarFilename(name)}
|
||||
aria-disabled={downloading}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
void download();
|
||||
}}
|
||||
>
|
||||
{downloading ? (
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
) : (
|
||||
<Download className="size-4" />
|
||||
)}
|
||||
Download avatar
|
||||
</a>
|
||||
</Button>
|
||||
{failed && (
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
Couldn’t download the avatar. Try downloading it again.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { resolveAgentAppearance } from "@paperclipai/shared";
|
||||
import { AgentAvatar } from "@/components/AgentAvatar";
|
||||
import { ChatConversationList } from "./ChatConversationList";
|
||||
import { GitHubBotManagement, GitHubReviews } from "./GitHubBotManagement";
|
||||
import { GitHubBotMention } from "./GitHubAppIdentity";
|
||||
import { EmailEndpointSettings } from "./EmailEndpointSetup";
|
||||
import { EmailConnectionAccess } from "@/components/EmailConnectionAccess";
|
||||
import { emailApi } from "@/api/email";
|
||||
@@ -309,6 +310,7 @@ export function ChatEndpointDetail() {
|
||||
<p className="mt-1 flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
{endpoint.provider === "github" ? <><AppLogo name="GitHub" brandKey="github" compact className="size-4! rounded-sm bg-transparent" />{endpoint.botLabel ?? endpoint.botUsername ?? "GitHub"}{endpoint.providerAccountLabel && <span>· {endpoint.providerAccountLabel}</span>}</> : endpoint.providerAccountLabel ?? (endpoint.provider === "agentmail" ? endpoint.botExternalId ?? "Email connection" : "Chat connection")}
|
||||
</p>
|
||||
{endpoint.provider === "github" && <div className="mt-1"><GitHubBotMention endpoint={endpoint} /></div>}
|
||||
{endpoint.provider === "imessage-photon" && endpoint.botExternalId && endpoint.photonAllocation !== "shared" && (
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2 text-sm">
|
||||
<span>{endpoint.botExternalId}</span>
|
||||
@@ -338,7 +340,8 @@ export function ChatEndpointDetail() {
|
||||
</div>
|
||||
</header>
|
||||
{endpoint.provider === "github" && <div hidden={activeTab !== "settings" && activeTab !== "access"}>
|
||||
<GitHubBotManagement key={endpoint.id} endpoint={endpoint} view={activeTab === "access" ? "access" : "settings"} />
|
||||
<GitHubBotManagement key={endpoint.id} endpoint={endpoint} view={activeTab === "access" ? "access" : "settings"}
|
||||
avatarUrl={avatarAgent.data ? agentAvatarUrl(resolveAgentAppearance(avatarAgent.data.appearance, endpoint.assignedAgentId), 512, 1, "rest") : undefined} />
|
||||
</div>}
|
||||
{activeTab === "settings" && endpoint.provider !== "github" && <Settings endpointId={endpoint.id} endpoint={endpoint} />}
|
||||
{activeTab === "reviews" && endpoint.provider === "github" && <GitHubReviews endpointId={endpoint.id} reviewId={reviewId} />}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import type { ChatEndpoint } from "@/api/chatEndpoints";
|
||||
import { AgentAvatarDownload } from "@/components/AgentAvatarDownload";
|
||||
import { CopyText } from "@/components/CopyText";
|
||||
import { Link } from "@/lib/router";
|
||||
|
||||
export function gitHubBotMention(endpoint: Pick<ChatEndpoint, "botUsername">) {
|
||||
const slug = endpoint.botUsername?.replace(/^@/, "").replace(/\[bot\]$/, "");
|
||||
return slug ? `@${slug}` : null;
|
||||
}
|
||||
|
||||
export function gitHubAppSettingsUrl(endpoint: ChatEndpoint) {
|
||||
const github = endpoint.setup?.github;
|
||||
const slug = gitHubBotMention(endpoint)?.slice(1);
|
||||
if (!slug) return "https://github.com/settings/apps";
|
||||
if (github?.ownerType === "organization" && github.ownerLogin)
|
||||
return `https://github.com/organizations/${encodeURIComponent(github.ownerLogin)}/settings/apps/${encodeURIComponent(slug)}`;
|
||||
if (github?.ownerType === "personal")
|
||||
return `https://github.com/settings/apps/${encodeURIComponent(slug)}`;
|
||||
// Legacy manual connections may not record ownership type. Never assume it.
|
||||
return "https://github.com/settings/apps";
|
||||
}
|
||||
|
||||
export function GitHubBotMention({ endpoint }: { endpoint: ChatEndpoint }) {
|
||||
const mention = gitHubBotMention(endpoint);
|
||||
return mention ? (
|
||||
<CopyText
|
||||
text={mention}
|
||||
ariaLabel="Copy GitHub mention"
|
||||
title="Copy GitHub mention"
|
||||
className="break-all font-mono text-xs"
|
||||
>
|
||||
{mention}
|
||||
</CopyText>
|
||||
) : null;
|
||||
}
|
||||
|
||||
/** Optional provider-owned branding, shared by completion and management. */
|
||||
export function GitHubAppBranding({
|
||||
endpoint,
|
||||
avatarUrl,
|
||||
}: {
|
||||
endpoint: ChatEndpoint;
|
||||
avatarUrl?: string;
|
||||
}) {
|
||||
return (
|
||||
<details className="text-sm">
|
||||
<summary className="cursor-pointer text-muted-foreground">
|
||||
GitHub App name and logo
|
||||
</summary>
|
||||
<div className="mt-4 space-y-4">
|
||||
<p>
|
||||
This is your custom GitHub App
|
||||
{endpoint.botLabel ? (
|
||||
<>
|
||||
: <strong>{endpoint.botLabel}</strong>.
|
||||
</>
|
||||
) : (
|
||||
"."
|
||||
)}
|
||||
{endpoint.providerAccountLabel && (
|
||||
<>
|
||||
{" "}
|
||||
Owned by <strong>{endpoint.providerAccountLabel}</strong>.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
GitHub derives the @mention from the App name. Upload your logo in the
|
||||
App’s Display information settings.
|
||||
</p>
|
||||
{avatarUrl && (
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt={`${endpoint.assignedAgentName}’s avatar for download`}
|
||||
className="size-20 rounded-lg bg-muted object-contain"
|
||||
/>
|
||||
<AgentAvatarDownload
|
||||
avatarUrl={avatarUrl}
|
||||
name={endpoint.botLabel ?? endpoint.assignedAgentName}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<a
|
||||
href={gitHubAppSettingsUrl(endpoint)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center gap-2 underline underline-offset-4"
|
||||
>
|
||||
Edit App name and logo on GitHub <ExternalLink className="size-3" />
|
||||
</a>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
After renaming the App,{" "}
|
||||
<Link
|
||||
className="underline underline-offset-4"
|
||||
to={`/apps/chat/connect?provider=github&resume=${endpoint.id}&reconnect=1`}
|
||||
>
|
||||
reconnect this App
|
||||
</Link>{" "}
|
||||
to refresh its mention in Paperclip.
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import { GitHubPolicyEditor } from "./GitHubBotConfiguration";
|
||||
import { conversationDestination } from "./ChatConversationList";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { gitHubAppSettingsUrl, gitHubBotMention } from "./GitHubAppIdentity";
|
||||
import type { ChatEndpoint } from "@/api/chatEndpoints";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
tab: "settings",
|
||||
@@ -217,6 +219,55 @@ describe("GitHub bot management", () => {
|
||||
);
|
||||
});
|
||||
}
|
||||
it("shows the verified custom App mention and its own organization branding settings", async () => {
|
||||
const branded: ChatEndpoint = {
|
||||
...endpoint,
|
||||
provider: "github",
|
||||
status: "active",
|
||||
botLabel: "Maya Reviews",
|
||||
botUsername: "maya-reviews[bot]",
|
||||
providerAccountLabel: "acme",
|
||||
setup: {
|
||||
step: "complete",
|
||||
github: {
|
||||
stage: "verify",
|
||||
ownerType: "organization",
|
||||
ownerLogin: "acme",
|
||||
appSlug: "old-draft-name",
|
||||
},
|
||||
},
|
||||
};
|
||||
mocks.get.mockResolvedValue(branded);
|
||||
await render();
|
||||
expect(
|
||||
container.querySelector('button[aria-label="Copy GitHub mention"]')
|
||||
?.textContent,
|
||||
).toBe("@maya-reviews");
|
||||
expect(container.textContent).toContain("This is your custom GitHub App");
|
||||
const branding = [...container.querySelectorAll("a")].find((a) =>
|
||||
a.textContent?.includes("Edit App name and logo"),
|
||||
);
|
||||
expect(branding?.href).toBe(
|
||||
"https://github.com/organizations/acme/settings/apps/maya-reviews",
|
||||
);
|
||||
expect(
|
||||
container.querySelector("a[download]")?.getAttribute("download"),
|
||||
).toBe("Maya-Reviews-avatar.png");
|
||||
expect(mocks.save).not.toHaveBeenCalled();
|
||||
expect(
|
||||
gitHubAppSettingsUrl({
|
||||
...branded,
|
||||
setup: {
|
||||
step: "complete",
|
||||
github: { stage: "verify", ownerType: "personal" },
|
||||
},
|
||||
}),
|
||||
).toBe("https://github.com/settings/apps/maya-reviews");
|
||||
expect(
|
||||
gitHubAppSettingsUrl({ ...branded, setup: { step: "complete" } }),
|
||||
).toBe("https://github.com/settings/apps");
|
||||
expect(gitHubBotMention({ botUsername: null })).toBeNull();
|
||||
});
|
||||
it("keeps one draft across Settings, Access, and read-only tabs, then saves with the original revision", async () => {
|
||||
await render();
|
||||
await input(container.querySelector("textarea")!, "Edited instructions");
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
GitHubToggle,
|
||||
githubSelectClass,
|
||||
} from "./GitHubBotConfiguration";
|
||||
import { GitHubAppBranding } from "./GitHubAppIdentity";
|
||||
|
||||
export function GitHubRepositoryAccess({
|
||||
resources,
|
||||
@@ -115,9 +116,11 @@ export function GitHubRepositoryAccess({
|
||||
export function GitHubBotManagement({
|
||||
endpoint,
|
||||
view,
|
||||
avatarUrl,
|
||||
}: {
|
||||
endpoint: ChatEndpoint;
|
||||
view: "settings" | "access";
|
||||
avatarUrl?: string;
|
||||
}) {
|
||||
const client = useQueryClient();
|
||||
const query = useQuery({
|
||||
@@ -314,6 +317,7 @@ export function GitHubBotManagement({
|
||||
)}
|
||||
</div>
|
||||
</details>
|
||||
<GitHubAppBranding endpoint={endpoint} avatarUrl={avatarUrl} />
|
||||
</>
|
||||
)}
|
||||
</fieldset>
|
||||
|
||||
@@ -528,6 +528,7 @@ describe("GitHub App wizard", () => {
|
||||
await render("resume=draft-1");
|
||||
expect(container.querySelector("h1")?.textContent).toBe("GitHub connected");
|
||||
expect(container.textContent).toContain("Actual GitHub Name");
|
||||
expect(container.textContent).toContain("GitHub App name and logo");
|
||||
expect(container.textContent).toContain("acme/repo");
|
||||
expect(container.textContent).toContain("Configure a runtime");
|
||||
expect(container.textContent).not.toContain("Finish");
|
||||
|
||||
@@ -22,6 +22,9 @@ import { useBreadcrumbs } from "@/context/BreadcrumbContext";
|
||||
import { useNavigate, useSearchParams, Link } from "@/lib/router";
|
||||
import { buildPermissionsForTrustPreset } from "@/lib/trust-policy-ui";
|
||||
import { copyTextToClipboard } from "@/lib/clipboard";
|
||||
import { resolveAgentAppearance } from "@paperclipai/shared";
|
||||
import { agentAvatarUrl } from "@/lib/agent-avatar-url";
|
||||
import { GitHubAppBranding, gitHubBotMention } from "./GitHubAppIdentity";
|
||||
|
||||
/** Restrict native manifest submission to GitHub registration endpoints. */
|
||||
export function gitHubAppManifestAction(
|
||||
@@ -233,7 +236,7 @@ export function GitHubChatSetup() {
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
const mention = `@${bot?.botUsername?.replace(/\[bot\]$/, "") ?? "your-bot"} review this pull request`;
|
||||
const mention = `${(bot && gitHubBotMention(bot)) || "@your-bot"} review this pull request`;
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-3xl space-y-6 px-4 py-6 sm:px-6">
|
||||
{!identityOnly && !connected && (
|
||||
@@ -356,6 +359,8 @@ export function GitHubChatSetup() {
|
||||
{copied ? "Copied" : "Copy mention"}
|
||||
</Button>
|
||||
</div>
|
||||
<GitHubAppBranding endpoint={bot}
|
||||
avatarUrl={selectedAgent ? agentAvatarUrl(resolveAgentAppearance(selectedAgent.appearance, bot.assignedAgentId), 512, 1, "rest") : undefined} />
|
||||
<Link
|
||||
className="text-sm underline"
|
||||
to={`/apps/chat/${bot.id}/settings`}
|
||||
@@ -649,6 +654,7 @@ export function GitHubChatSetup() {
|
||||
<Label htmlFor="github-app-name">App name</Label>
|
||||
<Input
|
||||
id="github-app-name"
|
||||
aria-describedby="github-app-name-help"
|
||||
disabled={busy || !!state?.registration}
|
||||
maxLength={34}
|
||||
value={
|
||||
@@ -659,6 +665,9 @@ export function GitHubChatSetup() {
|
||||
}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
/>
|
||||
<p id="github-app-name-help" className="text-xs text-muted-foreground">
|
||||
Creates your own GitHub App. Its name determines the @mention; GitHub confirms the final handle.
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
GitHub will ask you to approve creation and select repositories.
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { useId, useState } from "react";
|
||||
import {
|
||||
ArrowRight,
|
||||
Check,
|
||||
Download,
|
||||
ExternalLink,
|
||||
Loader2,
|
||||
} from "lucide-react";
|
||||
import { useId } from "react";
|
||||
import { ArrowRight, Check, ExternalLink } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SetupWizardFooter } from "@/components/SetupWizard";
|
||||
import {
|
||||
AgentAvatarDownload,
|
||||
agentAvatarFilename,
|
||||
} from "@/components/AgentAvatarDownload";
|
||||
|
||||
export interface SlackAvatarProps {
|
||||
agentName: string;
|
||||
@@ -23,35 +21,7 @@ export function SlackAvatarContent({
|
||||
compact = false,
|
||||
}: SlackAvatarProps & { compact?: boolean }) {
|
||||
const id = useId();
|
||||
const filename = `${appName.replace(/[^a-zA-Z0-9_-]+/g, "-") || "agent"}-avatar.png`;
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [downloadError, setDownloadError] = useState(false);
|
||||
const download = async () => {
|
||||
if (downloading) return;
|
||||
setDownloading(true);
|
||||
setDownloadError(false);
|
||||
try {
|
||||
const response = await fetch(avatarUrl);
|
||||
if (
|
||||
!response.ok ||
|
||||
!response.headers.get("content-type")?.startsWith("image/png")
|
||||
)
|
||||
throw new Error("Avatar unavailable");
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.append(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1_000);
|
||||
} catch {
|
||||
setDownloadError(true);
|
||||
} finally {
|
||||
setDownloading(false);
|
||||
}
|
||||
};
|
||||
const filename = agentAvatarFilename(appName);
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<section
|
||||
@@ -76,29 +46,7 @@ export function SlackAvatarContent({
|
||||
PNG · 512 × 512 · Ready for Slack
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" asChild>
|
||||
<a
|
||||
href={avatarUrl}
|
||||
download={filename}
|
||||
aria-disabled={downloading}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
void download();
|
||||
}}
|
||||
>
|
||||
{downloading ? (
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
) : (
|
||||
<Download className="size-4" />
|
||||
)}
|
||||
Download avatar
|
||||
</a>
|
||||
</Button>
|
||||
{downloadError && (
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
Couldn’t download the avatar. Try downloading it again.
|
||||
</p>
|
||||
)}
|
||||
<AgentAvatarDownload avatarUrl={avatarUrl} name={appName} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -25,7 +25,15 @@ export const endpoint: ChatEndpoint = {
|
||||
botUsername: "maya-reviews[bot]",
|
||||
providerAccountLabel: "acme",
|
||||
allowUnlinkedPeople: false,
|
||||
setup: { step: "complete" },
|
||||
setup: {
|
||||
step: "complete",
|
||||
github: {
|
||||
stage: "verify",
|
||||
appSlug: "maya-reviews",
|
||||
ownerType: "organization",
|
||||
ownerLogin: "acme",
|
||||
},
|
||||
},
|
||||
};
|
||||
export const configuration: GitHubChatConfiguration = {
|
||||
version: 1,
|
||||
|
||||
@@ -14,8 +14,15 @@ import {
|
||||
GitHubReviewDetail,
|
||||
} from "@/pages/apps/chat/GitHubBotManagement";
|
||||
import { ChatConversationList } from "@/pages/apps/chat/ChatConversationList";
|
||||
import {
|
||||
GitHubAppBranding,
|
||||
GitHubBotMention,
|
||||
} from "@/pages/apps/chat/GitHubAppIdentity";
|
||||
import { resolveAgentAppearance } from "@paperclipai/shared";
|
||||
import { agentAvatarUrl } from "@/lib/agent-avatar-url";
|
||||
import {
|
||||
endpoint,
|
||||
agent,
|
||||
configuration,
|
||||
resources,
|
||||
reviews,
|
||||
@@ -157,6 +164,24 @@ export const RepositoryAccess: Story = {
|
||||
name: "03 Components / Repository access",
|
||||
render: () => component(<Repositories />),
|
||||
};
|
||||
export const AppIdentityAndLogo: Story = {
|
||||
name: "03 Components / App identity and logo",
|
||||
render: () =>
|
||||
component(
|
||||
<div className="space-y-6">
|
||||
<GitHubBotMention endpoint={endpoint} />
|
||||
<GitHubAppBranding
|
||||
endpoint={endpoint}
|
||||
avatarUrl={agentAvatarUrl(
|
||||
resolveAgentAppearance(agent.appearance, agent.id),
|
||||
512,
|
||||
1,
|
||||
"rest",
|
||||
)}
|
||||
/>
|
||||
</div>,
|
||||
),
|
||||
};
|
||||
export const ReviewHistory: Story = {
|
||||
name: "03 Components / Review rows",
|
||||
render: () =>
|
||||
|
||||
Reference in new issue
Block a user