Skip to content

Commit f5146c3

Browse files
posthog[bot]PostHog Codejonathanlab
authored
feat: render GitHub URLs as chips in chat messages (#1869)
Co-authored-by: PostHog Code <code@posthog.com> Co-authored-by: Jonathan Mieloo <32547391+jonathanlab@users.noreply.github.com>
1 parent 678007c commit f5146c3

3 files changed

Lines changed: 91 additions & 46 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { GithubLogoIcon, GitPullRequestIcon } from "@phosphor-icons/react";
2+
import { Chip } from "@posthog/quill";
3+
import type { ReactNode } from "react";
4+
5+
export function GithubRefChip({
6+
href,
7+
kind,
8+
children,
9+
}: {
10+
href: string;
11+
kind: "issue" | "pr";
12+
children: ReactNode;
13+
}) {
14+
const Icon = kind === "pr" ? GitPullRequestIcon : GithubLogoIcon;
15+
return (
16+
<Chip
17+
size="xs"
18+
variant="outline"
19+
onClick={() => window.open(href, "_blank")}
20+
className="cli-file-mention relative top-px max-w-full cursor-pointer! whitespace-nowrap pl-1 active:translate-y-0"
21+
>
22+
<Icon size={10} />
23+
<span className="min-w-0 truncate">{children}</span>
24+
</Chip>
25+
);
26+
}

apps/code/src/renderer/features/editor/components/MarkdownRenderer.tsx

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { CodeBlock } from "@components/CodeBlock";
22
import { Divider } from "@components/Divider";
33
import { HighlightedCode } from "@components/HighlightedCode";
44
import { List, ListItem } from "@components/List";
5+
import { parseGithubIssueUrl } from "@features/message-editor/utils/githubIssueUrl";
56
import { Blockquote, Checkbox, Code, Em, Kbd, Text } from "@radix-ui/themes";
67
import { memo, useMemo } from "react";
78
import type { Components } from "react-markdown";
89
import ReactMarkdown from "react-markdown";
910
import remarkGfm from "remark-gfm";
1011
import type { PluggableList } from "unified";
12+
import { GithubRefChip } from "./GithubRefChip";
1113

1214
interface MarkdownRendererProps {
1315
content: string;
@@ -87,39 +89,53 @@ export const baseComponents: Components = {
8789
{children}
8890
</del>
8991
),
90-
a: ({ href, children }) => (
91-
<a
92-
href={href}
93-
target="_blank"
94-
rel="noopener noreferrer"
95-
className="markdown-link"
96-
style={{
97-
fontSize: "var(--font-size-1)",
98-
display: "inline-flex",
99-
alignItems: "center",
100-
gap: "2px",
101-
}}
102-
>
103-
{children}
104-
<svg
105-
width="10"
106-
height="10"
107-
viewBox="0 0 12 12"
108-
fill="none"
109-
stroke="var(--accent-11)"
110-
strokeWidth="1.5"
111-
strokeLinecap="round"
112-
strokeLinejoin="round"
113-
style={{ marginLeft: "var(--space-1)", flexShrink: 0 }}
114-
aria-label="external link icon"
115-
role="img"
92+
a: ({ href, children }) => {
93+
const githubRef = href ? parseGithubIssueUrl(href) : null;
94+
if (githubRef) {
95+
const isAutoLink = typeof children === "string" && children === href;
96+
const label = isAutoLink
97+
? `${githubRef.owner}/${githubRef.repo}#${githubRef.number}`
98+
: children;
99+
return (
100+
<GithubRefChip href={githubRef.normalizedUrl} kind={githubRef.kind}>
101+
{label}
102+
</GithubRefChip>
103+
);
104+
}
105+
return (
106+
<a
107+
href={href}
108+
target="_blank"
109+
rel="noopener noreferrer"
110+
className="markdown-link"
111+
style={{
112+
fontSize: "var(--font-size-1)",
113+
display: "inline-flex",
114+
alignItems: "center",
115+
gap: "2px",
116+
}}
116117
>
117-
<path d="M4.5 1.5H2.25C1.836 1.5 1.5 1.836 1.5 2.25V9.75C1.5 10.164 1.836 10.5 2.25 10.5H9.75C10.164 10.5 10.5 10.164 10.5 9.75V7.5" />
118-
<path d="M7.5 1.5H10.5V4.5" />
119-
<path d="M5.25 6.75L10.5 1.5" />
120-
</svg>
121-
</a>
122-
),
118+
{children}
119+
<svg
120+
width="10"
121+
height="10"
122+
viewBox="0 0 12 12"
123+
fill="none"
124+
stroke="var(--accent-11)"
125+
strokeWidth="1.5"
126+
strokeLinecap="round"
127+
strokeLinejoin="round"
128+
style={{ marginLeft: "var(--space-1)", flexShrink: 0 }}
129+
aria-label="external link icon"
130+
role="img"
131+
>
132+
<path d="M4.5 1.5H2.25C1.836 1.5 1.5 1.836 1.5 2.25V9.75C1.5 10.164 1.836 10.5 2.25 10.5H9.75C10.164 10.5 10.5 10.164 10.5 9.75V7.5" />
133+
<path d="M7.5 1.5H10.5V4.5" />
134+
<path d="M5.25 6.75L10.5 1.5" />
135+
</svg>
136+
</a>
137+
);
138+
},
123139
kbd: ({ children }) => <Kbd size="1">{children}</Kbd>,
124140
ul: ({ children }) => (
125141
<List as="ul" size="1">

apps/code/src/renderer/features/sessions/components/session-update/parseFileMentions.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { GithubRefChip } from "@features/editor/components/GithubRefChip";
12
import {
23
baseComponents,
34
defaultRemarkPlugins,
45
} from "@features/editor/components/MarkdownRenderer";
5-
import { File, GithubLogo, Warning } from "@phosphor-icons/react";
6+
import { File, Warning } from "@phosphor-icons/react";
67
import { Text } from "@radix-ui/themes";
78
import { unescapeXmlAttr } from "@utils/xml";
89
import type { ReactNode } from "react";
@@ -11,9 +12,9 @@ import type { Components } from "react-markdown";
1112
import ReactMarkdown from "react-markdown";
1213

1314
const MENTION_TAG_REGEX =
14-
/<file\s+path="([^"]+)"\s*\/>|<github_issue\s+number="([^"]+)"(?:\s+title="([^"]*)")?(?:\s+url="([^"]*)")?\s*\/>|<error_context\s+label="([^"]*)">[\s\S]*?<\/error_context>/g;
15+
/<file\s+path="([^"]+)"\s*\/>|<(github_issue|github_pr)\s+number="([^"]+)"(?:\s+title="([^"]*)")?(?:\s+url="([^"]*)")?\s*\/>|<error_context\s+label="([^"]*)">[\s\S]*?<\/error_context>/g;
1516
const MENTION_TAG_TEST =
16-
/<(?:file\s+path|github_issue\s+number|error_context\s+label)="[^"]+"/;
17+
/<(?:file\s+path|github_issue\s+number|github_pr\s+number|error_context\s+label)="[^"]+"/;
1718
const SLASH_COMMAND_START = /^\/([a-zA-Z][\w-]*)(?=\s|$)/;
1819

