diff --git a/apps/www/public/r/styles/default/editor-ai.json b/apps/www/public/r/styles/default/editor-ai.json index c5ab9626b3..3461872848 100644 --- a/apps/www/public/r/styles/default/editor-ai.json +++ b/apps/www/public/r/styles/default/editor-ai.json @@ -46,8 +46,8 @@ { "content": "'use client';\n\nimport { type ReactNode, createContext, useContext, useState } from 'react';\n\nimport { cn } from '@udecode/cn';\nimport { CopilotPlugin } from '@udecode/plate-ai/react';\nimport { useEditorPlugin } from '@udecode/plate-common/react';\nimport {\n Check,\n ChevronsUpDown,\n ExternalLinkIcon,\n Eye,\n EyeOff,\n Settings,\n Wand2Icon,\n} from 'lucide-react';\n\nimport { Button } from '@/components/plate-ui/button';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/components/plate-ui/command';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@/components/plate-ui/dialog';\nimport { Input } from '@/components/plate-ui/input';\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@/components/plate-ui/popover';\n\ninterface Model {\n label: string;\n value: string;\n}\n\ninterface SettingsContextType {\n keys: Record;\n model: Model;\n setKey: (service: string, key: string) => void;\n setModel: (model: Model) => void;\n}\n\nexport const models: Model[] = [\n { label: 'gpt-4o-mini', value: 'gpt-4o-mini' },\n { label: 'gpt-4o', value: 'gpt-4o' },\n { label: 'gpt-4-turbo', value: 'gpt-4-turbo' },\n { label: 'gpt-4', value: 'gpt-4' },\n { label: 'gpt-3.5-turbo', value: 'gpt-3.5-turbo' },\n { label: 'gpt-3.5-turbo-instruct', value: 'gpt-3.5-turbo-instruct' },\n];\n\nconst SettingsContext = createContext(\n undefined\n);\n\nexport function SettingsProvider({ children }: { children: ReactNode }) {\n const [keys, setKeys] = useState({\n openai: '',\n uploadthing: '',\n });\n const [model, setModel] = useState(models[0]);\n\n const setKey = (service: string, key: string) => {\n setKeys((prev) => ({ ...prev, [service]: key }));\n };\n\n return (\n \n {children}\n \n );\n}\n\nexport function useSettings() {\n const context = useContext(SettingsContext);\n\n return (\n context ?? {\n keys: {\n openai: '',\n uploadthing: '',\n },\n model: models[0],\n setKey: () => {},\n setModel: () => {},\n }\n );\n}\n\nexport function SettingsDialog() {\n const { keys, model, setKey, setModel } = useSettings();\n const [tempKeys, setTempKeys] = useState(keys);\n const [showKey, setShowKey] = useState>({});\n const [open, setOpen] = useState(false);\n const [openModel, setOpenModel] = useState(false);\n\n const { getOptions, setOption } = useEditorPlugin(CopilotPlugin);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n Object.entries(tempKeys).forEach(([service, key]) => {\n setKey(service, key);\n });\n setOpen(false);\n\n // Update AI options if needed\n const completeOptions = getOptions().completeOptions ?? {};\n setOption('completeOptions', {\n ...completeOptions,\n body: {\n ...completeOptions.body,\n apiKey: tempKeys.openai,\n model: model.value,\n },\n });\n };\n\n const toggleKeyVisibility = (key: string) => {\n setShowKey((prev) => ({ ...prev, [key]: !prev[key] }));\n };\n\n const renderApiKeyInput = (service: string, label: string) => (\n
\n
\n \n {label}\n \n \n \n \n Get {label}\n \n \n
\n\n \n setTempKeys((prev) => ({ ...prev, [service]: e.target.value }))\n }\n placeholder=\"\"\n data-1p-ignore\n type={showKey[service] ? 'text' : 'password'}\n />\n toggleKeyVisibility(service)}\n type=\"button\"\n >\n {showKey[service] ? (\n \n ) : (\n \n )}\n \n {showKey[service] ? 'Hide' : 'Show'} {label}\n \n \n
\n );\n\n return (\n \n \n \n
\n \n \n Settings\n \n
\n \n
\n \n \n Settings\n \n Configure your API keys and preferences.\n \n \n\n
\n {/* AI Settings Group */}\n
\n
\n
\n \n
\n

AI

\n
\n\n
\n {renderApiKeyInput('openai', 'OpenAI API key')}\n\n
\n \n Model\n \n \n \n \n {model.label}\n \n \n \n \n \n \n No model found.\n \n \n {models.map((m) => (\n {\n setModel(m);\n setOpenModel(false);\n }}\n >\n \n {m.label}\n \n ))}\n \n \n \n \n \n
\n
\n
\n\n {/* Upload Settings Group */}\n {/*
\n
\n
\n \n
\n

Upload

\n
\n\n
\n {renderApiKeyInput('uploadthing', 'Uploadthing API key')}\n
\n
*/}\n\n \n
\n\n

