mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
Merge pull request #1 from RecursivelyAI/atai/0619/react_input_bindings
WIP: almost usable v0.1
This commit is contained in:
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "examples/next-openai: Node",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "pnpm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/examples/next-openai/**",
|
||||
"!**/node_modules/**"
|
||||
],
|
||||
"sourceMaps": true
|
||||
},
|
||||
{
|
||||
"name": "examples/next-openai: Chrome",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000/",
|
||||
"webRoot": "${workspaceFolder}/examples/next-openai",
|
||||
"sourceMaps": true
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "examples/next-openai: Full",
|
||||
"configurations": ["examples/next-openai: Node", "examples/next-openai: Chrome"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"ai": "2.1.3",
|
||||
"ai": "2.1.12",
|
||||
"next": "13.4.4-canary.11",
|
||||
"langchain": "^0.0.86",
|
||||
"react": "18.2.0",
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "examples/next-openai: Node",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "pnpm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/**",
|
||||
"!**/node_modules/**"
|
||||
],
|
||||
"sourceMaps": true
|
||||
},
|
||||
{
|
||||
"name": "examples/next-openai: Chrome",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000/",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"sourceMaps": true
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "examples/next-openai: Full",
|
||||
"configurations": ["examples/next-openai: Node", "examples/next-openai: Chrome"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,17 +12,18 @@ const openai = new OpenAIApi(config)
|
||||
export const runtime = 'edge'
|
||||
|
||||
export async function POST(req: Request) {
|
||||
// Extract the `prompt` from the body of the request
|
||||
const { messages } = await req.json()
|
||||
const { messages, function_call, functions } = await req.json()
|
||||
|
||||
// Ask OpenAI for a streaming chat completion given the prompt
|
||||
const response = await openai.createChatCompletion({
|
||||
model: 'gpt-3.5-turbo',
|
||||
model: 'gpt-4',
|
||||
stream: true,
|
||||
messages: messages.map((message: any) => ({
|
||||
content: message.content,
|
||||
role: message.role
|
||||
}))
|
||||
})),
|
||||
functions,
|
||||
function_call
|
||||
})
|
||||
|
||||
// Convert the response into a friendly text-stream
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, ReactNode, useCallback } from 'react'
|
||||
import { AnnotatedFunction } from './use-make-copilot-actionable'
|
||||
import useTree, { TreeNodeId } from './use-tree'
|
||||
import { ChatCompletionFunctions } from 'openai-edge/types/api'
|
||||
import { FunctionCallHandler } from 'ai'
|
||||
|
||||
export interface CopilotContextParams {
|
||||
entryPoints: Record<string, AnnotatedFunction<any[]>>
|
||||
getChatCompletionFunctions: () => ChatCompletionFunctions[]
|
||||
getFunctionCallHandler: () => FunctionCallHandler
|
||||
setEntryPoint: (id: string, entryPoint: AnnotatedFunction<any[]>) => void
|
||||
removeEntryPoint: (id: string) => void
|
||||
|
||||
getContextString: () => string
|
||||
addContext: (context: string, parentId?: string) => TreeNodeId
|
||||
removeContext: (id: TreeNodeId) => void
|
||||
}
|
||||
export const CopilotContext = React.createContext<CopilotContextParams>(
|
||||
{} as CopilotContextParams
|
||||
)
|
||||
|
||||
export function CopilotProvider({
|
||||
children
|
||||
}: {
|
||||
children: ReactNode
|
||||
}): JSX.Element {
|
||||
const [entryPoints, setEntryPoints] = useState<
|
||||
Record<string, AnnotatedFunction<any[]>>
|
||||
>({})
|
||||
|
||||
const { addElement, removeElement, printTree } = useTree()
|
||||
|
||||
const setEntryPoint = useCallback(
|
||||
(id: string, entryPoint: AnnotatedFunction<any[]>) => {
|
||||
setEntryPoints(prevPoints => {
|
||||
return {
|
||||
...prevPoints,
|
||||
[id]: entryPoint
|
||||
}
|
||||
})
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const removeEntryPoint = useCallback((id: string) => {
|
||||
setEntryPoints(prevPoints => {
|
||||
const newPoints = { ...prevPoints }
|
||||
delete newPoints[id]
|
||||
return newPoints
|
||||
})
|
||||
}, [])
|
||||
|
||||
const getContextString = useCallback(() => {
|
||||
return printTree()
|
||||
}, [printTree])
|
||||
|
||||
const addContext = useCallback(
|
||||
(context: string, parentId?: string) => {
|
||||
return addElement(context, parentId)
|
||||
},
|
||||
[addElement]
|
||||
)
|
||||
|
||||
const removeContext = useCallback(
|
||||
(id: string) => {
|
||||
removeElement(id)
|
||||
},
|
||||
[removeElement]
|
||||
)
|
||||
|
||||
const getChatCompletionFunctions = useCallback(() => {
|
||||
return entryPointsToChatCompletionFunctions(Object.values(entryPoints))
|
||||
}, [entryPoints])
|
||||
|
||||
const getFunctionCallHandler = useCallback(() => {
|
||||
return entryPointsToFunctionCallHandler(Object.values(entryPoints))
|
||||
}, [entryPoints])
|
||||
|
||||
return (
|
||||
<CopilotContext.Provider
|
||||
value={{
|
||||
entryPoints,
|
||||
getChatCompletionFunctions,
|
||||
getFunctionCallHandler,
|
||||
setEntryPoint,
|
||||
removeEntryPoint,
|
||||
getContextString,
|
||||
addContext,
|
||||
removeContext
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CopilotContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function entryPointsToFunctionCallHandler(
|
||||
entryPoints: AnnotatedFunction<any[]>[]
|
||||
): FunctionCallHandler {
|
||||
return async (chatMessages, functionCall) => {
|
||||
let entrypointsByFunctionName: Record<string, AnnotatedFunction<any[]>> = {}
|
||||
for (let entryPoint of entryPoints) {
|
||||
entrypointsByFunctionName[entryPoint.name] = entryPoint
|
||||
}
|
||||
|
||||
const entryPointFunction =
|
||||
entrypointsByFunctionName[functionCall.name || '']
|
||||
if (entryPointFunction) {
|
||||
let parsedFunctionCallArguments: Record<string, any>[] = []
|
||||
if (functionCall.arguments) {
|
||||
parsedFunctionCallArguments = JSON.parse(functionCall.arguments)
|
||||
}
|
||||
|
||||
const paramsInCorrectOrder: any[] = []
|
||||
for (let arg of entryPointFunction.argumentAnnotations) {
|
||||
paramsInCorrectOrder.push(
|
||||
parsedFunctionCallArguments[
|
||||
arg.name as keyof typeof parsedFunctionCallArguments
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
await entryPointFunction.implementation(...paramsInCorrectOrder)
|
||||
|
||||
// commented out becasue for now we don't want to return anything
|
||||
// const result = await entryPointFunction.implementation(
|
||||
// ...parsedFunctionCallArguments
|
||||
// );
|
||||
// const functionResponse: ChatRequest = {
|
||||
// messages: [
|
||||
// ...chatMessages,
|
||||
// {
|
||||
// id: nanoid(),
|
||||
// name: functionCall.name,
|
||||
// role: 'function' as const,
|
||||
// content: JSON.stringify(result),
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
|
||||
// return functionResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function entryPointsToChatCompletionFunctions(
|
||||
entryPoints: AnnotatedFunction<any[]>[]
|
||||
): ChatCompletionFunctions[] {
|
||||
return entryPoints.map(annotatedFunctionToChatCompletionFunction)
|
||||
}
|
||||
|
||||
function annotatedFunctionToChatCompletionFunction(
|
||||
annotatedFunction: AnnotatedFunction<any[]>
|
||||
): ChatCompletionFunctions {
|
||||
// Create the parameters object based on the argumentAnnotations
|
||||
let parameters: { [key: string]: any } = {}
|
||||
for (let arg of annotatedFunction.argumentAnnotations) {
|
||||
parameters[arg.name] = { type: arg.type, description: arg.description }
|
||||
}
|
||||
|
||||
let requiredParameterNames: string[] = []
|
||||
for (let arg of annotatedFunction.argumentAnnotations) {
|
||||
if (arg.required) {
|
||||
requiredParameterNames.push(arg.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the ChatCompletionFunctions object
|
||||
let chatCompletionFunction: ChatCompletionFunctions = {
|
||||
name: annotatedFunction.name,
|
||||
description: annotatedFunction.description,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: parameters,
|
||||
required: requiredParameterNames
|
||||
}
|
||||
}
|
||||
|
||||
return chatCompletionFunction
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import { Inter } from 'next/font/google'
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata = {
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app'
|
||||
title: 'Copilot/Next/OpenAI example app',
|
||||
description: 'Copilot/Next/OpenAI example app'
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -14,8 +14,8 @@ export default function RootLayout({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<html className="h-full" lang="en">
|
||||
<body className={`h-full ${inter.className}`}>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import { useChat } from 'ai/react'
|
||||
|
||||
export default function Chat() {
|
||||
const { messages, input, handleInputChange, handleSubmit } = useChat()
|
||||
import React from 'react'
|
||||
import { Providers } from '@/chat-components/providers'
|
||||
import { GoodPeopleBadPeople } from '@/components/good-people-bad-people'
|
||||
import { SidebarProvider } from './sidebar/sidebar-context'
|
||||
|
||||
export default function CopilotControlled() {
|
||||
return (
|
||||
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
|
||||
{messages.length > 0
|
||||
? messages.map(m => (
|
||||
<div key={m.id} className="whitespace-pre-wrap">
|
||||
{m.role === 'user' ? 'User: ' : 'AI: '}
|
||||
{m.content}
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
|
||||
value={input}
|
||||
placeholder="Say something..."
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<Providers>
|
||||
<SidebarProvider>
|
||||
<div className="w-full h-full bg-slate-300">
|
||||
<GoodPeopleBadPeople />
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</Providers>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import React, { createContext, ReactNode, useCallback } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Sidebar } from './sidebar'
|
||||
|
||||
interface SidebarContextType {
|
||||
isSidebarOpen: boolean
|
||||
toggleSidebar: () => void
|
||||
}
|
||||
|
||||
export const SidebarContext = createContext<SidebarContextType>({
|
||||
isSidebarOpen: false,
|
||||
toggleSidebar: () => {}
|
||||
})
|
||||
|
||||
interface SidebarProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
export function SidebarProvider({ children }: SidebarProviderProps) {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true)
|
||||
|
||||
const toggleSidebar = useCallback(() => {
|
||||
setSidebarOpen(prev => !prev)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider
|
||||
value={{ isSidebarOpen: sidebarOpen, toggleSidebar }}
|
||||
>
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
height: '100%',
|
||||
width: sidebarOpen ? 'calc(100% - 450px)' : '100%', // New
|
||||
position: 'absolute', // New
|
||||
transition: 'width 0.5s ease-in-out' // New
|
||||
}}
|
||||
>
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
height: '100%',
|
||||
width: '450px',
|
||||
position: 'absolute',
|
||||
right: sidebarOpen ? '0' : '-450px',
|
||||
transition: 'right 0.5s ease-in-out'
|
||||
}}
|
||||
>
|
||||
<Sidebar setSidebarOpen={setSidebarOpen} />
|
||||
</div>
|
||||
{!sidebarOpen && (
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '5%',
|
||||
right: '20px',
|
||||
transform: 'translateY(-50%)',
|
||||
transition: 'opacity 0.5s ease-in-out'
|
||||
}}
|
||||
className="bg-white text-black p-2 rounded-lg shadow-lg"
|
||||
>
|
||||
Open Copilot
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</SidebarContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Chat } from '@/chat-components/chat'
|
||||
import React from 'react'
|
||||
|
||||
export interface SidebarProps {
|
||||
setSidebarOpen: (open: boolean) => void
|
||||
}
|
||||
|
||||
export function Sidebar(props: SidebarProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className="shadow-lg bg-white flex flex-col"
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
>
|
||||
<SidebarTopBar {...props} />
|
||||
<Chat />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
function SidebarTopBar(props: SidebarProps): JSX.Element {
|
||||
return (
|
||||
<div className="py-6 bg-white flex items-center justify-between px-4">
|
||||
<h1 className="text-base font-semibold leading-6 text-gray-900">
|
||||
Copilot Chat
|
||||
</h1>
|
||||
<div className="ml-3 flex h-7 items-center">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => props.setSidebarOpen(false)}
|
||||
>
|
||||
<span className="sr-only">Close panel</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
'use client'
|
||||
|
||||
import { useRef, useContext, useEffect, useMemo } from 'react'
|
||||
import { CopilotContext } from './copilot-context'
|
||||
import { generateRandomString } from './utils'
|
||||
|
||||
export function useMakeCopilotActionable<ActionInput extends any[]>(
|
||||
annotatedFunction: AnnotatedFunction<ActionInput>,
|
||||
dependencies: any[]
|
||||
) {
|
||||
const idRef = useRef(generateRandomString(10)) // generate a unique id
|
||||
const { setEntryPoint, removeEntryPoint } = useContext(CopilotContext)
|
||||
|
||||
const memoizedAnnotatedFunction: AnnotatedFunction<ActionInput> = useMemo(
|
||||
() => ({
|
||||
name: annotatedFunction.name,
|
||||
description: annotatedFunction.description,
|
||||
argumentAnnotations: annotatedFunction.argumentAnnotations,
|
||||
implementation: annotatedFunction.implementation
|
||||
}),
|
||||
dependencies
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setEntryPoint(
|
||||
idRef.current,
|
||||
memoizedAnnotatedFunction as AnnotatedFunction<any[]>
|
||||
)
|
||||
|
||||
return () => {
|
||||
removeEntryPoint(idRef.current)
|
||||
}
|
||||
}, [memoizedAnnotatedFunction, setEntryPoint, removeEntryPoint])
|
||||
}
|
||||
|
||||
export interface AnnotatedFunctionArgument {
|
||||
name: string
|
||||
type: string
|
||||
description: string
|
||||
allowedValues?: any[]
|
||||
required: boolean
|
||||
}
|
||||
|
||||
export interface AnnotatedFunction<Inputs extends any[]> {
|
||||
name: string
|
||||
description: string
|
||||
argumentAnnotations: AnnotatedFunctionArgument[]
|
||||
implementation: (...args: Inputs) => Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import { useRef, useContext, useEffect } from 'react'
|
||||
import { CopilotContext } from './copilot-context'
|
||||
import { generateRandomString } from './utils'
|
||||
|
||||
export function useMakeCopilotReadable(
|
||||
information: string,
|
||||
parentId?: string
|
||||
): string | undefined {
|
||||
const { addContext, removeContext } = useContext(CopilotContext)
|
||||
const idRef = useRef<string>()
|
||||
|
||||
useEffect(() => {
|
||||
const id = addContext(information, parentId)
|
||||
idRef.current = id
|
||||
|
||||
return () => {
|
||||
removeContext(id)
|
||||
}
|
||||
}, [information, parentId, addContext, removeContext])
|
||||
|
||||
return idRef.current
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { useReducer, useCallback } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export type TreeNodeId = string
|
||||
|
||||
export interface TreeNode {
|
||||
id: TreeNodeId
|
||||
value: string
|
||||
children: TreeNode[]
|
||||
parentId?: TreeNodeId
|
||||
}
|
||||
|
||||
export type Tree = TreeNode[]
|
||||
|
||||
export interface UseTreeReturn {
|
||||
tree: Tree
|
||||
addElement: (value: string, parentId?: TreeNodeId) => TreeNodeId
|
||||
printTree: () => string
|
||||
removeElement: (id: TreeNodeId) => void
|
||||
}
|
||||
|
||||
const findNode = (nodes: Tree, id: TreeNodeId): TreeNode | undefined => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === id) {
|
||||
return node
|
||||
}
|
||||
const result = findNode(node.children, id)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const removeNode = (nodes: Tree, id: TreeNodeId): Tree => {
|
||||
return nodes.reduce((result: Tree, node) => {
|
||||
if (node.id !== id) {
|
||||
const newNode = { ...node, children: removeNode(node.children, id) }
|
||||
result.push(newNode)
|
||||
}
|
||||
return result
|
||||
}, [])
|
||||
}
|
||||
|
||||
const treeIndentationRepresentation = (
|
||||
index: number,
|
||||
indentLevel: number
|
||||
): string => {
|
||||
if (indentLevel === 0) {
|
||||
return (index + 1).toString()
|
||||
} else if (indentLevel === 1) {
|
||||
return String.fromCharCode(65 + index) // 65 is the ASCII value for 'A'
|
||||
} else if (indentLevel === 2) {
|
||||
return String.fromCharCode(97 + index) // 97 is the ASCII value for 'a'
|
||||
} else {
|
||||
throw new Error('Indentation level not supported')
|
||||
}
|
||||
}
|
||||
|
||||
const printNode = (node: TreeNode, prefix = '', indentLevel = 0): string => {
|
||||
const indent = ' '.repeat(3).repeat(indentLevel)
|
||||
|
||||
const prefixPlusIndentLength = prefix.length + indent.length
|
||||
const subsequentLinesPrefix = ' '.repeat(prefixPlusIndentLength)
|
||||
|
||||
const valueLines = node.value.split('\n')
|
||||
|
||||
const outputFirstLine = `${indent}${prefix}${valueLines[0]}`
|
||||
const outputSubsequentLines = valueLines
|
||||
.slice(1)
|
||||
.map(line => `${subsequentLinesPrefix}${line}`)
|
||||
.join('\n')
|
||||
|
||||
let output = `${outputFirstLine}\n`
|
||||
if (outputSubsequentLines) {
|
||||
output += `${outputSubsequentLines}\n`
|
||||
}
|
||||
|
||||
node.children.forEach(
|
||||
(child, index) =>
|
||||
(output += printNode(
|
||||
child,
|
||||
`${prefix}${treeIndentationRepresentation(index, indentLevel + 1)}. `,
|
||||
indentLevel + 1
|
||||
))
|
||||
)
|
||||
return output
|
||||
}
|
||||
|
||||
// Action types
|
||||
type Action =
|
||||
| { type: 'ADD_NODE'; value: string; parentId?: string; id: string }
|
||||
| { type: 'REMOVE_NODE'; id: string }
|
||||
|
||||
// Reducer function
|
||||
function treeReducer(state: Tree, action: Action): Tree {
|
||||
switch (action.type) {
|
||||
case 'ADD_NODE': {
|
||||
const { value, parentId, id: newNodeId } = action
|
||||
const newNode: TreeNode = {
|
||||
id: newNodeId,
|
||||
value,
|
||||
children: []
|
||||
}
|
||||
|
||||
if (parentId) {
|
||||
const parent = findNode(state, parentId)
|
||||
if (parent) {
|
||||
newNode.parentId = parentId
|
||||
parent.children.push(newNode)
|
||||
} else {
|
||||
throw new Error(`Parent with id ${parentId} not found`)
|
||||
}
|
||||
} else {
|
||||
return [...state, newNode]
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
case 'REMOVE_NODE':
|
||||
return removeNode(state, action.id)
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
// useTree hook
|
||||
const useTree = (): UseTreeReturn => {
|
||||
const [tree, dispatch] = useReducer(treeReducer, [])
|
||||
|
||||
const addElement = useCallback(
|
||||
(value: string, parentId?: string): TreeNodeId => {
|
||||
const newNodeId = uuidv4() // Generate new ID outside of dispatch
|
||||
dispatch({ type: 'ADD_NODE', value, parentId, id: newNodeId })
|
||||
return newNodeId // Return the new ID
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const removeElement = useCallback((id: TreeNodeId): void => {
|
||||
dispatch({ type: 'REMOVE_NODE', id })
|
||||
}, [])
|
||||
|
||||
const printTree = (): string => {
|
||||
let output = ''
|
||||
tree.forEach(
|
||||
(node, index) =>
|
||||
(output += printNode(
|
||||
node,
|
||||
`${treeIndentationRepresentation(index, 0)}. `
|
||||
))
|
||||
)
|
||||
return output
|
||||
}
|
||||
|
||||
return { tree, addElement, printTree, removeElement }
|
||||
}
|
||||
|
||||
export default useTree
|
||||
@@ -0,0 +1,11 @@
|
||||
'use client'
|
||||
export function generateRandomString(length: number) {
|
||||
let result = ''
|
||||
const characters =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
const charactersLength = characters.length
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { type Message } from 'ai'
|
||||
|
||||
import { Separator } from '@/chat-components/ui/separator'
|
||||
import { ChatMessage } from '@/chat-components/chat-message'
|
||||
|
||||
export interface ChatList {
|
||||
messages: Message[]
|
||||
}
|
||||
|
||||
export function ChatList({ messages }: ChatList) {
|
||||
// we don't want to display system messages
|
||||
const displayedMessages = messages.filter(
|
||||
message => message.role !== 'system'
|
||||
)
|
||||
|
||||
if (!displayedMessages.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative mx-auto max-w-2xl px-0">
|
||||
{displayedMessages.map((message, index) => (
|
||||
<div key={index}>
|
||||
<ChatMessage message={message} />
|
||||
{index < displayedMessages.length - 1 && (
|
||||
<Separator className="my-4 md:my-4" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
|
||||
import { type Message } from 'ai'
|
||||
|
||||
import { Button } from '@/chat-components/ui/button'
|
||||
import { IconCheck, IconCopy } from '@/chat-components/ui/icons'
|
||||
import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface ChatMessageActionsProps extends React.ComponentProps<'div'> {
|
||||
message: Message
|
||||
}
|
||||
|
||||
export function ChatMessageActions({
|
||||
message,
|
||||
className,
|
||||
...props
|
||||
}: ChatMessageActionsProps) {
|
||||
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
|
||||
|
||||
const onCopy = () => {
|
||||
if (isCopied) return
|
||||
copyToClipboard(message.content)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-end transition-opacity group-hover:opacity-100 md:absolute md:-right-10 md:-top-2 md:opacity-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Button variant="ghost" size="icon" onClick={onCopy}>
|
||||
{isCopied ? <IconCheck /> : <IconCopy />}
|
||||
<span className="sr-only">Copy message</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Message } from 'ai'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { CodeBlock } from '@/chat-components/ui/codeblock'
|
||||
import { MemoizedReactMarkdown } from '@/chat-components/markdown'
|
||||
import { IconOpenAI, IconUser } from '@/chat-components/ui/icons'
|
||||
import { ChatMessageActions } from '@/chat-components/chat-message-actions'
|
||||
|
||||
export interface ChatMessageProps {
|
||||
message: Message
|
||||
}
|
||||
|
||||
export function ChatMessage({ message, ...props }: ChatMessageProps) {
|
||||
return (
|
||||
<div className={cn('group relative mb-4 flex items-start')} {...props}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow',
|
||||
message.role === 'user'
|
||||
? 'bg-background'
|
||||
: 'bg-primary text-primary-foreground'
|
||||
)}
|
||||
>
|
||||
{message.role === 'user' ? <IconUser /> : <IconOpenAI />}
|
||||
</div>
|
||||
<div className="ml-4 flex-1 space-y-2 overflow-hidden px-1">
|
||||
<MemoizedReactMarkdown
|
||||
className="prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 text-sm"
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
components={{
|
||||
p({ children }) {
|
||||
return <p className="mb-2 last:mb-0">{children}</p>
|
||||
},
|
||||
code({ children, className, inline, ...props }) {
|
||||
if (children.length) {
|
||||
if (children[0] == '▍') {
|
||||
return (
|
||||
<span className="mt-1 animate-pulse cursor-default">▍</span>
|
||||
)
|
||||
}
|
||||
|
||||
children[0] = (children[0] as string).replace('`▍`', '▍')
|
||||
}
|
||||
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
|
||||
if (inline) {
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<CodeBlock
|
||||
key={Math.random()}
|
||||
language={(match && match[1]) || ''}
|
||||
value={String(children).replace(/\n$/, '')}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{message.content}
|
||||
</MemoizedReactMarkdown>
|
||||
<ChatMessageActions message={message} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { type UseChatHelpers } from 'ai/react'
|
||||
|
||||
import { Button } from '@/chat-components/ui/button'
|
||||
import { PromptForm } from '@/chat-components/prompt-form'
|
||||
import { IconRefresh, IconStop } from '@/chat-components/ui/icons'
|
||||
|
||||
export interface ChatPanelProps
|
||||
extends Pick<
|
||||
UseChatHelpers,
|
||||
| 'append'
|
||||
| 'isLoading'
|
||||
| 'reload'
|
||||
| 'messages'
|
||||
| 'stop'
|
||||
| 'input'
|
||||
| 'setInput'
|
||||
> {
|
||||
id?: string
|
||||
}
|
||||
|
||||
export function ChatPanel({
|
||||
id,
|
||||
isLoading,
|
||||
stop,
|
||||
append,
|
||||
reload,
|
||||
input,
|
||||
setInput,
|
||||
messages
|
||||
}: ChatPanelProps) {
|
||||
return (
|
||||
<div
|
||||
className="inset-x-0 bottom-0 bg-gradient-to-b from-muted/10 from-10% to-muted/30 to-50% mt-4 mb-8"
|
||||
style={{ width: '100%', overflow: 'hidden', boxSizing: 'border-box' }}
|
||||
>
|
||||
<div className="mx-auto sm:max-w-2xl sm:px-4">
|
||||
<div className="flex h-10 items-center justify-center mb-4">
|
||||
{isLoading ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => stop()}
|
||||
className="bg-background"
|
||||
>
|
||||
<IconStop className="mr-2" />
|
||||
Stop generating
|
||||
</Button>
|
||||
) : (
|
||||
messages?.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => reload()}
|
||||
className="bg-background"
|
||||
>
|
||||
<IconRefresh className="mr-2" />
|
||||
Regenerate response
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-4 border bg-background px-4 py-2 shadow-lg sm:rounded-xl sm:border md:py-4">
|
||||
<PromptForm
|
||||
onSubmit={async value => {
|
||||
await append({
|
||||
id,
|
||||
content: value,
|
||||
role: 'user'
|
||||
})
|
||||
}}
|
||||
input={input}
|
||||
setInput={setInput}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { useInView } from 'react-intersection-observer'
|
||||
|
||||
import { useAtBottom } from '@/lib/hooks/use-at-bottom'
|
||||
|
||||
interface ChatScrollAnchorProps {
|
||||
trackVisibility?: boolean
|
||||
}
|
||||
|
||||
export function ChatScrollAnchor({ trackVisibility }: ChatScrollAnchorProps) {
|
||||
const isAtBottom = useAtBottom()
|
||||
const { ref, entry, inView } = useInView({
|
||||
trackVisibility,
|
||||
delay: 100,
|
||||
rootMargin: '0px 0px -150px 0px'
|
||||
})
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isAtBottom && trackVisibility && !inView) {
|
||||
entry?.target.scrollIntoView({
|
||||
block: 'start'
|
||||
})
|
||||
}
|
||||
}, [inView, entry, isAtBottom, trackVisibility])
|
||||
|
||||
return <div ref={ref} className="h-px w-full" />
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
'use client'
|
||||
|
||||
import { useChat, type Message } from 'ai/react'
|
||||
|
||||
import { ChatList } from '@/chat-components/chat-list'
|
||||
import { ChatPanel } from '@/chat-components/chat-panel'
|
||||
import {
|
||||
DefaultEmptyScreen,
|
||||
EmptyScreenProps
|
||||
} from '@/chat-components/default-empty-screen'
|
||||
import { ChatScrollAnchor } from '@/chat-components/chat-scroll-anchor'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { CopilotContext } from '@/app/copilot-context'
|
||||
import { useContext, useEffect, useMemo } from 'react'
|
||||
|
||||
export interface ChatProps extends React.ComponentProps<'div'> {
|
||||
initialMessages?: Message[]
|
||||
id?: string
|
||||
makeSystemMessage: (contextString: string) => string
|
||||
}
|
||||
|
||||
interface ChatComponentInjectionsProps {
|
||||
EmptyScreen?: React.ComponentType<EmptyScreenProps>
|
||||
}
|
||||
|
||||
export function Chat({
|
||||
id,
|
||||
initialMessages,
|
||||
makeSystemMessage = defaultSystemMessage,
|
||||
EmptyScreen = DefaultEmptyScreen
|
||||
}: ChatProps & ChatComponentInjectionsProps) {
|
||||
const {
|
||||
getContextString,
|
||||
getChatCompletionFunctions,
|
||||
getFunctionCallHandler
|
||||
} = useContext(CopilotContext)
|
||||
|
||||
const contextString = getContextString()
|
||||
|
||||
const systemMessage: Message = useMemo(() => {
|
||||
return {
|
||||
id: 'system',
|
||||
content: makeSystemMessage(contextString),
|
||||
role: 'system'
|
||||
}
|
||||
}, [contextString, makeSystemMessage])
|
||||
|
||||
const initialMessagesWithContext = [systemMessage].concat(
|
||||
initialMessages || []
|
||||
)
|
||||
|
||||
const functions = useMemo(() => {
|
||||
return getChatCompletionFunctions()
|
||||
}, [getChatCompletionFunctions])
|
||||
|
||||
const { messages, append, reload, stop, isLoading, input, setInput } =
|
||||
useChat({
|
||||
initialMessages: initialMessagesWithContext,
|
||||
experimental_onFunctionCall: getFunctionCallHandler(),
|
||||
id,
|
||||
body: {
|
||||
id,
|
||||
previewToken,
|
||||
functions
|
||||
},
|
||||
onResponse(response) {
|
||||
if (response.status === 401) {
|
||||
toast.error(response.statusText)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const visibleMessages = messages.filter(
|
||||
message => message.role === 'user' || message.role === 'assistant'
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
boxSizing: 'border-box', // ensure padding is included in total height
|
||||
alignItems: 'flex-start' // prevent stretching of items
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="pt-5 px-5"
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
width: '100%',
|
||||
flexGrow: 1
|
||||
}}
|
||||
>
|
||||
{visibleMessages.length ? (
|
||||
<div className="pl-0 pr-6">
|
||||
<ChatList messages={visibleMessages} />
|
||||
<ChatScrollAnchor trackVisibility={isLoading} />
|
||||
</div>
|
||||
) : (
|
||||
<EmptyScreen setInput={setInput} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ flexShrink: 0, width: '100%' }}>
|
||||
<ChatPanel
|
||||
id={id}
|
||||
isLoading={isLoading}
|
||||
stop={stop}
|
||||
append={append}
|
||||
reload={reload}
|
||||
messages={visibleMessages}
|
||||
input={input}
|
||||
setInput={setInput}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const previewToken = 'TODO123'
|
||||
|
||||
export function defaultSystemMessage(contextString: string): string {
|
||||
return `
|
||||
Please act as a efficient, competent, and conscientious professional assistant.
|
||||
You help the user achieve their goals, and you do so in a way that is as efficient as possible, without unnecessary fluff, but also without sacrificing professionalism.
|
||||
Always be polite and respectful, and prefer brevity over verbosity.
|
||||
|
||||
The user has provided you with the following context:
|
||||
\`\`\`
|
||||
${contextString}
|
||||
\`\`\`
|
||||
|
||||
They have also provided you with functions you can call to initiate actions on their behalf, or functions you can call to receive more information.
|
||||
|
||||
Please assist them as best you can.
|
||||
If you are not sure how to proceed to best fulfill their requests, please ask them for more information.
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { ServerActionResult } from '@/lib/types'
|
||||
import { Button } from '@/chat-components/ui/button'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger
|
||||
} from '@/chat-components/ui/alert-dialog'
|
||||
import { IconSpinner } from '@/chat-components/ui/icons'
|
||||
|
||||
interface ClearHistoryProps {
|
||||
clearChats: () => ServerActionResult<void>
|
||||
}
|
||||
|
||||
export function ClearHistory({ clearChats }: ClearHistoryProps) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [isPending, startTransition] = React.useTransition()
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="ghost" disabled={isPending}>
|
||||
{isPending && <IconSpinner className="mr-2" />}
|
||||
Clear history
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will permanently delete your chat history and remove your data
|
||||
from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isPending}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
disabled={isPending}
|
||||
onClick={(event: any) => {
|
||||
event.preventDefault()
|
||||
startTransition(async () => {
|
||||
const result = await clearChats()
|
||||
|
||||
if (result && 'error' in result) {
|
||||
toast.error(result.error)
|
||||
return
|
||||
}
|
||||
|
||||
setOpen(false)
|
||||
router.push('/')
|
||||
})
|
||||
}}
|
||||
>
|
||||
{isPending && <IconSpinner className="mr-2 animate-spin" />}
|
||||
Delete
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { UseChatHelpers } from 'ai/react'
|
||||
|
||||
import { Button } from '@/chat-components/ui/button'
|
||||
import { ExternalLink } from '@/chat-components/external-link'
|
||||
import { IconArrowRight } from '@/chat-components/ui/icons'
|
||||
|
||||
const exampleMessages = [
|
||||
{
|
||||
heading: 'Explain technical concepts',
|
||||
message: `What is a "serverless function"?`
|
||||
},
|
||||
{
|
||||
heading: 'Summarize an article',
|
||||
message: 'Summarize the following article for a 2nd grader: \n'
|
||||
},
|
||||
{
|
||||
heading: 'Draft an email',
|
||||
message: `Draft an email to my boss about the following: \n`
|
||||
}
|
||||
]
|
||||
|
||||
export interface EmptyScreenProps extends Pick<UseChatHelpers, 'setInput'> {}
|
||||
|
||||
export function DefaultEmptyScreen({ setInput }: EmptyScreenProps) {
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl px-4">
|
||||
<div className="rounded-lg border bg-background p-8">
|
||||
<h1 className="mb-2 text-lg font-semibold">Welcome to Copilot! 👋</h1>
|
||||
<p className="mb-2 leading-normal text-muted-foreground">
|
||||
This is a Copilot built with{' '}
|
||||
<ExternalLink href="https://recursively.ai">
|
||||
recursively.ai's
|
||||
</ExternalLink>{' '}
|
||||
<ExternalLink href="https://github.com/RecursivelyAI/CopilotKit">
|
||||
CopilotKit
|
||||
</ExternalLink>{' '}
|
||||
.
|
||||
</p>
|
||||
<p className="leading-normal text-muted-foreground">
|
||||
You can start a conversation here or try the following examples:
|
||||
</p>
|
||||
<div className="mt-4 flex flex-col items-start space-y-2">
|
||||
{exampleMessages.map((message, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant="link"
|
||||
className="h-auto p-0 text-base"
|
||||
onClick={() => setInput(message.message)}
|
||||
>
|
||||
<IconArrowRight className="mr-2 text-muted-foreground" />
|
||||
{message.heading}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export function ExternalLink({
|
||||
href,
|
||||
children
|
||||
}: {
|
||||
href: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
className="inline-flex flex-1 justify-center gap-1 leading-4 hover:underline"
|
||||
>
|
||||
<span>{children}</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
height="7"
|
||||
viewBox="0 0 6 6"
|
||||
width="7"
|
||||
className="opacity-70"
|
||||
>
|
||||
<path
|
||||
d="M1.25215 5.54731L0.622742 4.9179L3.78169 1.75597H1.3834L1.38936 0.890915H5.27615V4.78069H4.40513L4.41109 2.38538L1.25215 5.54731Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { FC, memo } from 'react'
|
||||
import ReactMarkdown, { Options } from 'react-markdown'
|
||||
|
||||
export const MemoizedReactMarkdown: FC<Options> = memo(
|
||||
ReactMarkdown,
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.children === nextProps.children &&
|
||||
prevProps.className === nextProps.className
|
||||
)
|
||||
@@ -0,0 +1,95 @@
|
||||
import * as React from 'react'
|
||||
import Link from 'next/link'
|
||||
import TextareaAutosize from 'react-textarea-autosize'
|
||||
import { UseChatHelpers } from 'ai/react'
|
||||
|
||||
import { useEnterSubmit } from '@/lib/hooks/use-enter-submit'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button, buttonVariants } from '@/chat-components/ui/button'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger
|
||||
} from '@/chat-components/ui/tooltip'
|
||||
import { IconArrowElbow, IconPlus } from '@/chat-components/ui/icons'
|
||||
|
||||
export interface PromptProps
|
||||
extends Pick<UseChatHelpers, 'input' | 'setInput'> {
|
||||
onSubmit: (value: string) => Promise<void>
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export function PromptForm({
|
||||
onSubmit,
|
||||
input,
|
||||
setInput,
|
||||
isLoading
|
||||
}: PromptProps) {
|
||||
const { formRef, onKeyDown } = useEnterSubmit()
|
||||
const inputRef = React.useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={async e => {
|
||||
e.preventDefault()
|
||||
if (!input?.trim()) {
|
||||
return
|
||||
}
|
||||
setInput('')
|
||||
await onSubmit(input)
|
||||
}}
|
||||
ref={formRef}
|
||||
>
|
||||
<div className="relative flex max-h-60 w-full grow flex-col overflow-hidden bg-background px-8 sm:rounded-md sm:border sm:px-12">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href="/"
|
||||
className={cn(
|
||||
buttonVariants({ size: 'sm', variant: 'outline' }),
|
||||
'absolute left-0 top-4 h-8 w-8 rounded-full bg-background p-0 sm:left-4'
|
||||
)}
|
||||
>
|
||||
<IconPlus />
|
||||
<span className="sr-only">New Chat</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>New Chat</TooltipContent>
|
||||
</Tooltip>
|
||||
<TextareaAutosize
|
||||
ref={inputRef}
|
||||
tabIndex={0}
|
||||
onKeyDown={onKeyDown}
|
||||
rows={1}
|
||||
value={input}
|
||||
onChange={e => setInput(e.target.value)}
|
||||
placeholder="Send a message."
|
||||
spellCheck={false}
|
||||
className="min-h-[60px] w-full resize-none bg-transparent px-4 py-[1.3rem] focus-within:outline-none sm:text-sm"
|
||||
/>
|
||||
<div className="absolute right-0 top-4 sm:right-4">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="submit"
|
||||
size="icon"
|
||||
disabled={isLoading || input === ''}
|
||||
className=" bg-slate-300"
|
||||
>
|
||||
<IconArrowElbow />
|
||||
<span className="sr-only">Send message</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Send message</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { ThemeProviderProps } from 'next-themes/dist/types'
|
||||
|
||||
import { TooltipProvider } from '@/chat-components/ui/tooltip'
|
||||
import { CopilotProvider } from '@/app/copilot-context'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
|
||||
export function Providers({ children, ...props }: ThemeProviderProps) {
|
||||
return (
|
||||
<CopilotProvider>
|
||||
<ThemeProvider {...props}>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</ThemeProvider>
|
||||
</CopilotProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
import { Button } from '@/chat-components/ui/button'
|
||||
import { IconMoon, IconSun } from '@/chat-components/ui/icons'
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { setTheme, theme } = useTheme()
|
||||
const [_, startTransition] = React.useTransition()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => {
|
||||
startTransition(() => {
|
||||
setTheme(theme === 'light' ? 'dark' : 'light')
|
||||
})
|
||||
}}
|
||||
>
|
||||
{!theme ? null : theme === 'dark' ? (
|
||||
<IconMoon className="transition-all" />
|
||||
) : (
|
||||
<IconSun className="transition-all" />
|
||||
)}
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
'use client'
|
||||
|
||||
export { Toaster } from 'react-hot-toast'
|
||||
@@ -0,0 +1,150 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/chat-components/ui/button'
|
||||
|
||||
const AlertDialog = AlertDialogPrimitive.Root
|
||||
|
||||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||
|
||||
const AlertDialogPortal = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: AlertDialogPrimitive.AlertDialogPortalProps) => (
|
||||
<AlertDialogPrimitive.Portal className={cn(className)} {...props}>
|
||||
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
|
||||
{children}
|
||||
</div>
|
||||
</AlertDialogPrimitive.Portal>
|
||||
)
|
||||
AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName
|
||||
|
||||
const AlertDialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-opacity animate-in fade-in',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||
|
||||
const AlertDialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed z-50 grid w-full max-w-lg scale-100 gap-4 border bg-background p-6 opacity-100 shadow-lg animate-in fade-in-90 slide-in-from-bottom-10 sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0 md:w-full',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
))
|
||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||
|
||||
const AlertDialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col space-y-2 text-center sm:text-left',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
AlertDialogHeader.displayName = 'AlertDialogHeader'
|
||||
|
||||
const AlertDialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
AlertDialogFooter.displayName = 'AlertDialogFooter'
|
||||
|
||||
const AlertDialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn('text-lg font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||
|
||||
const AlertDialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn('text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogDescription.displayName =
|
||||
AlertDialogPrimitive.Description.displayName
|
||||
|
||||
const AlertDialogAction = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Action
|
||||
ref={ref}
|
||||
className={cn(buttonVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||
|
||||
const AlertDialogCancel = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Cancel
|
||||
ref={ref}
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'mt-2 sm:mt-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as React from 'react'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const badgeVariants = cva(
|
||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
||||
secondary:
|
||||
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
||||
outline: 'text-foreground'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
@@ -0,0 +1,57 @@
|
||||
import * as React from 'react'
|
||||
import { Slot } from '@radix-ui/react-slot'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium shadow ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'bg-primary text-primary-foreground shadow-md hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline:
|
||||
'border border-input hover:bg-accent hover:text-accent-foreground',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'shadow-none hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 shadow-none hover:underline'
|
||||
},
|
||||
size: {
|
||||
default: 'h-8 px-4 py-2',
|
||||
sm: 'h-8 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-8 w-8 p-0'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : 'button'
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = 'Button'
|
||||
|
||||
export { Button, buttonVariants }
|
||||
@@ -0,0 +1,142 @@
|
||||
'use client'
|
||||
|
||||
import { FC, memo } from 'react'
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
|
||||
import { coldarkDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
|
||||
|
||||
import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard'
|
||||
import { IconCheck, IconCopy, IconDownload } from '@/chat-components/ui/icons'
|
||||
import { Button } from '@/chat-components/ui/button'
|
||||
|
||||
interface Props {
|
||||
language: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface languageMap {
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
|
||||
export const programmingLanguages: languageMap = {
|
||||
javascript: '.js',
|
||||
python: '.py',
|
||||
java: '.java',
|
||||
c: '.c',
|
||||
cpp: '.cpp',
|
||||
'c++': '.cpp',
|
||||
'c#': '.cs',
|
||||
ruby: '.rb',
|
||||
php: '.php',
|
||||
swift: '.swift',
|
||||
'objective-c': '.m',
|
||||
kotlin: '.kt',
|
||||
typescript: '.ts',
|
||||
go: '.go',
|
||||
perl: '.pl',
|
||||
rust: '.rs',
|
||||
scala: '.scala',
|
||||
haskell: '.hs',
|
||||
lua: '.lua',
|
||||
shell: '.sh',
|
||||
sql: '.sql',
|
||||
html: '.html',
|
||||
css: '.css'
|
||||
// add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
|
||||
}
|
||||
|
||||
export const generateRandomString = (length: number, lowercase = false) => {
|
||||
const chars = 'ABCDEFGHJKLMNPQRSTUVWXY3456789' // excluding similar looking characters like Z, 2, I, 1, O, 0
|
||||
let result = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
return lowercase ? result.toLowerCase() : result
|
||||
}
|
||||
|
||||
const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
||||
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
|
||||
|
||||
const downloadAsFile = () => {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
const fileExtension = programmingLanguages[language] || '.file'
|
||||
const suggestedFileName = `file-${generateRandomString(
|
||||
3,
|
||||
true
|
||||
)}${fileExtension}`
|
||||
const fileName = window.prompt('Enter file name' || '', suggestedFileName)
|
||||
|
||||
if (!fileName) {
|
||||
// User pressed cancel on prompt.
|
||||
return
|
||||
}
|
||||
|
||||
const blob = new Blob([value], { type: 'text/plain' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.download = fileName
|
||||
link.href = url
|
||||
link.style.display = 'none'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
const onCopy = () => {
|
||||
if (isCopied) return
|
||||
copyToClipboard(value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="codeblock relative w-full bg-zinc-950 font-sans">
|
||||
<div className="flex w-full items-center justify-between bg-zinc-800 px-6 py-2 pr-4 text-zinc-100">
|
||||
<span className="text-xs lowercase">{language}</span>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0"
|
||||
onClick={downloadAsFile}
|
||||
size="icon"
|
||||
>
|
||||
<IconDownload />
|
||||
<span className="sr-only">Download</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-xs hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0"
|
||||
onClick={onCopy}
|
||||
>
|
||||
{isCopied ? <IconCheck /> : <IconCopy />}
|
||||
<span className="sr-only">Copy code</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
language={language}
|
||||
style={coldarkDark}
|
||||
PreTag="div"
|
||||
showLineNumbers
|
||||
customStyle={{
|
||||
margin: 0,
|
||||
width: '100%',
|
||||
background: 'transparent',
|
||||
padding: '1.5rem 1rem'
|
||||
}}
|
||||
codeTagProps={{
|
||||
style: {
|
||||
fontSize: '0.9rem',
|
||||
fontFamily: 'var(--font-mono)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
CodeBlock.displayName = 'CodeBlock'
|
||||
|
||||
export { CodeBlock }
|
||||
@@ -0,0 +1,128 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { IconClose } from '@/chat-components/ui/icons'
|
||||
|
||||
const Dialog = DialogPrimitive.Root
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger
|
||||
|
||||
const DialogPortal = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: DialogPrimitive.DialogPortalProps) => (
|
||||
<DialogPrimitive.Portal className={cn(className)} {...props}>
|
||||
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
|
||||
{children}
|
||||
</div>
|
||||
</DialogPrimitive.Portal>
|
||||
)
|
||||
DialogPortal.displayName = DialogPrimitive.Portal.displayName
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background p-6 shadow-sm animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<IconClose />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col space-y-1.5 text-center sm:text-left',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogHeader.displayName = 'DialogHeader'
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogFooter.displayName = 'DialogFooter'
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-lg font-semibold leading-none tracking-tight',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn('text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
inset && 'pl-8',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'px-2 py-1.5 text-sm font-semibold',
|
||||
inset && 'pl-8',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuRadioGroup
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function IconNextChat({
|
||||
className,
|
||||
inverted,
|
||||
...props
|
||||
}: React.ComponentProps<'svg'> & { inverted?: boolean }) {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 17 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={`gradient-${id}-1`}
|
||||
x1="10.6889"
|
||||
y1="10.3556"
|
||||
x2="13.8445"
|
||||
y2="14.2667"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor={inverted ? 'white' : 'black'} />
|
||||
<stop
|
||||
offset={1}
|
||||
stopColor={inverted ? 'white' : 'black'}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id={`gradient-${id}-2`}
|
||||
x1="11.7555"
|
||||
y1="4.8"
|
||||
x2="11.7376"
|
||||
y2="9.50002"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor={inverted ? 'white' : 'black'} />
|
||||
<stop
|
||||
offset={1}
|
||||
stopColor={inverted ? 'white' : 'black'}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M1 16L2.58314 11.2506C1.83084 9.74642 1.63835 8.02363 2.04013 6.39052C2.4419 4.75741 3.41171 3.32057 4.776 2.33712C6.1403 1.35367 7.81003 0.887808 9.4864 1.02289C11.1628 1.15798 12.7364 1.8852 13.9256 3.07442C15.1148 4.26363 15.842 5.83723 15.9771 7.5136C16.1122 9.18997 15.6463 10.8597 14.6629 12.224C13.6794 13.5883 12.2426 14.5581 10.6095 14.9599C8.97637 15.3616 7.25358 15.1692 5.74942 14.4169L1 16Z"
|
||||
fill={inverted ? 'black' : 'white'}
|
||||
stroke={inverted ? 'black' : 'white'}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<mask
|
||||
id="mask0_91_2047"
|
||||
style={{ maskType: 'alpha' }}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={1}
|
||||
y={0}
|
||||
width={16}
|
||||
height={16}
|
||||
>
|
||||
<circle cx={9} cy={8} r={8} fill={inverted ? 'black' : 'white'} />
|
||||
</mask>
|
||||
<g mask="url(#mask0_91_2047)">
|
||||
<circle cx={9} cy={8} r={8} fill={inverted ? 'black' : 'white'} />
|
||||
<path
|
||||
d="M14.2896 14.0018L7.146 4.8H5.80005V11.1973H6.87681V6.16743L13.4444 14.6529C13.7407 14.4545 14.0231 14.2369 14.2896 14.0018Z"
|
||||
fill={`url(#gradient-${id}-1)`}
|
||||
/>
|
||||
<rect
|
||||
x="11.2222"
|
||||
y="4.8"
|
||||
width="1.06667"
|
||||
height="6.4"
|
||||
fill={`url(#gradient-${id}-2)`}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconOpenAI({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<title>OpenAI icon</title>
|
||||
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconVercel({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
aria-label="Vercel logomark"
|
||||
role="img"
|
||||
viewBox="0 0 74 64"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M37.5896 0.25L74.5396 64.25H0.639648L37.5896 0.25Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconGitHub({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<title>GitHub</title>
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconSeparator({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
shapeRendering="geometricPrecision"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M16.88 3.549L7.12 20.451"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconArrowDown({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="m205.66 149.66-72 72a8 8 0 0 1-11.32 0l-72-72a8 8 0 0 1 11.32-11.32L120 196.69V40a8 8 0 0 1 16 0v156.69l58.34-58.35a8 8 0 0 1 11.32 11.32Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconArrowRight({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="m221.66 133.66-72 72a8 8 0 0 1-11.32-11.32L196.69 136H40a8 8 0 0 1 0-16h156.69l-58.35-58.34a8 8 0 0 1 11.32-11.32l72 72a8 8 0 0 1 0 11.32Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconUser({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M230.92 212c-15.23-26.33-38.7-45.21-66.09-54.16a72 72 0 1 0-73.66 0c-27.39 8.94-50.86 27.82-66.09 54.16a8 8 0 1 0 13.85 8c18.84-32.56 52.14-52 89.07-52s70.23 19.44 89.07 52a8 8 0 1 0 13.85-8ZM72 96a56 56 0 1 1 56 56 56.06 56.06 0 0 1-56-56Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconPlus({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M224 128a8 8 0 0 1-8 8h-80v80a8 8 0 0 1-16 0v-80H40a8 8 0 0 1 0-16h80V40a8 8 0 0 1 16 0v80h80a8 8 0 0 1 8 8Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconArrowElbow({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M200 32v144a8 8 0 0 1-8 8H67.31l34.35 34.34a8 8 0 0 1-11.32 11.32l-48-48a8 8 0 0 1 0-11.32l48-48a8 8 0 0 1 11.32 11.32L67.31 168H184V32a8 8 0 0 1 16 0Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconSpinner({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4 animate-spin', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M232 128a104 104 0 0 1-208 0c0-41 23.81-78.36 60.66-95.27a8 8 0 0 1 6.68 14.54C60.15 61.59 40 93.27 40 128a88 88 0 0 0 176 0c0-34.73-20.15-66.41-51.34-80.73a8 8 0 0 1 6.68-14.54C208.19 49.64 232 87 232 128Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconMessage({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M216 48H40a16 16 0 0 0-16 16v160a15.84 15.84 0 0 0 9.25 14.5A16.05 16.05 0 0 0 40 240a15.89 15.89 0 0 0 10.25-3.78.69.69 0 0 0 .13-.11L82.5 208H216a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16ZM40 224Zm176-32H82.5a16 16 0 0 0-10.3 3.75l-.12.11L40 224V64h176Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconTrash({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16ZM96 40a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8v8H96Zm96 168H64V64h128Zm-80-104v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconRefresh({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M197.67 186.37a8 8 0 0 1 0 11.29C196.58 198.73 170.82 224 128 224c-37.39 0-64.53-22.4-80-39.85V208a8 8 0 0 1-16 0v-48a8 8 0 0 1 8-8h48a8 8 0 0 1 0 16H55.44C67.76 183.35 93 208 128 208c36 0 58.14-21.46 58.36-21.68a8 8 0 0 1 11.31.05ZM216 40a8 8 0 0 0-8 8v23.85C192.53 54.4 165.39 32 128 32c-42.82 0-68.58 25.27-69.66 26.34a8 8 0 0 0 11.3 11.34C69.86 69.46 92 48 128 48c35 0 60.24 24.65 72.56 40H168a8 8 0 0 0 0 16h48a8 8 0 0 0 8-8V48a8 8 0 0 0-8-8Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconStop({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24Zm0 192a88 88 0 1 1 88-88 88.1 88.1 0 0 1-88 88Zm24-120h-48a8 8 0 0 0-8 8v48a8 8 0 0 0 8 8h48a8 8 0 0 0 8-8v-48a8 8 0 0 0-8-8Zm-8 48h-32v-32h32Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconSidebar({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M216 40H40a16 16 0 0 0-16 16v144a16 16 0 0 0 16 16h176a16 16 0 0 0 16-16V56a16 16 0 0 0-16-16ZM40 56h40v144H40Zm176 144H96V56h120v144Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconMoon({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M233.54 142.23a8 8 0 0 0-8-2 88.08 88.08 0 0 1-109.8-109.8 8 8 0 0 0-10-10 104.84 104.84 0 0 0-52.91 37A104 104 0 0 0 136 224a103.09 103.09 0 0 0 62.52-20.88 104.84 104.84 0 0 0 37-52.91 8 8 0 0 0-1.98-7.98Zm-44.64 48.11A88 88 0 0 1 65.66 67.11a89 89 0 0 1 31.4-26A106 106 0 0 0 96 56a104.11 104.11 0 0 0 104 104 106 106 0 0 0 14.92-1.06 89 89 0 0 1-26.02 31.4Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconSun({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M120 40V16a8 8 0 0 1 16 0v24a8 8 0 0 1-16 0Zm72 88a64 64 0 1 1-64-64 64.07 64.07 0 0 1 64 64Zm-16 0a48 48 0 1 0-48 48 48.05 48.05 0 0 0 48-48ZM58.34 69.66a8 8 0 0 0 11.32-11.32l-16-16a8 8 0 0 0-11.32 11.32Zm0 116.68-16 16a8 8 0 0 0 11.32 11.32l16-16a8 8 0 0 0-11.32-11.32ZM192 72a8 8 0 0 0 5.66-2.34l16-16a8 8 0 0 0-11.32-11.32l-16 16A8 8 0 0 0 192 72Zm5.66 114.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32-11.32ZM48 128a8 8 0 0 0-8-8H16a8 8 0 0 0 0 16h24a8 8 0 0 0 8-8Zm80 80a8 8 0 0 0-8 8v24a8 8 0 0 0 16 0v-24a8 8 0 0 0-8-8Zm112-88h-24a8 8 0 0 0 0 16h24a8 8 0 0 0 0-16Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconCopy({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M216 32H88a8 8 0 0 0-8 8v40H40a8 8 0 0 0-8 8v128a8 8 0 0 0 8 8h128a8 8 0 0 0 8-8v-40h40a8 8 0 0 0 8-8V40a8 8 0 0 0-8-8Zm-56 176H48V96h112Zm48-48h-32V88a8 8 0 0 0-8-8H96V48h112Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconCheck({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="m229.66 77.66-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69 218.34 66.34a8 8 0 0 1 11.32 11.32Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconDownload({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M224 152v56a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16v-56a8 8 0 0 1 16 0v56h160v-56a8 8 0 0 1 16 0Zm-101.66 5.66a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0-11.32-11.32L136 132.69V40a8 8 0 0 0-16 0v92.69l-26.34-26.35a8 8 0 0 0-11.32 11.32Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconClose({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128 50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconEdit({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconShare({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
viewBox="0 0 256 256"
|
||||
{...props}
|
||||
>
|
||||
<path d="m237.66 106.35-80-80A8 8 0 0 0 144 32v40.35c-25.94 2.22-54.59 14.92-78.16 34.91-28.38 24.08-46.05 55.11-49.76 87.37a12 12 0 0 0 20.68 9.58c11-11.71 50.14-48.74 107.24-52V192a8 8 0 0 0 13.66 5.65l80-80a8 8 0 0 0 0-11.3ZM160 172.69V144a8 8 0 0 0-8-8c-28.08 0-55.43 7.33-81.29 21.8a196.17 196.17 0 0 0-36.57 26.52c5.8-23.84 20.42-46.51 42.05-64.86C99.41 99.77 127.75 88 152 88a8 8 0 0 0 8-8V51.32L220.69 112Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconUsers({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
viewBox="0 0 256 256"
|
||||
{...props}
|
||||
>
|
||||
<path d="M117.25 157.92a60 60 0 1 0-66.5 0 95.83 95.83 0 0 0-47.22 37.71 8 8 0 1 0 13.4 8.74 80 80 0 0 1 134.14 0 8 8 0 0 0 13.4-8.74 95.83 95.83 0 0 0-47.22-37.71ZM40 108a44 44 0 1 1 44 44 44.05 44.05 0 0 1-44-44Zm210.14 98.7a8 8 0 0 1-11.07-2.33A79.83 79.83 0 0 0 172 168a8 8 0 0 1 0-16 44 44 0 1 0-16.34-84.87 8 8 0 1 1-5.94-14.85 60 60 0 0 1 55.53 105.64 95.83 95.83 0 0 1 47.22 37.71 8 8 0 0 1-2.33 11.07Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconExternalLink({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
viewBox="0 0 256 256"
|
||||
{...props}
|
||||
>
|
||||
<path d="M224 104a8 8 0 0 1-16 0V59.32l-66.33 66.34a8 8 0 0 1-11.32-11.32L196.68 48H152a8 8 0 0 1 0-16h64a8 8 0 0 1 8 8Zm-40 24a8 8 0 0 0-8 8v72H48V80h72a8 8 0 0 0 0-16H48a16 16 0 0 0-16 16v128a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-72a8 8 0 0 0-8-8Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function IconChevronUpDown({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
className={cn('h-4 w-4', className)}
|
||||
viewBox="0 0 256 256"
|
||||
{...props}
|
||||
>
|
||||
<path d="M181.66 170.34a8 8 0 0 1 0 11.32l-48 48a8 8 0 0 1-11.32 0l-48-48a8 8 0 0 1 11.32-11.32L128 212.69l42.34-42.35a8 8 0 0 1 11.32 0Zm-96-84.68L128 43.31l42.34 42.35a8 8 0 0 0 11.32-11.32l-48-48a8 8 0 0 0-11.32 0l-48 48a8 8 0 0 0 11.32 11.32Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
IconEdit,
|
||||
IconNextChat,
|
||||
IconOpenAI,
|
||||
IconVercel,
|
||||
IconGitHub,
|
||||
IconSeparator,
|
||||
IconArrowDown,
|
||||
IconArrowRight,
|
||||
IconUser,
|
||||
IconPlus,
|
||||
IconArrowElbow,
|
||||
IconSpinner,
|
||||
IconMessage,
|
||||
IconTrash,
|
||||
IconRefresh,
|
||||
IconStop,
|
||||
IconSidebar,
|
||||
IconMoon,
|
||||
IconSun,
|
||||
IconCopy,
|
||||
IconCheck,
|
||||
IconDownload,
|
||||
IconClose,
|
||||
IconShare,
|
||||
IconUsers,
|
||||
IconExternalLink,
|
||||
IconChevronUpDown
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = 'Input'
|
||||
|
||||
export { Input }
|
||||
@@ -0,0 +1,26 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as LabelPrimitive from '@radix-ui/react-label'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const labelVariants = cva(
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
@@ -0,0 +1,123 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as SelectPrimitive from '@radix-ui/react-select'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
IconArrowDown,
|
||||
IconCheck,
|
||||
IconChevronUpDown
|
||||
} from '@/chat-components/ui/icons'
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group
|
||||
|
||||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<IconChevronUpDown className="opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = 'popper', ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md animate-in fade-in-80',
|
||||
position === 'popper' && 'translate-y-1',
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
'p-1',
|
||||
position === 'popper' &&
|
||||
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<IconCheck className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(
|
||||
(
|
||||
{ className, orientation = 'horizontal', decorative = true, ...props },
|
||||
ref
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
'shrink-0 bg-border',
|
||||
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||
|
||||
export { Separator }
|
||||
@@ -0,0 +1,122 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as SheetPrimitive from '@radix-ui/react-dialog'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { IconClose } from '@/chat-components/ui/icons'
|
||||
|
||||
const Sheet = SheetPrimitive.Root
|
||||
|
||||
const SheetTrigger = SheetPrimitive.Trigger
|
||||
|
||||
const SheetClose = SheetPrimitive.Close
|
||||
|
||||
const SheetPortal = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: SheetPrimitive.DialogPortalProps) => (
|
||||
<SheetPrimitive.Portal
|
||||
className={cn('fixed inset-0 z-50 flex', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</SheetPrimitive.Portal>
|
||||
)
|
||||
SheetPortal.displayName = SheetPrimitive.Portal.displayName
|
||||
|
||||
const SheetOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SheetPortal>
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed z-50 h-full border-r bg-background p-6 opacity-100 shadow-lg data-[state=closed]:animate-slide-to-left data-[state=open]:animate-slide-from-left',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||
<IconClose />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
))
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||
|
||||
const SheetHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn('flex flex-col space-y-2', className)} {...props} />
|
||||
)
|
||||
SheetHeader.displayName = 'SheetHeader'
|
||||
|
||||
const SheetFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetFooter.displayName = 'SheetFooter'
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn('text-lg font-semibold text-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn('text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as SwitchPrimitives from '@radix-ui/react-switch'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Switch = React.forwardRef<
|
||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
'peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0'
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
))
|
||||
Switch.displayName = SwitchPrimitives.Root.displayName
|
||||
|
||||
export { Switch }
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface TextareaProps
|
||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
'flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Textarea.displayName = 'Textarea'
|
||||
|
||||
export { Textarea }
|
||||
@@ -0,0 +1,30 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-xs font-medium text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useMakeCopilotActionable } from '@/app/use-make-copilot-actionable'
|
||||
import React, { useState } from 'react'
|
||||
import PersonList, { peopleListA, peopleListB } from './person-list'
|
||||
|
||||
export function GoodPeopleBadPeople(): JSX.Element {
|
||||
const [searchFieldText, setSearchFieldText] = useState('')
|
||||
|
||||
useMakeCopilotActionable(
|
||||
{
|
||||
name: 'setSearchFieldText',
|
||||
description: 'Set the search field text to the given value',
|
||||
argumentAnnotations: [
|
||||
{
|
||||
name: 'searchTerm',
|
||||
type: 'string',
|
||||
description: 'The text we wish to search for',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
implementation: async (searchTerm: string) => {
|
||||
setSearchFieldText(searchTerm)
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="w-full mx-auto px-6 pt-4 pb-10">
|
||||
<h2 className="font-bold text-2xl">Search</h2>
|
||||
<input
|
||||
type="text"
|
||||
value={searchFieldText}
|
||||
onChange={e => setSearchFieldText(e.target.value)}
|
||||
className="bg-slate-100 rounded-lg py-4 px-4 my-2 w-full"
|
||||
placeholder="Search..."
|
||||
/>
|
||||
|
||||
<div className=" bg-slate-100 rounded-lg py-8 px-8 mt-10 w-full">
|
||||
<PersonList title="Current employees" people={peopleListA} />
|
||||
</div>
|
||||
|
||||
<div className=" bg-slate-100 rounded-lg py-8 px-8 mt-20 w-full">
|
||||
<PersonList title="Ex employees" people={peopleListB} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { EnvelopeIcon, PhoneIcon } from '@heroicons/react/20/solid'
|
||||
import { useMakeCopilotReadable } from '@/app/use-make-copilot-readable'
|
||||
|
||||
export interface Person {
|
||||
name: string
|
||||
title: string
|
||||
role: string
|
||||
email: string
|
||||
telephone: string
|
||||
imageUrl: string
|
||||
}
|
||||
|
||||
export interface PersonCardProps {
|
||||
person: Person
|
||||
parentCopilotId?: string
|
||||
}
|
||||
|
||||
export default function PersonCard(props: PersonCardProps) {
|
||||
const { person } = props
|
||||
|
||||
const jsonString = JSON.stringify(person, null, 2)
|
||||
useMakeCopilotReadable(`Person: ${jsonString}`, props.parentCopilotId)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex w-full items-center justify-between space-x-6 p-6">
|
||||
<div className="flex-1 truncate">
|
||||
<div className="flex items-center space-x-3">
|
||||
<h3 className="truncate text-sm font-medium text-gray-900">
|
||||
{person.name}
|
||||
</h3>
|
||||
<span className="inline-flex flex-shrink-0 items-center rounded-full bg-green-50 px-1.5 py-0.5 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
|
||||
{person.role}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 truncate text-sm text-gray-500">{person.title}</p>
|
||||
</div>
|
||||
<img
|
||||
className="h-10 w-10 flex-shrink-0 rounded-full bg-gray-300"
|
||||
src={person.imageUrl}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="-mt-px flex divide-x divide-gray-200">
|
||||
<div className="flex w-0 flex-1">
|
||||
<a
|
||||
href={`mailto:${person.email}`}
|
||||
className="relative -mr-px inline-flex w-0 flex-1 items-center justify-center gap-x-3 rounded-bl-lg border border-transparent py-4 text-sm font-semibold text-gray-900"
|
||||
>
|
||||
<EnvelopeIcon
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Email
|
||||
</a>
|
||||
</div>
|
||||
<div className="-ml-px flex w-0 flex-1">
|
||||
<a
|
||||
href={`tel:${person.telephone}`}
|
||||
className="relative inline-flex w-0 flex-1 items-center justify-center gap-x-3 rounded-br-lg border border-transparent py-4 text-sm font-semibold text-gray-900"
|
||||
>
|
||||
<PhoneIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
Call
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import PersonCard, { Person } from './person-card'
|
||||
import { useMakeCopilotReadable } from '@/app/use-make-copilot-readable'
|
||||
|
||||
export interface PersonListProps {
|
||||
title: string
|
||||
people: Person[]
|
||||
}
|
||||
|
||||
export default function PersonList(props: PersonListProps) {
|
||||
const listId = useMakeCopilotReadable(`People list: ${props.title}`)
|
||||
|
||||
const listItself = (
|
||||
<ul
|
||||
role="list"
|
||||
className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"
|
||||
>
|
||||
{props.people.map(person => (
|
||||
<li
|
||||
key={person.email}
|
||||
className="col-span-1 divide-y divide-gray-200 rounded-lg bg-white shadow"
|
||||
>
|
||||
<PersonCard person={person} parentCopilotId={listId} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className=" font-bold text-2xl pb-4"> {props.title}</h2>
|
||||
{listItself}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const peopleListA: Person[] = [
|
||||
{
|
||||
name: 'Jane Cooper',
|
||||
title: 'Regional Paradigm Technician',
|
||||
role: 'Admin',
|
||||
email: 'janecooper@example.com',
|
||||
telephone: '+1-202-555-0170',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'John Smith',
|
||||
title: 'Senior Software Engineer',
|
||||
role: 'Engineering',
|
||||
email: 'johnsmith@example.com',
|
||||
telephone: '+1-202-555-0180',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Emily Johnson',
|
||||
title: 'Marketing Manager',
|
||||
role: 'Marketing',
|
||||
email: 'emilyjohnson@example.com',
|
||||
telephone: '+1-202-555-0190',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1520813792240-56fc4a3765a7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Michael Davis',
|
||||
title: 'Financial Analyst',
|
||||
role: 'Finance',
|
||||
email: 'michaeldavis@example.com',
|
||||
telephone: '+1-202-555-0200',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1498551172505-8ee7ad69f235?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Sarah Wilson',
|
||||
title: 'Customer Support Specialist',
|
||||
role: 'Support',
|
||||
email: 'sarahwilson@example.com',
|
||||
telephone: '+1-202-555-0210',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1532417344469-368f9ae6d187?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'David Anderson',
|
||||
title: 'Project Manager',
|
||||
role: 'Management',
|
||||
email: 'davidanderson@example.com',
|
||||
telephone: '+1-202-555-0220',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1566492031773-4f4e44671857?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Emma Thompson',
|
||||
title: 'Graphic Designer',
|
||||
role: 'Design',
|
||||
email: 'emmathompson@example.com',
|
||||
telephone: '+1-202-555-0230',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1522770179533-24471fcdba45?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Daniel Roberts',
|
||||
title: 'Sales Representative',
|
||||
role: 'Sales',
|
||||
email: 'danielroberts@example.com',
|
||||
telephone: '+1-202-555-0240',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1554423551-6c69a14588b3?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Olivia Moore',
|
||||
title: 'Human Resources Coordinator',
|
||||
role: 'HR',
|
||||
email: 'oliviamoore@example.com',
|
||||
telephone: '+1-202-555-0250',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1551808422-442b54b3f7c2?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Matthew Lee',
|
||||
title: 'Data Analyst',
|
||||
role: 'Analytics',
|
||||
email: 'matthewlee@example.com',
|
||||
telephone: '+1-202-555-0260',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1520222731644-9b87400e18b8?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Sophia Turner',
|
||||
title: 'Product Manager',
|
||||
role: 'Product',
|
||||
email: 'sophiaturner@example.com',
|
||||
telephone: '+1-202-555-0270',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1558642452-9d2a7deb7f62?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
}
|
||||
]
|
||||
|
||||
export const peopleListB: Person[] = [
|
||||
{
|
||||
name: 'Robert Brown',
|
||||
title: 'UX Designer',
|
||||
role: 'Design',
|
||||
email: 'robertbrown@example.com',
|
||||
telephone: '+1-202-555-0280',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Victoria Taylor',
|
||||
title: 'Content Strategist',
|
||||
role: 'Marketing',
|
||||
email: 'victoriataylor@example.com',
|
||||
telephone: '+1-202-555-0290',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1534751516642-a1af1ef26a56?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'William Jackson',
|
||||
title: 'Database Administrator',
|
||||
role: 'Engineering',
|
||||
email: 'williamjackson@example.com',
|
||||
telephone: '+1-202-555-0300',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1566492031773-4f4e44671857?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Grace Lewis',
|
||||
title: 'Quality Assurance Analyst',
|
||||
role: 'QA',
|
||||
email: 'gracelewis@example.com',
|
||||
telephone: '+1-202-555-0310',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1532417344469-368f9ae6d187?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Samuel King',
|
||||
title: 'SEO Specialist',
|
||||
role: 'Marketing',
|
||||
email: 'samuelking@example.com',
|
||||
telephone: '+1-202-555-0320',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1498551172505-8ee7ad69f235?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Linda Wright',
|
||||
title: 'Frontend Developer',
|
||||
role: 'Engineering',
|
||||
email: 'lindawright@example.com',
|
||||
telephone: '+1-202-555-0330',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1520813792240-56fc4a3765a7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Joshua Harris',
|
||||
title: 'Social Media Manager',
|
||||
role: 'Marketing',
|
||||
email: 'joshuaharris@example.com',
|
||||
telephone: '+1-202-555-0340',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Jennifer Walker',
|
||||
title: 'Backend Developer',
|
||||
role: 'Engineering',
|
||||
email: 'jenniferwalker@example.com',
|
||||
telephone: '+1-202-555-0350',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Charles Hall',
|
||||
title: 'HR Specialist',
|
||||
role: 'HR',
|
||||
email: 'charleshall@example.com',
|
||||
telephone: '+1-202-555-0360',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Patricia Allen',
|
||||
title: 'Technical Writer',
|
||||
role: 'Support',
|
||||
email: 'patriciaallen@example.com',
|
||||
telephone: '+1-202-555-0370',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
},
|
||||
{
|
||||
name: 'Christopher Scott',
|
||||
title: 'Risk Management Officer',
|
||||
role: 'Management',
|
||||
email: 'christopherscott@example.com',
|
||||
telephone: '+1-202-555-0380',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60'
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react'
|
||||
|
||||
export function useAtBottom(offset = 0) {
|
||||
const [isAtBottom, setIsAtBottom] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsAtBottom(
|
||||
window.innerHeight + window.scrollY >=
|
||||
document.body.offsetHeight - offset
|
||||
)
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
handleScroll()
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [offset])
|
||||
|
||||
return isAtBottom
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
export interface useCopyToClipboardProps {
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export function useCopyToClipboard({
|
||||
timeout = 2000
|
||||
}: useCopyToClipboardProps) {
|
||||
const [isCopied, setIsCopied] = React.useState<Boolean>(false)
|
||||
|
||||
const copyToClipboard = (value: string) => {
|
||||
if (typeof window === 'undefined' || !navigator.clipboard?.writeText) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(value).then(() => {
|
||||
setIsCopied(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, timeout)
|
||||
})
|
||||
}
|
||||
|
||||
return { isCopied, copyToClipboard }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useRef, type RefObject } from 'react'
|
||||
|
||||
export function useEnterSubmit(): {
|
||||
formRef: RefObject<HTMLFormElement>
|
||||
onKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void
|
||||
} {
|
||||
const formRef = useRef<HTMLFormElement>(null)
|
||||
|
||||
const handleKeyDown = (
|
||||
event: React.KeyboardEvent<HTMLTextAreaElement>
|
||||
): void => {
|
||||
if (
|
||||
event.key === 'Enter' &&
|
||||
!event.shiftKey &&
|
||||
!event.nativeEvent.isComposing
|
||||
) {
|
||||
formRef.current?.requestSubmit()
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
return { formRef, onKeyDown: handleKeyDown }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { type Message } from 'ai'
|
||||
|
||||
export interface Chat extends Record<string, any> {
|
||||
id: string
|
||||
title: string
|
||||
createdAt: Date
|
||||
userId: string
|
||||
path: string
|
||||
messages: Message[]
|
||||
sharePath?: string
|
||||
}
|
||||
|
||||
export type ServerActionResult<Result> = Promise<
|
||||
| Result
|
||||
| {
|
||||
error: string
|
||||
}
|
||||
>
|
||||
@@ -0,0 +1,43 @@
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { customAlphabet } from 'nanoid'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export const nanoid = customAlphabet(
|
||||
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
|
||||
7
|
||||
) // 7-character random string
|
||||
|
||||
export async function fetcher<JSON = any>(
|
||||
input: RequestInfo,
|
||||
init?: RequestInit
|
||||
): Promise<JSON> {
|
||||
const res = await fetch(input, init)
|
||||
|
||||
if (!res.ok) {
|
||||
const json = await res.json()
|
||||
if (json.error) {
|
||||
const error = new Error(json.error) as Error & {
|
||||
status: number
|
||||
}
|
||||
error.status = res.status
|
||||
throw error
|
||||
} else {
|
||||
throw new Error('An unexpected error occurred')
|
||||
}
|
||||
}
|
||||
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export function formatDate(input: string | number | Date): string {
|
||||
const date = new Date(input)
|
||||
return date.toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
})
|
||||
}
|
||||
@@ -9,20 +9,49 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"ai": "2.1.3",
|
||||
"@headlessui/react": "^1.7.15",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-select": "^1.2.2",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
"ai": "^2.1.12",
|
||||
"class-variance-authority": "^0.6.1",
|
||||
"clsx": "^1.2.1",
|
||||
"copilotkit": "workspace:^",
|
||||
"install": "^0.13.0",
|
||||
"nanoid": "^3.3.6",
|
||||
"next": "13.4.4-canary.11",
|
||||
"openai-edge": "^0.5.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"openai-edge": "^1.1.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-intersection-observer": "^9.5.2",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-textarea-autosize": "^8.5.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"tailwind-merge": "^1.13.2",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.12",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"@preconstruct/cli": "^2.7.0",
|
||||
"@types/node": "^17.0.45",
|
||||
"@types/react": "18.2.7",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"@types/react-syntax-highlighter": "^15.5.7",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-next": "13.4.4-canary.11",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss": "^8.4.24",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "5.0.4"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ module.exports = {
|
||||
content: [
|
||||
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./chat-components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./app/**/*.{js,ts,jsx,tsx,mdx}'
|
||||
],
|
||||
theme: {
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
import { useId } from 'react'
|
||||
import type { CopilotEntrypointOptions } from '../shared/types'
|
||||
export type { CopilotEntrypointOptions }
|
||||
|
||||
export type UseCopilotEntrypointHelpers = {}
|
||||
import type { CopilotAction } from '../shared/types'
|
||||
export type { CopilotAction }
|
||||
|
||||
export function useCopilotEntrypoint({}: CopilotEntrypointOptions = {}): UseCopilotEntrypointHelpers {
|
||||
// Generate an unique id for the chat if not provided.
|
||||
const hookId = useId()
|
||||
import { useEffect, useContext } from 'react'
|
||||
import { EntryPointContext } from './context'
|
||||
|
||||
export function useCopilotEntrypoint<ActionInput extends any[]>(
|
||||
action: CopilotAction<ActionInput>
|
||||
) {
|
||||
// const { setEntryPoint, removeEntryPoint } = useContext(EntryPointContext);
|
||||
|
||||
// useEffect(() => {
|
||||
// setEntryPoint(id, func);
|
||||
|
||||
// return () => {
|
||||
// removeEntryPoint(id);
|
||||
// };
|
||||
// }, [id, func, setEntryPoint, removeEntryPoint]);
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ParameterProperty } from './openai_function_calling/types'
|
||||
|
||||
/**
|
||||
* Shared types between the API and UI packages.
|
||||
*/
|
||||
export type CopilotEntrypointOptions = {
|
||||
id: string
|
||||
export type CopilotMutation<Inputs extends any[]> = {
|
||||
function: (...args: Inputs) => any
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { CopilotEntrypointOptions } from '../shared/types'
|
||||
export type { CopilotEntrypointOptions }
|
||||
import type { CopilotAction } from '../shared/types'
|
||||
export type { CopilotAction }
|
||||
|
||||
export type UseCopilotEntrypointHelpers = {}
|
||||
|
||||
export function useCopilotEntrypoint({}: CopilotEntrypointOptions = {}): UseCopilotEntrypointHelpers {
|
||||
// Generate an unique id for the chat if not provided.
|
||||
// export function useCopilotEntrypoint({}: CopilotAction = {}): UseCopilotEntrypointHelpers {
|
||||
// // Generate an unique id for the chat if not provided.
|
||||
|
||||
return {}
|
||||
}
|
||||
// return {}
|
||||
// }
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { CopilotEntrypointOptions } from '../shared/types'
|
||||
export type { CopilotEntrypointOptions }
|
||||
import type { CopilotAction } from '../shared/types'
|
||||
export type { CopilotAction }
|
||||
|
||||
export type UseCopilotEntrypointHelpers = {}
|
||||
|
||||
export function useCopilotEntrypoint({}: CopilotEntrypointOptions = {}): UseCopilotEntrypointHelpers {
|
||||
// Generate an unique id for the chat if not provided.
|
||||
// export function useCopilotEntrypoint({}: CopilotAction = {}): UseCopilotEntrypointHelpers {
|
||||
// // Generate an unique id for the chat if not provided.
|
||||
|
||||
return {}
|
||||
}
|
||||
// return {}
|
||||
// }
|
||||
|
||||
Generated
+2440
-152
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user