1920
const inlineComponents: Components = {
@@ -129,26 +130,28 @@ export function parseMentionTags(content: string): ReactNode[] {
129130
/>,
130131
);
131132
} else if (match[2]) {
132-
const issueNumber = match[2];
133-
const issueTitle = match[3] ? unescapeXmlAttr(match[3]) : undefined;
134-
const issueUrl = match[4] ? unescapeXmlAttr(match[4]) : undefined;
133+
const kind = match[2] === "github_pr" ? "pr" : "issue";
134+
const issueNumber = match[3];
135+
const issueTitle = match[4] ? unescapeXmlAttr(match[4]) : undefined;
136+
const issueUrl = match[5] ? unescapeXmlAttr(match[5]) : "";
135137
const label = issueTitle
136138
? `#${issueNumber} - ${issueTitle}`
137139
: `#${issueNumber}`;
138140
parts.push(
139-
<MentionChip
140-
key={`issue-${matchIndex}`}
141-
icon={<GithubLogo size={12} />}
142-
label={label}
143-
onClick={issueUrl ? () => window.open(issueUrl, "_blank") : undefined}
144-
/>,
141+
<GithubRefChip
142+
key={`${match[2]}-${matchIndex}`}
143+
href={issueUrl}
144+
kind={kind}
145+
>
146+
{label}
147+
</GithubRefChip>,
145148
);
146-
} else if (match[5]) {
149+
} else if (match[6]) {
147150
parts.push(
148151
<MentionChip
149152
key={`error-ctx-${matchIndex}`}
150153
icon={<Warning size={12} />}
151-
label={unescapeXmlAttr(match[5])}
154+
label={unescapeXmlAttr(match[6])}
152155
/>,
153156
);
154157
}

0 commit comments

Comments
 (0)