'use client'; import { type ReactNode, createContext, useContext, useState } from 'react'; import { faker } from '@faker-js/faker'; import { cn } from '@udecode/cn'; import { CopilotPlugin } from '@udecode/plate-ai/react'; import { useEditorPlugin } from '@udecode/plate-common/react'; import { useChat as useBaseChat } from 'ai/react'; import { ArrowUpRight, Check, ChevronsUpDown, Eye, EyeOff, Settings, } from 'lucide-react'; import { Button } from '~/components/plate-ui/button'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '~/components/plate-ui/command'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from '~/components/plate-ui/dialog'; import { Input } from '~/components/plate-ui/input'; import { Popover, PopoverContent, PopoverTrigger, } from '~/components/plate-ui/popover'; export const useChat = () => { return useBaseChat({ id: 'editor', api: '/api/ai/command', body: { apiKey: useOpenAI().apiKey, model: useOpenAI().model.value, }, fetch: async (input, init) => { const res = await fetch(input, init); if (!res.ok) { // Mock the API response. Remove it when you implement the route /api/ai/command await new Promise((resolve) => setTimeout(resolve, 400)); const stream = fakeStreamText(); return new Response(stream, { headers: { Connection: 'keep-alive', 'Content-Type': 'text/plain', }, }); } return res; }, }); }; // Used for testing. Remove it after implementing useChat api. const fakeStreamText = ({ chunkCount = 10, streamProtocol = 'data', }: { chunkCount?: number; streamProtocol?: 'data' | 'text'; } = {}) => { const chunks = Array.from({ length: chunkCount }, () => ({ delay: faker.number.int({ max: 150, min: 50 }), texts: faker.lorem.words({ max: 3, min: 1 }) + ' ', })); const encoder = new TextEncoder(); return new ReadableStream({ async start(controller) { for (const chunk of chunks) { await new Promise((resolve) => setTimeout(resolve, chunk.delay)); if (streamProtocol === 'text') { controller.enqueue(encoder.encode(chunk.texts)); } else { controller.enqueue( encoder.encode(`0:${JSON.stringify(chunk.texts)}\n`) ); } } if (streamProtocol === 'data') { controller.enqueue( `d:{"finishReason":"stop","usage":{"promptTokens":0,"completionTokens":${chunks.length}}}\n` ); } controller.close(); }, }); }; interface Model { label: string; value: string; } interface OpenAIContextType { apiKey: string; model: Model; setApiKey: (key: string) => void; setModel: (model: Model) => void; } export const models: Model[] = [ { label: 'gpt-4o-mini', value: 'gpt-4o-mini' }, { label: 'gpt-4o', value: 'gpt-4o' }, { label: 'gpt-4-turbo', value: 'gpt-4-turbo' }, { label: 'gpt-4', value: 'gpt-4' }, { label: 'gpt-3.5-turbo', value: 'gpt-3.5-turbo' }, { label: 'gpt-3.5-turbo-instruct', value: 'gpt-3.5-turbo-instruct' }, ]; const OpenAIContext = createContext(undefined); export function OpenAIProvider({ children }: { children: ReactNode }) { const [apiKey, setApiKey] = useState(''); const [model, setModel] = useState(models[0]); return ( {children} ); } export function useOpenAI() { const context = useContext(OpenAIContext); return ( context ?? ({ apiKey: '', model: models[0], setApiKey: () => {}, setModel: () => {}, } as OpenAIContextType) ); } export function SettingsDialog() { const { apiKey, model, setApiKey, setModel } = useOpenAI(); const [tempKey, setTempKey] = useState(apiKey); const [showKey, setShowKey] = useState(false); const [open, setOpen] = useState(false); const [openModel, setOpenModel] = useState(false); const { getOptions, setOption } = useEditorPlugin(CopilotPlugin); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setApiKey(tempKey); setOpen(false); const completeOptions = getOptions().completeOptions ?? {}; setOption('completeOptions', { ...completeOptions, body: { ...completeOptions.body, apiKey: tempKey, model: model.value, }, }); }; return ( AI Settings Enter your{' '} OpenAI API key {' '} to use AI features.
setTempKey(e.target.value)} placeholder="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" data-1p-ignore type={showKey ? 'text' : 'password'} />
No model found. {models.map((m) => ( { setModel(m); setOpenModel(false); }} > {m.label} ))}

Not stored anywhere. Used only for current session requests.

); }