From 91893b160e4a8c2773c6c55853e4b6ab39687a94 Mon Sep 17 00:00:00 2001 From: Blair Chen Date: Sun, 18 Feb 2024 16:48:38 +0800 Subject: [PATCH] Minor code refactoring and cleanup --- .github/workflows/docker.chatgpt-lite.yml | 2 +- .../Chat => app/chat}/PersonaPanel.tsx | 3 +- app/chat/page.tsx | 3 +- components/Chat/Chat.tsx | 5 -- components/Chat/Message.tsx | 36 ++++++++-- components/Chat/index.ts | 3 - components/{ => Header}/Header.tsx | 4 +- components/Header/index.ts | 1 + components/Link.tsx | 13 +++- components/Markdown.tsx | 72 ++++++++++++------- components/Themes/ThemeContext.ts | 2 + components/Themes/index.ts | 2 - hooks/useCopyToClipboard.ts | 25 +++++++ styles/globals.scss | 27 ------- 14 files changed, 124 insertions(+), 74 deletions(-) rename {components/Chat => app/chat}/PersonaPanel.tsx (98%) rename components/{ => Header}/Header.tsx (96%) create mode 100644 components/Header/index.ts create mode 100644 hooks/useCopyToClipboard.ts diff --git a/.github/workflows/docker.chatgpt-lite.yml b/.github/workflows/docker.chatgpt-lite.yml index b2372b0..afd58fc 100644 --- a/.github/workflows/docker.chatgpt-lite.yml +++ b/.github/workflows/docker.chatgpt-lite.yml @@ -7,7 +7,7 @@ on: - main jobs: - build_and_push_image: + build-and-push-image: name: Push image to Docker Hub runs-on: ubuntu-latest steps: diff --git a/components/Chat/PersonaPanel.tsx b/app/chat/PersonaPanel.tsx similarity index 98% rename from components/Chat/PersonaPanel.tsx rename to app/chat/PersonaPanel.tsx index 266b29f..3cbd8aa 100644 --- a/components/Chat/PersonaPanel.tsx +++ b/app/chat/PersonaPanel.tsx @@ -16,8 +16,7 @@ import { import { debounce } from 'lodash-es' import { AiOutlineClose, AiOutlineDelete, AiOutlineEdit } from 'react-icons/ai' import { LuMessageSquarePlus } from 'react-icons/lu' -import ChatContext from './chatContext' -import { Persona } from './interface' +import { ChatContext, Persona } from '@/components' export interface PersonaPanelProps {} diff --git a/app/chat/page.tsx b/app/chat/page.tsx index f821d4a..15277cd 100644 --- a/app/chat/page.tsx +++ b/app/chat/page.tsx @@ -1,8 +1,9 @@ 'use client' import { Suspense } from 'react' import { Flex } from '@radix-ui/themes' -import { Chat, ChatContext, ChatSideBar, PersonaPanel, useChatHook } from '@/components' +import { Chat, ChatContext, ChatSideBar, useChatHook } from '@/components' import PersonaModal from './PersonaModal' +import PersonaPanel from './PersonaPanel' const ChatProvider = () => { const provider = useChatHook() diff --git a/components/Chat/Chat.tsx b/components/Chat/Chat.tsx index e1251ff..6ebbbf4 100644 --- a/components/Chat/Chat.tsx +++ b/components/Chat/Chat.tsx @@ -10,7 +10,6 @@ import { useState } from 'react' import { Flex, Heading, IconButton, ScrollArea, Tooltip } from '@radix-ui/themes' -import clipboard from 'clipboard' import ContentEditable from 'react-contenteditable' import toast from 'react-hot-toast' import { AiOutlineClear, AiOutlineLoading3Quarters, AiOutlineUnorderedList } from 'react-icons/ai' @@ -206,10 +205,6 @@ const Chat = (props: ChatProps, ref: any) => { } }) - useEffect(() => { - new clipboard('.copy-btn').on('success', () => {}) - }, []) - return ( { const { role, content } = props.message const isUser = role === 'user' + const copy = useCopyToClipboard() + const [tooltipOpen, setTooltipOpen] = useState(false) + + const onCopy = useCallback(() => { + copy(content, (isSuccess) => { + if (isSuccess) { + setTooltipOpen(true) + } + }) + }, [content, copy]) return ( : } + fallback={isUser ? : } color={isUser ? undefined : 'green'} size="2" radius="full" @@ -34,7 +47,22 @@ const Message = (props: MessageProps) => { }} > ) : ( - {content} + + {content} + + + setTooltipOpen(false)} + > + + + + + )} diff --git a/components/Chat/index.ts b/components/Chat/index.ts index 10c258c..d1ae10d 100644 --- a/components/Chat/index.ts +++ b/components/Chat/index.ts @@ -1,8 +1,5 @@ -'use client' - export * from './interface' export { default as Chat } from './Chat' export { default as ChatSideBar } from './ChatSideBar' -export { default as PersonaPanel } from './PersonaPanel' export { default as ChatContext } from './chatContext' export { default as useChatHook } from './useChatHook' diff --git a/components/Header.tsx b/components/Header/Header.tsx similarity index 96% rename from components/Header.tsx rename to components/Header/Header.tsx index 85af287..259fd44 100644 --- a/components/Header.tsx +++ b/components/Header/Header.tsx @@ -6,8 +6,8 @@ import { Avatar, Flex, Heading, IconButton, Select, Tooltip } from '@radix-ui/th import cs from 'classnames' import NextLink from 'next/link' import { FaAdjust, FaGithub, FaMoon, FaRegSun } from 'react-icons/fa' -import { Link } from './Link' -import { useTheme } from './Themes' +import { Link } from '../Link' +import { useTheme } from '../Themes' export const Header = () => { const { theme, setTheme } = useTheme() diff --git a/components/Header/index.ts b/components/Header/index.ts new file mode 100644 index 0000000..f887995 --- /dev/null +++ b/components/Header/index.ts @@ -0,0 +1 @@ +export * from './Header' diff --git a/components/Link.tsx b/components/Link.tsx index 22ec012..907df28 100644 --- a/components/Link.tsx +++ b/components/Link.tsx @@ -8,12 +8,19 @@ interface LinkProps { className?: string color?: LinkOwnProps['color'] children?: React.ReactNode + disabled?: boolean + highContrast?: boolean } -export const Link = ({ href, className, children, color }: LinkProps) => { +export const Link = ({ href, className, children, color, highContrast, disabled }: LinkProps) => { return ( - - + + {children} diff --git a/components/Markdown.tsx b/components/Markdown.tsx index 5f092fa..f26626f 100644 --- a/components/Markdown.tsx +++ b/components/Markdown.tsx @@ -1,7 +1,10 @@ -import { IconButton } from '@radix-ui/themes' +'use client' + +import { ClassAttributes, Fragment, HTMLAttributes, useCallback, useState } from 'react' +import { IconButton, Tooltip } from '@radix-ui/themes' import cs from 'classnames' import { RxClipboardCopy } from 'react-icons/rx' -import ReactMarkdown from 'react-markdown' +import ReactMarkdown, { ExtraProps } from 'react-markdown' import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism' import rehypeKatex from 'rehype-katex' @@ -11,12 +14,54 @@ import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' import remarkParse from 'remark-parse' import remarkRehype from 'remark-rehype' +import { useCopyToClipboard } from '@/hooks/useCopyToClipboard' export interface MarkdownProps { className?: string children: string } +const HighlightCode = ( + props: ClassAttributes & HTMLAttributes & ExtraProps +) => { + const { children, className, ref, ...rest } = props + const match = /language-(\w+)/.exec(className || '') + const copy = useCopyToClipboard() + const [tooltipOpen, setTooltipOpen] = useState(false) + + const code = match ? String(children).replace(/\n$/, '') : '' + + const onCopy = useCallback(() => { + copy(code, (isSuccess) => { + if (isSuccess) { + setTooltipOpen(true) + } + }) + }, [code, copy]) + + return match ? ( + + + setTooltipOpen(false)} + > + + + + + {code} + + + ) : ( + + {children} + + ) +} + export const Markdown = ({ className, children }: MarkdownProps) => { return ( { rehypePlugins={[rehypeRaw, rehypeKatex, rehypeStringify]} components={{ code(props) { - const { children, className, ref, ...rest } = props - const match = /language-(\w+)/.exec(className || '') - return match ? ( - <> - - - - - {String(children).replace(/\n$/, '')} - - - ) : ( - - {children} - - ) + return } }} > @@ -52,5 +78,3 @@ export const Markdown = ({ className, children }: MarkdownProps) => { ) } - -export default Markdown diff --git a/components/Themes/ThemeContext.ts b/components/Themes/ThemeContext.ts index f476422..a28d607 100644 --- a/components/Themes/ThemeContext.ts +++ b/components/Themes/ThemeContext.ts @@ -1,3 +1,5 @@ +'use client' + import { createContext } from 'react' import { UseThemeProps } from './interface' diff --git a/components/Themes/index.ts b/components/Themes/index.ts index 4b5494f..70732dd 100644 --- a/components/Themes/index.ts +++ b/components/Themes/index.ts @@ -1,5 +1,3 @@ -'use client' - export * from './interface' export * from './useTheme' export * from './ThemeProvider' diff --git a/hooks/useCopyToClipboard.ts b/hooks/useCopyToClipboard.ts new file mode 100644 index 0000000..d5f7f5c --- /dev/null +++ b/hooks/useCopyToClipboard.ts @@ -0,0 +1,25 @@ +import { useCallback } from 'react' + +type CopyFn = (text: string, callback?: (data: any) => void) => Promise // Return success + +export const useCopyToClipboard = (): CopyFn => { + const copy: CopyFn = useCallback(async (text, callback) => { + if (!navigator?.clipboard) { + console.warn('Clipboard not supported') + callback?.(false) + return false + } + + try { + await navigator.clipboard.writeText(text) + callback?.(true) + return true + } catch (error) { + console.warn('Copy failed', error) + callback?.(false) + return false + } + }, []) + + return copy +} diff --git a/styles/globals.scss b/styles/globals.scss index ba5e8e1..ef94061 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -424,33 +424,6 @@ body { @apply flex flex-col; } -.table-fixed-header { - .rt-TableRootTable { - overflow: initial !important; - } - - .rt-TableHeader { - --table-row-background-color: var(--gray-2) !important; - position: relative; - z-index: 1; - .rt-TableCell { - &:first-child { - border-top-left-radius: calc(var(--table-border-radius) - 1px); - } - &:last-child { - border-top-right-radius: calc(var(--table-border-radius) - 1px); - } - } - } - - .react-loading-skeleton { - z-index: 0; - } - .rt-ScrollAreaScrollbar { - z-index: 2; - } -} - .navbar-collapse { @apply max-md:absolute max-md:top-14 max-md:gap-0 max-md:left-0 max-md:right-0 max-md:px-1 max-md:flex-col max-md:items-start max-md:backdrop-blur-md max-md:bg-[--color-page-background] max-md:shadow-sm max-md:overflow-hidden; height: 0;