\n Not stored anywhere. Used only for current session requests.\n

\n
\n
\n );\n}\n", "path": "components/editor/settings.tsx", - "target": "components/settings.tsx", - "type": "registry:block" + "target": "components/editor/settings.tsx", + "type": "registry:component" } ], "name": "editor-ai", diff --git a/apps/www/src/registry/registry-blocks.ts b/apps/www/src/registry/registry-blocks.ts index 6b61431e0e..f8b42780d8 100644 --- a/apps/www/src/registry/registry-blocks.ts +++ b/apps/www/src/registry/registry-blocks.ts @@ -43,7 +43,11 @@ export const blocks: Registry = [ target: 'components/editor/use-create-editor.ts', type: 'registry:component', }, - 'components/editor/settings.tsx', + { + path: 'components/editor/settings.tsx', + target: 'components/editor/settings.tsx', + type: 'registry:component', + }, ], name: 'editor-ai', registryDependencies: [ diff --git a/templates/plate-playground-template/src/components/editor/plugins/copilot-plugins.ts b/templates/plate-playground-template/src/components/editor/plugins/copilot-plugins.ts deleted file mode 100644 index 21d7c600bb..0000000000 --- a/templates/plate-playground-template/src/components/editor/plugins/copilot-plugins.ts +++ /dev/null @@ -1,60 +0,0 @@ -'use client'; - -import type { TElement } from '@udecode/plate-common'; - -import { faker } from '@faker-js/faker'; -import { CopilotPlugin } from '@udecode/plate-ai/react'; -import { getAncestorNode } from '@udecode/plate-common'; -import { serializeMdNodes, stripMarkdown } from '@udecode/plate-markdown'; - -import { GhostText } from '@/components/plate-ui/ghost-text'; - -export const copilotPlugins = [ - CopilotPlugin.configure(({ api }) => ({ - options: { - completeOptions: { - api: '/api/ai/copilot', - body: { - system: `You are an advanced AI writing assistant, similar to VSCode Copilot but for general text. Your task is to predict and generate the next part of the text based on the given context. - - Rules: - - Continue the text naturally up to the next punctuation mark (., ,, ;, :, ?, or !). - - Maintain style and tone. Don't repeat given text. - - For unclear context, provide the most likely continuation. - - Handle code snippets, lists, or structured text if needed. - - Don't include """ in your response. - - CRITICAL: Always end with a punctuation mark. - - CRITICAL: Avoid starting a new block. Do not use block formatting like >, #, 1., 2., -, etc. The suggestion should continue in the same block as the context. - - If no context is provided or you can't generate a continuation, return "0" without explanation.`, - }, - onError: () => { - // Mock the API response. Remove it when you implement the route /api/ai/copilot - api.copilot.setBlockSuggestion({ - text: stripMarkdown(faker.lorem.sentence()), - }); - }, - onFinish: (_, completion) => { - if (completion === '0') return; - - api.copilot.setBlockSuggestion({ - text: stripMarkdown(completion), - }); - }, - }, - debounceDelay: 500, - getPrompt: ({ editor }) => { - const contextEntry = getAncestorNode(editor); - - if (!contextEntry) return ''; - - const prompt = serializeMdNodes([contextEntry[0] as TElement]); - - return `Continue the text up to the next punctuation mark: - """ - ${prompt} - """`; - }, - renderGhostText: GhostText, - }, - })), -] as const; diff --git a/templates/plate-playground-template/src/components/editor/settings.tsx b/templates/plate-playground-template/src/components/editor/settings.tsx index da0e18e45f..5dceae1c08 100644 --- a/templates/plate-playground-template/src/components/editor/settings.tsx +++ b/templates/plate-playground-template/src/components/editor/settings.tsx @@ -134,7 +134,7 @@ export function SettingsDialog() {