51 |
59 |
63 |
64 |
65 | {/*
66 | {messages.length === 0 &&
67 | exampleMessages.map((example, index) => (
68 |
1 && 'hidden md:block'
72 | }`}
73 | onClick={async () => {
74 | setMessages(currentMessages => [
75 | ...currentMessages,
76 | {
77 | id: nanoid(),
78 | display:
{example.message}
79 | }
80 | ])
81 |
82 | const responseMessage = await submitUserMessage(
83 | example.message
84 | )
85 |
86 | setMessages(currentMessages => [
87 | ...currentMessages,
88 | responseMessage
89 | ])
90 | }}
91 | >
92 |
{example.heading}
93 |
94 | {example.subheading}
95 |
96 |
97 | ))}
98 |
*/}
99 |
100 |
101 | {/*
*/}
102 |
103 |
104 | )
105 | }
106 |
--------------------------------------------------------------------------------
/components/chat-scroll-anchor.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useInView } from 'react-intersection-observer'
5 |
6 | import { useAtBottom } from '@/lib/hooks/use-at-bottom'
7 |
8 | interface ChatScrollAnchorProps {
9 | trackVisibility?: boolean
10 | }
11 |
12 | export function ChatScrollAnchor({ trackVisibility }: ChatScrollAnchorProps) {
13 | const isAtBottom = useAtBottom()
14 | const { ref, entry, inView } = useInView({
15 | trackVisibility,
16 | delay: 100,
17 | rootMargin: '0px 0px -150px 0px'
18 | })
19 |
20 | React.useEffect(() => {
21 | if (isAtBottom && trackVisibility && !inView) {
22 | entry?.target.scrollIntoView({
23 | block: 'start'
24 | })
25 | }
26 | }, [inView, entry, isAtBottom, trackVisibility])
27 |
28 | return
29 | }
30 |
--------------------------------------------------------------------------------
/components/chat.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { cn } from '@/lib/utils'
4 | import { ChatList } from '@/components/chat-list'
5 | import { ChatPanel } from '@/components/chat-panel'
6 | import { EmptyScreen } from '@/components/empty-screen'
7 | import { useLocalStorage } from '@/lib/hooks/use-local-storage'
8 | import { useEffect, useState } from 'react'
9 | import { useUIState, useAIState } from 'ai/rsc'
10 | import { usePathname, useRouter } from 'next/navigation'
11 | import { Message } from '@/lib/chat/actions'
12 | import { useScrollAnchor } from '@/lib/hooks/use-scroll-anchor'
13 | import { Header } from './header'
14 | import { Models } from '@/lib/types'
15 | import { useSidebar } from '@/lib/hooks/use-sidebar'
16 | // import { toast } from 'sonner'
17 |
18 | import { useInput } from '@/lib/hooks/use-form-input'
19 |
20 | export interface ChatProps extends React.ComponentProps<'div'> {
21 | initialMessages?: Message[]
22 | id?: string
23 | models: Models
24 | }
25 |
26 | export function Chat({ id, className, models }: ChatProps) {
27 | const router = useRouter()
28 | const path = usePathname()
29 | // const [input, setInput] = useState('')
30 | const { inputValue, setInputValue } = useInput()
31 | const [messages] = useUIState()
32 | const [aiState] = useAIState()
33 |
34 | const [_, setNewChatId] = useLocalStorage('newChatId', id)
35 |
36 | useEffect(() => {
37 | if (!path.includes('chat') && messages.length === 1) {
38 | window.history.replaceState({}, '', `/chat/${id}`)
39 | }
40 | }, [id, path, messages])
41 |
42 | useEffect(() => {
43 | const messagesLength = aiState.messages?.length
44 | if (messagesLength === 2) {
45 | router.refresh()
46 | }
47 | }, [aiState.messages, router])
48 |
49 | useEffect(() => {
50 | setNewChatId(id)
51 | setInputValue('')
52 | // eslint-disable-next-line react-hooks/exhaustive-deps
53 | }, [])
54 |
55 | const { messagesRef, scrollRef, visibilityRef, isAtBottom, scrollToBottom } =
56 | useScrollAnchor()
57 |
58 | const { isLeftSidebarOpen, isRightSidebarOpen } = useSidebar()
59 |
60 | return (
61 |
70 |
71 |
72 | {messages.length ?
:
}
73 |
74 |
75 |
80 |
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/components/clear-history.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useRouter } from 'next/navigation'
5 | import { toast } from 'sonner'
6 |
7 | import { ServerActionResult } from '@/lib/types'
8 | import { Button } from '@/components/ui/button'
9 | import {
10 | AlertDialog,
11 | AlertDialogAction,
12 | AlertDialogCancel,
13 | AlertDialogContent,
14 | AlertDialogDescription,
15 | AlertDialogFooter,
16 | AlertDialogHeader,
17 | AlertDialogTitle,
18 | AlertDialogTrigger
19 | } from '@/components/ui/alert-dialog'
20 | import { IconSpinner } from '@/components/ui/icons'
21 |
22 | interface ClearHistoryProps {
23 | isEnabled: boolean
24 | clearChats: () => ServerActionResult
25 | }
26 |
27 | export function ClearHistory({
28 | isEnabled = false,
29 | clearChats
30 | }: ClearHistoryProps) {
31 | const [open, setOpen] = React.useState(false)
32 | const [isPending, startTransition] = React.useTransition()
33 | const router = useRouter()
34 |
35 | return (
36 |
37 |
38 |
39 | {isPending && }
40 | Clear history
41 |
42 |
43 |
44 |
45 | Are you absolutely sure?
46 |
47 | This will permanently delete your chat history and remove your data
48 | from our servers.
49 |
50 |
51 |
52 | Cancel
53 | {
56 | event.preventDefault()
57 | startTransition(async () => {
58 | const result = await clearChats()
59 | if (result && 'error' in result) {
60 | toast.error(result.error)
61 | return
62 | }
63 |
64 | setOpen(false)
65 | })
66 | }}
67 | >
68 | {isPending && }
69 | Delete
70 |
71 |
72 |
73 |
74 | )
75 | }
76 |
--------------------------------------------------------------------------------
/components/empty-screen.tsx:
--------------------------------------------------------------------------------
1 | import { UseChatHelpers } from 'ai/react'
2 |
3 | import { Button } from '@/components/ui/button'
4 | import { ExternalLink } from '@/components/external-link'
5 | import {
6 | IconArrowRight,
7 | IconGitHub,
8 | IconNextChat,
9 | getModelIcon
10 | } from '@/components/ui/icons'
11 | import { Card, CardContent } from '@/components/ui/card'
12 | import { useAIState } from 'ai/rsc'
13 |
14 | import { prompts } from '@/database/prompts'
15 |
16 | import { PromptCardHome } from './prompt-list'
17 | import Link from 'next/link'
18 |
19 | const exampleMessages = [
20 | {
21 | heading: 'Explain technical concepts',
22 | message: `What is a "serverless function"?`
23 | },
24 | {
25 | heading: 'Summarize an article',
26 | message: 'Summarize the following article for a 2nd grader: \n'
27 | },
28 | {
29 | heading: 'Draft an email',
30 | message: `Draft an email to my boss about the following: \n`
31 | }
32 | ]
33 |
34 | // export function EmptyScreen({ setInput }: Pick) {
35 | export function EmptyScreen() {
36 | const [aiState] = useAIState()
37 | const selected = aiState.model
38 |
39 | const icon = getModelIcon(selected.created_by)
40 |
41 | // get the first 5 prompts
42 | const examplePrompts = prompts.slice(87, 91)
43 |
44 | return (
45 |
46 |
47 |
48 |
49 |
{icon}
50 |
51 |
52 | Model
53 | /
54 |
55 | {selected.label}
56 |
57 |
58 |
59 |
60 | {selected.description}
61 |
62 |
63 |
64 | {/*
65 |
Context
66 |
67 |
68 |
69 |
70 |
Input Pricing
71 |
72 |
73 |
74 |
75 |
Output Pricing
76 |
77 |
78 |
*/}
79 |
80 | You can start a conversation here or try the following examples:
81 |
82 |
83 |
84 | {examplePrompts.map((prompt: any, index: any) => (
85 |
86 | ))}
87 |
88 |
89 |
90 |
91 |
92 |
96 |
97 | Github
98 |
99 |
100 |
101 |
105 |
106 | Twitter
107 |
108 |
109 |
110 |
111 |
112 |
113 | )
114 | }
115 |
--------------------------------------------------------------------------------
/components/external-link.tsx:
--------------------------------------------------------------------------------
1 | export function ExternalLink({
2 | href,
3 | children
4 | }: {
5 | href: string
6 | children: React.ReactNode
7 | }) {
8 | return (
9 |
14 | {children}
15 |
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/components/footer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { cn } from '@/lib/utils'
4 | import { ExternalLink } from '@/components/external-link'
5 |
6 | export function FooterText({ className, ...props }: React.ComponentProps<'p'>) {
7 | return (
8 |
15 | Open source AI chatbot built with{' '}
16 | Next.js and{' '}
17 |
18 | Vercel AI SDK
19 |
20 | .
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/components/header.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 |
5 | import { Button, buttonVariants } from '@/components/ui/button'
6 | import { IconSettings, IconDownload } from '@/components/ui/icons'
7 | import { SidebarMobile } from './sidebar-mobile'
8 | import { SidebarToggle } from './sidebar-toggle'
9 | import { ChatHistory } from './chat-history'
10 | import { ModelSelector } from './model-selector'
11 | import { ModelConfig } from './model-config'
12 | import { Models } from '@/lib/types'
13 |
14 | import { PromptLibrary } from './prompt-library'
15 |
16 | interface HeaderProps {
17 | models: Models
18 | }
19 |
20 | export function Header({ models }: HeaderProps) {
21 | return (
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {/* space-x-2 */}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/components/hover-card-model-select.txt:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 | {/*
*/}
15 |
16 | Anthropic
17 | /
18 |
19 | claude-instant-1.2
20 |
21 |
22 |
23 |
24 | A faster, cheaper yet still very capable version of
25 | Claude, which can handle a range of tasks including casual
26 | dialogue, text analysis, summarization, and document
27 | comprehension.
28 |
29 |
30 |
31 |
32 |
Context
33 |
34 | 100,000 tokens
35 |
36 |
37 |
38 |
39 | Input Pricing
40 |
41 |
42 | $1.63 / million tokens
43 |
44 |
45 |
46 |
47 | Output Pricing
48 |
49 |
50 | $5.51 / million tokens
51 |
52 |
53 |
54 |
Uptime
55 |
56 |
57 |
58 |
12 hrs ago
59 |
60 |
61 | 100.00% Uptime
62 |
63 |
64 |
Now
65 |
66 |
67 |
68 |
69 |
70 |
71 | {/*
{peekedModel.name}
72 |
73 | {peekedModel.description}
74 |
75 | {peekedModel.strengths ? (
76 |
77 |
78 | Strengths
79 |
80 |
81 | {peekedModel.strengths}
82 |
83 |
84 | ) : null} */}
85 |
86 |
--------------------------------------------------------------------------------
/components/markdown.tsx:
--------------------------------------------------------------------------------
1 | import { FC, memo } from 'react'
2 | import ReactMarkdown, { Options } from 'react-markdown'
3 |
4 | export const MemoizedReactMarkdown: FC = memo(
5 | ReactMarkdown,
6 | (prevProps, nextProps) =>
7 | prevProps.children === nextProps.children &&
8 | prevProps.className === nextProps.className
9 | )
10 |
--------------------------------------------------------------------------------
/components/model-config-slider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 |
5 | import { SliderProps } from '@radix-ui/react-slider'
6 |
7 | import {
8 | HoverCard,
9 | HoverCardContent,
10 | HoverCardTrigger
11 | } from '@/components//ui/hover-card'
12 | import { Slider } from '@/components//ui/slider'
13 | import { Label } from '@/components/ui/label'
14 | import { InfoCircledIcon } from '@radix-ui/react-icons'
15 |
16 | interface ConfigSliderProps {
17 | defaultValue: SliderProps['defaultValue']
18 | label: string
19 | step?: number
20 | min_value: number
21 | max_value: number
22 | information: string
23 | }
24 |
25 | export function ConfigSlider({
26 | defaultValue,
27 | label,
28 | step = 1,
29 | min_value,
30 | max_value,
31 | information
32 | }: ConfigSliderProps) {
33 | const [value, setValue] = React.useState(defaultValue)
34 |
35 | return (
36 |
37 |
38 |
39 |
40 |
41 |
42 | {label}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {value}
51 |
52 |
53 |
63 |
64 |
68 | {information}
69 |
70 |
71 |
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/components/model-config.tsx:
--------------------------------------------------------------------------------
1 | interface ModelConfigProps {}
2 | import { Button } from '@/components/ui/button'
3 |
4 | import {
5 | DropdownMenu,
6 | DropdownMenuContent,
7 | DropdownMenuTrigger
8 | } from '@/components/ui/dropdown-menu'
9 | import { MixerHorizontalIcon } from '@radix-ui/react-icons'
10 |
11 | export function ModelConfig({}: ModelConfigProps) {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | model config is in development :)
22 |
23 |
24 | )
25 | }
26 |
27 | // TODO: DELETE THIS
28 | import {
29 | Card,
30 | CardContent,
31 | CardDescription,
32 | CardFooter
33 | } from '@/components/ui/card'
34 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
35 |
36 | export function TabsDemo() {
37 | return (
38 |
39 |
40 | Only This Chat
41 | All llama2 Chats
42 | {/* All Chats */}
43 |
44 |
45 |
46 | This Configuration will only affect this chat.
47 |
48 |
49 | {/*
50 | Conversation
51 |
52 | This Configuration will only affect this chat.
53 |
54 | */}
55 |
56 |
57 | {/*
58 | Name
59 |
60 |
61 |
62 | Username
63 |
64 |
*/}
65 | {/*
66 |
67 |
68 | */}
69 |
70 |
71 | Cancel
72 | Save
73 |
74 |
75 |
76 |
77 | {/*
78 |
79 | Password
80 |
81 | Change your password here. After saving, you'll be logged out.
82 |
83 |
84 |
85 |
86 | Current password
87 |
88 |
89 |
90 | New password
91 |
92 |
93 |
94 |
95 | Save password
96 |
97 | */}
98 |
99 |
100 | )
101 | }
102 |
--------------------------------------------------------------------------------
/components/model-selector-item.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useMutationObserver } from '@/lib/hooks/use-mutation-observer'
5 | import { CommandItem } from '@/components/ui/command'
6 | import { getModelIcon } from '@/components/ui/icons'
7 | import { CheckIcon } from '@radix-ui/react-icons'
8 | import { cn } from '@/lib/utils'
9 |
10 | interface ModelItemProps {
11 | model: any
12 | isSelected: boolean
13 | onSelect: () => void
14 | onPeek: (model: any) => void
15 | }
16 |
17 | export function ModelItem({
18 | model,
19 | isSelected,
20 | onSelect,
21 | onPeek
22 | }: ModelItemProps) {
23 | const ref = React.useRef(null)
24 | const icon = getModelIcon(model.created_by)
25 |
26 | useMutationObserver(ref, mutations => {
27 | for (const mutation of mutations) {
28 | if (mutation.type === 'attributes') {
29 | if (mutation.attributeName === 'aria-selected') {
30 | onPeek(model)
31 | }
32 | }
33 | }
34 | })
35 |
36 | return (
37 |
43 | {icon}
44 |
45 | {model.name}
46 |
47 |
53 |
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/components/model-selector.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { CaretSortIcon } from '@radix-ui/react-icons'
3 | import { useRouter } from 'next/navigation'
4 | import { ModelItem } from './model-selector-item'
5 | import { useAIState } from 'ai/rsc'
6 | import { getModelIcon } from '@/components/ui/icons'
7 | import { Models } from '@/lib/types'
8 | import { usePathname } from 'next/navigation'
9 |
10 | import { Button } from '@/components/ui/button'
11 | import {
12 | Command,
13 | CommandEmpty,
14 | CommandGroup,
15 | CommandInput,
16 | CommandList
17 | } from '@/components/ui/command'
18 |
19 | import {
20 | Popover,
21 | PopoverContent,
22 | PopoverTrigger
23 | } from '@/components/ui/popover'
24 |
25 | interface ModelSelectorProps {
26 | models: Models
27 | }
28 |
29 | export function ModelSelector({ models }: ModelSelectorProps) {
30 | // url and check if include something after chat then don't allow to change model
31 | const pathname = usePathname()
32 | const router = useRouter()
33 | // const router = useRouter()
34 | const [aiState, setAIState] = useAIState()
35 |
36 | const [open, setOpen] = React.useState(false)
37 | const [peekedModel, setPeekedModel] = React.useState(models[0])
38 | const installed = Object.values(models).filter(model => model.installed)
39 |
40 | const [selectedModel, setSelectedModel] = React.useState(
41 | aiState.model ?? installed[0]
42 | )
43 |
44 | const icon = getModelIcon(selectedModel.created_by)
45 |
46 | return (
47 |
48 |
49 |
56 |
57 | {icon}
58 |
59 | {selectedModel.created_by} - {selectedModel.label}
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | No Models found.
72 |
73 | {installed.map(model => (
74 | setPeekedModel(model)}
79 | onSelect={() => {
80 | if (pathname.includes('/chat/')) {
81 | router.push(`/?model=${model.name}`)
82 | } else {
83 | setSelectedModel(model)
84 | setAIState({ ...aiState, model: model })
85 | setOpen(false)
86 | }
87 | }}
88 | />
89 | ))}
90 |
91 |
92 |
93 |
94 |
95 | )
96 | }
97 |
--------------------------------------------------------------------------------
/components/onboarding-screen.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Button } from '@/components/ui/button'
4 | import {
5 | Card,
6 | CardContent,
7 | CardDescription,
8 | CardFooter,
9 | CardHeader,
10 | CardTitle
11 | } from '@/components/ui/card'
12 | import { Input } from '@/components/ui/input'
13 | import { Label } from '@/components/ui/label'
14 | import {
15 | Select,
16 | SelectContent,
17 | SelectItem,
18 | SelectTrigger,
19 | SelectValue
20 | } from '@/components/ui/select'
21 | import { IconCopy, IconOllama } from './ui/icons'
22 | import { DownloadIcon } from '@radix-ui/react-icons'
23 |
24 | // Get up and running with large language models locally.
25 |
26 | // macOS
27 | // Download
28 |
29 | // Windows preview
30 | // Download
31 |
32 | // Linux
33 | // curl -fsSL https://ollama.com/install.sh | sh
34 | // Manual install instructions
35 |
36 | // Docker
37 | // The official Ollama Docker image ollama/ollama is available on Docker Hub.
38 |
39 | // Libraries
40 | // ollama-python
41 | // ollama-js
42 | // Quickstart
43 | // To run and chat with Llama 2:
44 |
45 | // ollama run llama2
46 |
47 | export function OnBoardingScreen() {
48 | return (
49 |
50 |
51 |
52 | Get started with Ollama
53 |
54 | Oops! It seems like you don't have Ollama running or installed,
55 | or perhaps you haven't added any models yet.
56 |
57 |
58 |
59 | {/* download mac, linux, windows */}
60 |
61 |
62 | Install Ollama for your operating system:
63 |
64 |
65 | macOS
66 | Windows
67 | Linux
68 |
69 |
70 |
71 |
72 |
73 | Install Ollama manually using the following command:
74 |
75 |
76 |
77 |
78 |
79 |
80 | Install one of supported models:
81 |
82 |
83 | Once you have Ollama installed and running and have added a model
84 | make sure ollama is running on port 11434
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | )
95 | }
96 |
97 | function ClickToCopyCommand({ command }: { command: string }) {
98 | // const [isCopied, setIsCopied] = React.useState(false)
99 |
100 | return (
101 |
102 |
107 |
108 |
109 |
110 |
111 | )
112 | }
113 |
--------------------------------------------------------------------------------
/components/prompt-form.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import Textarea from 'react-textarea-autosize'
5 | import { useActions, useUIState } from 'ai/rsc'
6 | import { UserMessage } from './stocks/message'
7 | import { type AI } from '@/lib/chat/actions'
8 | import { Button } from '@/components/ui/button'
9 | import { IconArrowElbow, IconPaperPlane, IconPlus } from '@/components/ui/icons'
10 | import { useEnterSubmit } from '@/lib/hooks/use-enter-submit'
11 | import { nanoid } from 'nanoid'
12 | import { useRouter } from 'next/navigation'
13 | import { LightningBoltIcon } from '@radix-ui/react-icons'
14 |
15 | import {
16 | Tooltip,
17 | TooltipContent,
18 | TooltipTrigger
19 | } from '@/components/ui/tooltip'
20 | import { useInput } from '@/lib/hooks/use-form-input'
21 |
22 | export function PromptForm() {
23 | const router = useRouter()
24 | const { formRef, onKeyDown } = useEnterSubmit()
25 | const inputRef = React.useRef(null)
26 | const { submitUserMessage } = useActions()
27 | const [_, setMessages] = useUIState()
28 |
29 | const { inputValue, setInputValue } = useInput()
30 |
31 | React.useEffect(() => {
32 | if (inputRef.current) {
33 | inputRef.current.focus()
34 | }
35 | }, [])
36 |
37 | return (
38 |
117 | )
118 | }
119 |
--------------------------------------------------------------------------------
/components/prompt-library.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import Link from 'next/link'
4 |
5 | import { cn } from '@/lib/utils'
6 | import { SidebarList } from '@/components/sidebar-list'
7 | import { buttonVariants } from '@/components/ui/button'
8 | import { IconPlus } from '@/components/ui/icons'
9 | import { PromptList } from './prompt-list'
10 |
11 | interface PromptLibraryProps {}
12 |
13 | export function PromptLibrary({}: PromptLibraryProps) {
14 | return (
15 |
16 | {/*
17 |
24 |
25 | New Prompt
26 |
27 |
*/}
28 |
31 | {Array.from({ length: 10 }).map((_, i) => (
32 |
36 | ))}
37 |
38 | }
39 | >
40 | {/* */}
41 |
42 |
43 |
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/components/prompt-list.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { clearChats, getChats } from '@/app/actions'
4 | import { ClearHistory } from '@/components/clear-history'
5 | import { SidebarItems } from '@/components/sidebar-items'
6 | import { ThemeToggle } from '@/components/theme-toggle'
7 | import { cache } from 'react'
8 | import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'
9 | import { Button } from './ui/button'
10 | import { prompts } from '@/database/prompts'
11 | import { Card, CardHeader, CardTitle } from './ui/card'
12 | import { useInput } from '@/lib/hooks/use-form-input'
13 | import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard'
14 |
15 | import {
16 | DropdownMenu,
17 | DropdownMenuContent,
18 | DropdownMenuItem,
19 | DropdownMenuLabel,
20 | DropdownMenuSeparator,
21 | DropdownMenuTrigger
22 | } from './ui/dropdown-menu'
23 | import {
24 | BookmarkIcon,
25 | CheckIcon,
26 | CopyIcon,
27 | DotsHorizontalIcon,
28 | RocketIcon
29 | } from '@radix-ui/react-icons'
30 | import React from 'react'
31 |
32 | interface PromptListProps {
33 | children?: React.ReactNode
34 | }
35 |
36 | export async function PromptList({}: PromptListProps) {
37 | return (
38 |
81 |
82 |
83 | {prompt.title}
84 |
85 |
86 |
87 |
92 | {isAction ? (
93 |
94 | ) : (
95 |
96 | )}
97 |
98 |
99 |
100 | {
102 | setIsAction(true)
103 | setTimeout(() => {
104 | setIsAction(false)
105 | }, 1000)
106 | setInputValue(prompt.prompt)
107 | }}
108 | >
109 |
110 | Use Prompt
111 |
112 | {/*
113 |
114 | Bookmark
115 | */}
116 | {
118 | setIsAction(true)
119 | setTimeout(() => {
120 | setIsAction(false)
121 | }, 1000)
122 | copyToClipboard(prompt.prompt)
123 | }}
124 | >
125 |
126 | Copy to clipboard
127 |
128 |
129 |
130 |
131 |
132 | {prompt.remark}
133 |
134 |
135 | >
136 | )
137 | }
138 |
139 | export function PromptCardHome({ prompt }: any) {
140 | const { inputValue, setInputValue } = useInput()
141 |
142 | return (
143 |