docs(copyButton): Add LLM question feature to the document copy button#470
docs(copyButton): Add LLM question feature to the document copy button#470MaxLee-dev wants to merge 8 commits intomainfrom
Conversation
- 마크다운 복사 버튼을 split button 형태로 변경 - 드롭다운 메뉴에 다음 기능 추가: - 마크다운으로 보기 (새 탭에서 열기) - Claude에게 질문하기 - ChatGPT에게 질문하기 - Anthropic, OpenAI 아이콘 추가 - 외부 링크 아이콘 표시 - useMemo로 메뉴 아이템 최적화 - 모든 텍스트 한국어로 변경 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Summary of ChangesHello @MaxLee-dev, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 문서 복사 버튼의 기능을 확장하여 사용자 편의성을 크게 향상시키는 것을 목표로 합니다. 단순히 마크다운을 복사하는 것을 넘어, 사용자가 문서를 외부 LLM(대규모 언어 모델)에 직접 질문할 수 있는 기능을 통합하여 정보 탐색 및 활용을 더욱 용이하게 합니다. 이는 사용자가 문서 내용을 더 깊이 이해하고 다양한 방식으로 활용할 수 있도록 돕는 중요한 개선 사항입니다. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
- docs/[[...slug]]/page.tsx: vapor-ui.goorm.io 도메인 추가 - theme/[[...tool]]/page.tsx: vapor-ui.goorm.io 도메인 추가 - blocks/[...slug]/page.tsx: markdownUrl prop 전달 - block-page-header.tsx: markdownUrl prop 추가 및 조건부 렌더링 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
이 PR은 문서 복사 버튼에 LLM 질문 기능을 추가하여 사용자 편의성을 높였습니다. 기존 버튼을 분할 버튼(Split Button)으로 리팩토링하고, 드롭다운 메뉴에 Claude 및 ChatGPT 질문 기능을 추가한 점은 긍정적입니다. 그러나 markdownUrl prop 처리와 관련하여 잠재적인 보안 문제가 있습니다. 특히, 이 prop에 대한 유효성 검사 부족은 Cross-Site Scripting (XSS), Prompt Injection 및 안전하지 않은 데이터 가져오기로 이어질 수 있습니다. 이러한 위험을 완화하기 위해 강력한 URL 유효성 검사 및 프로토콜 검사를 추가하는 것이 좋습니다. 전반적으로 좋은 개선이지만, 가독성과 유지보수성을 높이기 위한 몇 가지 제안 사항과 함께 보안 취약점에 대한 조치가 필요합니다.
| const AnthropicIcon = () => ( | ||
| <svg | ||
| fill="currentColor" | ||
| fillRule="evenodd" | ||
| height={16} | ||
| width={16} | ||
| viewBox="0 0 24 24" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <path d="M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z" /> | ||
| </svg> | ||
| ); | ||
|
|
||
| const OpenAIIcon = () => ( | ||
| <svg | ||
| fill="currentColor" | ||
| fillRule="evenodd" | ||
| height={16} | ||
| width={16} | ||
| viewBox="0 0 24 24" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z" /> | ||
| </svg> | ||
| ); |
There was a problem hiding this comment.
AnthropicIcon과 OpenAIIcon 컴포넌트가 copy-button.tsx 파일 내에 직접 정의되어 있어 파일이 길어지고 가독성을 해칠 수 있습니다.
이 아이콘 컴포넌트들을 별도의 파일(예: icons.tsx)로 분리하여 관리하는 것을 제안합니다. 이렇게 하면 copy-button.tsx 파일은 주 로직에 더 집중할 수 있어 코드 유지보수성이 향상됩니다.
예시:
apps/website/src/components/copy-button/icons.tsx
export const AnthropicIcon = () => (
<svg
fill="currentColor"
fillRule="evenodd"
height={16}
width={16}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="..." />
</svg>
);
export const OpenAIIcon = () => (
<svg
fill="currentColor"
fillRule="evenodd"
height={16}
width={16}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="..." />
</svg>
);apps/website/src/components/copy-button/copy-button.tsx
import { AnthropicIcon, OpenAIIcon } from './icons';
// ...| <HStack | ||
| gap="0" | ||
| className="rounded-md shadow-[inset_0_0_0_1px_var(--vapor-color-border-secondary)] w-fit" | ||
| > |
There was a problem hiding this comment.
Adding role="group" might be worth considering.
| {checked ? <ConfirmOutlineIcon /> : <CopyAsMarkdownOutlineIcon />} | ||
| 마크다운 복사 | ||
| </Button> | ||
| <span className="w-px bg-v-gray-200 self-stretch" /> |
There was a problem hiding this comment.
This element is used as a separator, so adding role="separator" would make it clearer.
| onClick: () => { | ||
| if (isValidMarkdownUrl(markdownUrl)) { | ||
| trackCopyButtonEvent(COPY_BUTTON_ACTIONS.VIEW_MARKDOWN, markdownUrl); | ||
| window.open(markdownUrl, '_blank'); | ||
| } | ||
| }, |
There was a problem hiding this comment.
The "View in Markdown" item cannot be tracked when clicked. However, the other two elements trigger event tracking when clicked, even if no action occurs. This issue arises due to differences in validation locations using isValidMarkdownUrl. To ensure consistency, it would be best to standardize this behavior.
| const menuItems = useMemo( | ||
| () => [ | ||
| { | ||
| label: '마크다운으로 보기', | ||
| icon: markdownIcon, | ||
| onClick: () => { | ||
| if (isValidMarkdownUrl(markdownUrl)) { | ||
| trackCopyButtonEvent(COPY_BUTTON_ACTIONS.VIEW_MARKDOWN, markdownUrl); | ||
| window.open(markdownUrl, '_blank'); | ||
| } | ||
| }, | ||
| isExternal: true, | ||
| }, | ||
| { | ||
| label: 'Claude에게 질문하기', | ||
| icon: anthropicIcon, | ||
| onClick: () => { | ||
| trackCopyButtonEvent(COPY_BUTTON_ACTIONS.ASK_CLAUDE, markdownUrl); | ||
| openLLMChat('claude', markdownUrl); | ||
| }, | ||
| isExternal: true, | ||
| }, | ||
| { | ||
| label: 'ChatGPT에게 질문하기', | ||
| icon: openAIIcon, | ||
| onClick: () => { | ||
| trackCopyButtonEvent(COPY_BUTTON_ACTIONS.ASK_CHATGPT, markdownUrl); | ||
| openLLMChat('chatgpt', markdownUrl); | ||
| }, | ||
| isExternal: true, | ||
| }, | ||
| ], | ||
| [markdownUrl], | ||
| ); |
There was a problem hiding this comment.
menuItems should ideally only know what actions each button actually performs. Currently, each menu item handles validation, event tracking, and even the action logic it must process, making it difficult to grasp at a glance what each element does.
Therefore, I propose the following structure to separate the MenuItem handler from the actions of the buttons:
const handleMenuItemClick = (action: CopyButtonAction, handler: () => void) => {
if (!isValidMarkdownUrl(markdownUrl)) return;
trackCopyButtonEvent(action, markdownUrl);
handler();
};
const menuItems = useMemo(
() => [
{
label: '마크다운으로 보기',
icon: markdownIcon,
action: COPY_BUTTON_ACTIONS.VIEW_MARKDOWN,
handler: () => window.open(markdownUrl, '_blank'),
isExternal: true,
},
{
label: 'Claude에게 질문하기',
icon: anthropicIcon,
action: COPY_BUTTON_ACTIONS.ASK_CLAUDE,
handler: () => openLLMChat('claude', markdownUrl),
isExternal: true,
},
{
label: 'ChatGPT에게 질문하기',
icon: openAIIcon,
action: COPY_BUTTON_ACTIONS.ASK_CHATGPT,
handler: () => openLLMChat('chatgpt', markdownUrl),
isExternal: true,
},
],
[markdownUrl],
);
// ...
{
menuItems.map(({ label, icon, action, handler, isExternal }) => (
<Menu.Item key={label} onClick={() => handleMenuItemClick(action, handler)}>
{icon}
{label}
{isExternal && <OpenInNewOutlineIcon width={16} height={16} className="ml-auto" />}
</Menu.Item>
));
}This approach should also maintain the consistency of the validation logic mentioned earlier.
There was a problem hiding this comment.
The tracking logic seems tightly coupled only to the Copy Button, and since these functions are directly called within the Copy Button to create the tracking functions, it feels like concerns aren't well separated.
Event tracking could be useful elsewhere too, so it might be a good idea to refactor it over time.
Summary
Changes
copy-button.tsx: Refactored into split button + menu structurepage.tsx: Added full domain tomarkdownUrltool-detail-sheet-client.tsx: RemovedsizepropFeatures