{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-chat",
  "dependencies": [
    "@ai-sdk/react@4",
    "@faker-js/faker"
  ],
  "registryDependencies": [
    "https://platejs.org/r/ai-kit.json"
  ],
  "files": [
    {
      "path": "src/registry/components/editor/use-chat.ts",
      "content": "'use client';\n\n/* eslint-disable react-hooks/refs -- Fake stream abort control is imperative transport state. */\n\nimport * as React from 'react';\n\nimport { type UseChatHelpers, useChat as useBaseChat } from '@ai-sdk/react';\nimport { faker } from '@faker-js/faker';\nimport {\n  AIChatPlugin,\n  aiCommentToRange,\n  applyTableCellSuggestion,\n} from '@platejs/ai/react';\nimport { getCommentKey, getTransientCommentKey } from '@platejs/comment';\nimport { deserializeMd } from '@platejs/markdown';\nimport { BlockSelectionPlugin } from '@platejs/selection/react';\nimport { type UIMessage, DefaultChatTransport } from 'ai';\nimport { type TNode, KEYS, nanoid, NodeApi, TextApi } from 'platejs';\nimport { type PlateEditor, useEditorRef, usePluginOption } from 'platejs/react';\n\nimport { aiChatPlugin } from '@/registry/components/editor/plugins/ai-kit';\n\nimport { discussionPlugin } from './plugins/discussion-kit';\nimport { withAIBatch } from '@platejs/ai';\n\nexport type ToolName = 'comment' | 'edit' | 'generate';\n\nexport type TComment = {\n  comment: {\n    blockId: string;\n    comment: string;\n    content: string;\n  } | null;\n  status: 'finished' | 'streaming';\n};\n\nexport type TTableCellUpdate = {\n  cellUpdate: {\n    content: string;\n    id: string;\n  } | null;\n  status: 'finished' | 'streaming';\n};\n\nexport type MessageDataPart = {\n  toolName: ToolName;\n  comment?: TComment;\n  table?: TTableCellUpdate;\n};\n\nexport type Chat = UseChatHelpers<ChatMessage>;\n\nexport type ChatMessage = UIMessage<{}, MessageDataPart>;\n\nfunction createChatTransport({\n  api,\n  abortControllerRef,\n  editor,\n}: {\n  api: string;\n  abortControllerRef: React.RefObject<AbortController | null>;\n  editor: PlateEditor;\n}) {\n  return new DefaultChatTransport({\n    api,\n    // Mock the API response. Remove it when you implement the route /api/ai/command\n    fetch: (async (input, init) => {\n      const bodyOptions = editor.getOptions(aiChatPlugin).chatOptions?.body;\n\n      const initBody = JSON.parse(init?.body as string);\n\n      const body = {\n        ...initBody,\n        ...bodyOptions,\n      };\n\n      const res = await fetch(input, {\n        ...init,\n        body: JSON.stringify(body),\n      });\n\n      if (!res.ok) {\n        let sample: 'comment' | 'markdown' | 'mdx' | 'table' | null = null;\n\n        try {\n          const body = JSON.parse(init?.body as string);\n          const content = body.messages\n            .at(-1)\n            .parts.find((p: any) => p.type === 'text')?.text;\n\n          if (content.includes('Generate a markdown sample')) {\n            sample = 'markdown';\n          } else if (content.includes('Generate a mdx sample')) {\n            sample = 'mdx';\n          } else if (content.includes('comment')) {\n            sample = 'comment';\n          }\n\n          // Detect table editing by checking if multiple table cells are selected\n          // Single cell selection should use normal edit flow, only multi-cell uses table tool\n          if (!sample) {\n            // First check: selectedCells from TablePlugin (cell selection mode)\n            const selectedCells =\n              editor.getOption({ key: KEYS.table }, 'selectedCells') || [];\n\n            if (selectedCells.length > 1) {\n              sample = 'table';\n            }\n            // Second check: selection range spans multiple cells\n            else if (body.ctx?.children && body.ctx?.selection) {\n              const { selection, children } = body.ctx;\n              const anchorPath = selection.anchor?.path;\n              const focusPath = selection.focus?.path;\n\n              if (anchorPath && anchorPath.length >= 3) {\n                const rootIndex = anchorPath[0];\n                const rootNode = children[rootIndex];\n\n                if (rootNode?.type === 'table') {\n                  // Cell path is at index 2 (table -> row -> cell)\n                  const anchorCellPath = anchorPath.slice(0, 3).join(',');\n                  const focusCellPath = focusPath?.slice(0, 3).join(',');\n\n                  // Only use table mock if anchor and focus are in different cells\n                  if (focusCellPath && anchorCellPath !== focusCellPath) {\n                    sample = 'table';\n                  }\n                }\n              }\n            }\n          }\n        } catch {\n          sample = null;\n        }\n\n        const abortController = new AbortController();\n        abortControllerRef.current = abortController;\n\n        await new Promise((resolve) => setTimeout(resolve, 400));\n\n        const stream = fakeStreamText({\n          editor,\n          sample,\n          signal: abortController.signal,\n        });\n\n        const response = new Response(stream, {\n          headers: {\n            Connection: 'keep-alive',\n            'Content-Type': 'text/plain',\n          },\n        });\n\n        return response;\n      }\n\n      return res;\n    }) as typeof fetch,\n  });\n}\n\nexport const useChat = () => {\n  const editor = useEditorRef();\n  const options = usePluginOption(aiChatPlugin, 'chatOptions');\n\n  // remove when you implement the route /api/ai/command\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n  const _abortFakeStream = React.useCallback(() => {\n    if (abortControllerRef.current) {\n      abortControllerRef.current.abort();\n      abortControllerRef.current = null;\n    }\n  }, []);\n\n  const transport = React.useMemo(\n    () =>\n      createChatTransport({\n        api: options.api || '/api/ai/command',\n        abortControllerRef,\n        editor,\n      }),\n    [editor, options.api]\n  );\n\n  const baseChat = useBaseChat<ChatMessage>({\n    id: 'editor',\n    transport,\n    onData(data) {\n      if (data.type === 'data-toolName') {\n        editor.setOption(AIChatPlugin, 'toolName', data.data as ToolName);\n      }\n\n      if (data.type === 'data-table' && data.data) {\n        const tableData = data.data as TTableCellUpdate;\n\n        if (tableData.status === 'finished') {\n          const chatSelection = editor.getOption(AIChatPlugin, 'chatSelection');\n\n          if (!chatSelection) return;\n\n          editor.tf.setSelection(chatSelection);\n\n          return;\n        }\n\n        const cellUpdate = tableData.cellUpdate!;\n\n        withAIBatch(editor, () => {\n          applyTableCellSuggestion(editor, cellUpdate);\n        });\n      }\n\n      if (data.type === 'data-comment' && data.data) {\n        const commentData = data.data as TComment;\n\n        if (commentData.status === 'finished') {\n          editor.getApi(BlockSelectionPlugin).blockSelection.deselect();\n\n          return;\n        }\n\n        const aiComment = commentData.comment!;\n        const range = aiCommentToRange(editor, aiComment);\n\n        if (!range) return console.warn('No range found for AI comment');\n\n        const discussions =\n          editor.getOption(discussionPlugin, 'discussions') || [];\n\n        // Generate a new discussion ID\n        const discussionId = nanoid();\n\n        // Create a new comment\n        const newComment = {\n          id: nanoid(),\n          contentRich: [{ children: [{ text: aiComment.comment }], type: 'p' }],\n          createdAt: new Date(),\n          discussionId,\n          isEdited: false,\n          userId: editor.getOption(discussionPlugin, 'currentUserId'),\n        };\n\n        // Create a new discussion\n        const newDiscussion = {\n          id: discussionId,\n          comments: [newComment],\n          createdAt: new Date(),\n          documentContent: deserializeMd(editor, aiComment.content)\n            .map((node: TNode) => NodeApi.string(node))\n            .join('\\n'),\n          isResolved: false,\n          userId: editor.getOption(discussionPlugin, 'currentUserId'),\n        };\n\n        // Update discussions\n        const updatedDiscussions = [...discussions, newDiscussion];\n        editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n\n        // Apply comment marks to the editor\n        editor.tf.withMerging(() => {\n          editor.tf.setNodes(\n            {\n              [getCommentKey(newDiscussion.id)]: true,\n              [getTransientCommentKey()]: true,\n              [KEYS.comment]: true,\n            },\n            {\n              at: range,\n              match: TextApi.isText,\n              split: true,\n            }\n          );\n        });\n      }\n    },\n\n    ...options,\n  });\n\n  const chat = {\n    ...baseChat,\n    _abortFakeStream,\n  };\n\n  React.useEffect(() => {\n    editor.setOption(AIChatPlugin, 'chat', chat as any);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [chat.status, chat.messages, chat.error, _abortFakeStream]);\n\n  return chat;\n};\n\n// Used for testing. Remove it after implementing useChat api.\nconst fakeStreamText = ({\n  chunkCount = 10,\n  editor,\n  sample = null,\n  signal,\n}: {\n  editor: PlateEditor;\n  chunkCount?: number;\n  sample?: 'comment' | 'markdown' | 'mdx' | 'table' | null;\n  signal?: AbortSignal;\n}) => {\n  const encoder = new TextEncoder();\n\n  return new ReadableStream({\n    async start(controller) {\n      const blocks = (() => {\n        if (sample === 'markdown') {\n          return markdownChunks;\n        }\n\n        if (sample === 'mdx') {\n          return mdxChunks;\n        }\n\n        if (sample === 'comment') {\n          const commentChunks = createCommentChunks(editor);\n          return commentChunks;\n        }\n\n        if (sample === 'table') {\n          const tableChunks = createTableCellChunks(editor);\n          return tableChunks;\n        }\n\n        return [\n          Array.from({ length: chunkCount }, () => ({\n            delay: faker.number.int({ max: 100, min: 30 }),\n            texts: `${faker.lorem.words({ max: 3, min: 1 })} `,\n          })),\n\n          Array.from({ length: chunkCount + 2 }, () => ({\n            delay: faker.number.int({ max: 100, min: 30 }),\n            texts: `${faker.lorem.words({ max: 3, min: 1 })} `,\n          })),\n\n          Array.from({ length: chunkCount + 4 }, () => ({\n            delay: faker.number.int({ max: 100, min: 30 }),\n            texts: `${faker.lorem.words({ max: 3, min: 1 })} `,\n          })),\n        ];\n      })();\n      if (signal?.aborted) {\n        controller.error(new Error('Aborted before start'));\n        return;\n      }\n\n      const abortHandler = () => {\n        controller.error(new Error('Stream aborted'));\n      };\n\n      signal?.addEventListener('abort', abortHandler);\n\n      // Generate a unique message ID\n      const messageId = `msg_${faker.string.alphanumeric(40)}`;\n\n      // Handle comment and table data differently (they use data events, not text streams)\n      if (sample === 'comment' || sample === 'table') {\n        controller.enqueue(encoder.encode('data: {\"type\":\"start\"}\\n\\n'));\n        await new Promise((resolve) => setTimeout(resolve, 10));\n\n        controller.enqueue(encoder.encode('data: {\"type\":\"start-step\"}\\n\\n'));\n        await new Promise((resolve) => setTimeout(resolve, 10));\n\n        // For comments and tables, send data events directly\n        for (const block of blocks) {\n          for (const chunk of block) {\n            await new Promise((resolve) => setTimeout(resolve, chunk.delay));\n\n            if (signal?.aborted) {\n              signal?.removeEventListener('abort', abortHandler);\n              return;\n            }\n\n            // Send the data event directly (already formatted as JSON)\n            controller.enqueue(encoder.encode(`data: ${chunk.texts}\\n\\n`));\n          }\n        }\n\n        // Send the final DONE event\n        controller.enqueue(encoder.encode('data: [DONE]\\n\\n'));\n      } else {\n        // Send initial stream events for text content\n        controller.enqueue(encoder.encode('data: {\"type\":\"start\"}\\n\\n'));\n        await new Promise((resolve) => setTimeout(resolve, 10));\n\n        controller.enqueue(encoder.encode('data: {\"type\":\"start-step\"}\\n\\n'));\n        await new Promise((resolve) => setTimeout(resolve, 10));\n\n        controller.enqueue(\n          encoder.encode(\n            `data: {\"type\":\"text-start\",\"id\":\"${messageId}\",\"providerMetadata\":{\"openai\":{\"itemId\":\"${messageId}\"}}}\\n\\n`\n          )\n        );\n        await new Promise((resolve) => setTimeout(resolve, 10));\n\n        for (let i = 0; i < blocks.length; i++) {\n          const block = blocks[i];\n\n          // Stream the block content\n          for (const chunk of block) {\n            await new Promise((resolve) => setTimeout(resolve, chunk.delay));\n\n            if (signal?.aborted) {\n              signal?.removeEventListener('abort', abortHandler);\n              return;\n            }\n\n            // Properly escape the text for JSON\n            const escapedText = chunk.texts\n              .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes first\n              .replace(/\"/g, String.raw`\\\"`) // Escape quotes\n              .replace(/\\n/g, String.raw`\\n`) // Escape newlines\n              .replace(/\\r/g, String.raw`\\r`) // Escape carriage returns\n              .replace(/\\t/g, String.raw`\\t`); // Escape tabs\n\n            controller.enqueue(\n              encoder.encode(\n                `data: {\"type\":\"text-delta\",\"id\":\"${messageId}\",\"delta\":\"${escapedText}\"}\\n\\n`\n              )\n            );\n          }\n\n          // Add double newline after each block except the last one\n          if (i < blocks.length - 1) {\n            controller.enqueue(\n              encoder.encode(\n                `data: {\"type\":\"text-delta\",\"id\":\"${messageId}\",\"delta\":\"\\\\n\\\\n\"}\\n\\n`\n              )\n            );\n          }\n        }\n\n        // Send end events\n        controller.enqueue(\n          encoder.encode(`data: {\"type\":\"text-end\",\"id\":\"${messageId}\"}\\n\\n`)\n        );\n        await new Promise((resolve) => setTimeout(resolve, 10));\n\n        controller.enqueue(encoder.encode('data: {\"type\":\"finish-step\"}\\n\\n'));\n        await new Promise((resolve) => setTimeout(resolve, 10));\n\n        controller.enqueue(encoder.encode('data: {\"type\":\"finish\"}\\n\\n'));\n        await new Promise((resolve) => setTimeout(resolve, 10));\n\n        controller.enqueue(encoder.encode('data: [DONE]\\n\\n'));\n      }\n\n      signal?.removeEventListener('abort', abortHandler);\n      controller.close();\n    },\n  });\n};\n\nconst delay = faker.number.int({ max: 20, min: 5 });\n\nconst markdownChunks = [\n  [\n    { delay, texts: 'Make text ' },\n    { delay, texts: '**bold**' },\n    { delay, texts: ', ' },\n    { delay, texts: '*italic*' },\n    { delay, texts: ', ' },\n    { delay, texts: '__underlined__' },\n    { delay, texts: ', or apply a ' },\n    {\n      delay,\n      texts: '***combination***',\n    },\n    { delay, texts: ' ' },\n    { delay, texts: 'of ' },\n    { delay, texts: 'these ' },\n    { delay, texts: 'styles ' },\n    { delay, texts: 'for ' },\n    { delay, texts: 'a ' },\n    { delay, texts: 'visually ' },\n    { delay, texts: 'striking ' },\n    { delay, texts: 'effect.' },\n    { delay, texts: '\\n\\n' },\n    { delay, texts: 'Add ' },\n    {\n      delay,\n      texts: '~~strikethrough~~',\n    },\n    { delay, texts: ' ' },\n    { delay, texts: 'to ' },\n    { delay, texts: 'indicate ' },\n    { delay, texts: 'deleted ' },\n    { delay, texts: 'or ' },\n    { delay, texts: 'outdated ' },\n    { delay, texts: 'content.' },\n    { delay, texts: '\\n\\n' },\n    { delay, texts: 'Write ' },\n    { delay, texts: 'code ' },\n    { delay, texts: 'snippets ' },\n    { delay, texts: 'with ' },\n    { delay, texts: 'inline ' },\n    { delay, texts: '`code`' },\n    { delay, texts: ' formatting ' },\n    { delay, texts: 'for ' },\n    { delay, texts: 'easy ' },\n    { delay: faker.number.int({ max: 100, min: 30 }), texts: 'readability.' },\n    { delay, texts: '\\n\\n' },\n    { delay, texts: 'Add ' },\n    {\n      delay,\n      texts: '[links](https://example.com)',\n    },\n    { delay: faker.number.int({ max: 100, min: 30 }), texts: ' to ' },\n    { delay: faker.number.int({ max: 100, min: 30 }), texts: 'external ' },\n    { delay, texts: 'resources ' },\n    { delay, texts: 'or ' },\n    {\n      delay,\n      texts: 'references.\\n\\n',\n    },\n\n    { delay, texts: 'Use ' },\n    { delay, texts: 'inline ' },\n    { delay, texts: 'math ' },\n    { delay, texts: 'equations ' },\n    { delay, texts: 'like ' },\n    { delay, texts: '$E = mc^2$ ' },\n    { delay, texts: 'for ' },\n    { delay, texts: 'scientific ' },\n    { delay, texts: 'notation.' },\n    { delay, texts: '\\n\\n' },\n\n    { delay, texts: '# ' },\n    { delay, texts: 'Heading ' },\n    { delay, texts: '1\\n\\n' },\n    { delay, texts: '## ' },\n    { delay, texts: 'Heading ' },\n    { delay, texts: '2\\n\\n' },\n    { delay, texts: '### ' },\n    { delay, texts: 'Heading ' },\n    { delay, texts: '3\\n\\n' },\n    { delay, texts: '> ' },\n    { delay, texts: 'Blockquote\\n\\n' },\n    { delay, texts: '- ' },\n    { delay, texts: 'Unordered ' },\n    { delay, texts: 'list ' },\n    { delay, texts: 'item ' },\n    { delay, texts: '1\\n' },\n    { delay, texts: '- ' },\n    { delay, texts: 'Unordered ' },\n    { delay, texts: 'list ' },\n    { delay, texts: 'item ' },\n    { delay, texts: '2\\n\\n' },\n    { delay, texts: '1. ' },\n    { delay, texts: 'Ordered ' },\n    { delay, texts: 'list ' },\n    { delay, texts: 'item ' },\n    { delay, texts: '1\\n' },\n    { delay, texts: '2. ' },\n    { delay, texts: 'Ordered ' },\n    { delay, texts: 'list ' },\n    { delay, texts: 'item ' },\n    { delay, texts: '2\\n\\n' },\n    { delay, texts: '- ' },\n    { delay, texts: '[ ' },\n    { delay, texts: '] ' },\n    { delay, texts: 'Task ' },\n    { delay, texts: 'list ' },\n    { delay, texts: 'item ' },\n    { delay, texts: '1\\n' },\n    { delay, texts: '- ' },\n    { delay, texts: '[x] ' },\n    { delay, texts: 'Task ' },\n    { delay, texts: 'list ' },\n    { delay, texts: 'item ' },\n    { delay, texts: '2\\n\\n' },\n    { delay, texts: '![Alt ' },\n    {\n      delay,\n      texts:\n        'text](https://images.unsplash.com/photo-1712688930249-98e1963af7bd?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D)\\n\\n',\n    },\n    {\n      delay,\n      texts: '### Advantage blocks:\\n',\n    },\n    { delay, texts: '\\n' },\n    { delay, texts: '$$\\n' },\n    {\n      delay,\n      texts: 'a^2 + b^2 = c^2\\n',\n    },\n    { delay, texts: '$$\\n' },\n    { delay, texts: '\\n' },\n    { delay, texts: '```python\\n' },\n    { delay, texts: '# ' },\n    { delay, texts: 'Code ' },\n    { delay, texts: 'block\\n' },\n    { delay, texts: 'print(\"Hello, ' },\n    { delay, texts: 'World!\")\\n' },\n    { delay, texts: '```\\n\\n' },\n    { delay, texts: 'Horizontal ' },\n    { delay, texts: 'rule\\n\\n' },\n    { delay, texts: '---\\n\\n' },\n    { delay, texts: '| ' },\n    { delay, texts: 'Header ' },\n    { delay, texts: '1 ' },\n    { delay, texts: '| ' },\n    { delay, texts: 'Header ' },\n    { delay, texts: '2 ' },\n    { delay, texts: '|\\n' },\n    {\n      delay,\n      texts: '|----------|----------|\\n',\n    },\n    { delay, texts: '| ' },\n    { delay, texts: 'Row ' },\n    { delay, texts: '1   ' },\n    { delay, texts: ' | ' },\n    { delay, texts: 'Data    ' },\n    { delay, texts: ' |\\n' },\n    { delay, texts: '| ' },\n    { delay, texts: 'Row ' },\n    { delay, texts: '2   ' },\n    { delay, texts: ' | ' },\n    { delay, texts: 'Data    ' },\n    { delay, texts: ' |' },\n  ],\n];\n\nconst mdxChunks = [\n  [\n    {\n      delay,\n      texts: '## ',\n    },\n    {\n      delay,\n      texts: 'Basic ',\n    },\n    {\n      delay,\n      texts: 'Markdown\\n\\n',\n    },\n    {\n      delay,\n      texts: '> ',\n    },\n    {\n      delay,\n      texts: 'The ',\n    },\n    {\n      delay,\n      texts: 'following ',\n    },\n    {\n      delay,\n      texts: 'node ',\n    },\n    {\n      delay,\n      texts: 'and ',\n    },\n    {\n      delay,\n      texts: 'marks ',\n    },\n    {\n      delay,\n      texts: 'is ',\n    },\n    {\n      delay,\n      texts: 'supported ',\n    },\n    {\n      delay,\n      texts: 'by ',\n    },\n    {\n      delay,\n      texts: 'the ',\n    },\n    {\n      delay,\n      texts: 'Markdown ',\n    },\n    {\n      delay,\n      texts: 'standard.\\n\\n',\n    },\n    {\n      delay,\n      texts: 'Format ',\n    },\n    {\n      delay,\n      texts: 'text ',\n    },\n    {\n      delay,\n      texts: 'with **b',\n    },\n    {\n      delay,\n      texts: 'old**, _',\n    },\n    {\n      delay,\n      texts: 'italic_,',\n    },\n    {\n      delay,\n      texts: ' _**comb',\n    },\n    {\n      delay,\n      texts: 'ined sty',\n    },\n    {\n      delay,\n      texts: 'les**_, ',\n    },\n    {\n      delay,\n      texts: '~~strike',\n    },\n    {\n      delay,\n      texts: 'through~',\n    },\n    {\n      delay,\n      texts: '~, `code',\n    },\n    {\n      delay,\n      texts: '` format',\n    },\n    {\n      delay,\n      texts: 'ting, an',\n    },\n    {\n      delay,\n      texts: 'd [hyper',\n    },\n    {\n      delay,\n      texts: 'links](https://en.wikipedia.org/wiki/Hypertext).\\n\\n',\n    },\n    {\n      delay,\n      texts: '```javascript\\n',\n    },\n    {\n      delay,\n      texts: '// Use code blocks to showcase code snippets\\n',\n    },\n    {\n      delay,\n      texts: 'function greet() {\\n',\n    },\n    {\n      delay,\n      texts: '  console.info(\"Hello World!\")\\n',\n    },\n    {\n      delay,\n      texts: '}\\n',\n    },\n    {\n      delay,\n      texts: '```\\n\\n',\n    },\n    {\n      delay,\n      texts: '- Simple',\n    },\n    {\n      delay,\n      texts: ' lists f',\n    },\n    {\n      delay,\n      texts: 'or organ',\n    },\n    {\n      delay,\n      texts: 'izing co',\n    },\n    {\n      delay,\n      texts: 'ntent\\n',\n    },\n    {\n      delay,\n      texts: '1. ',\n    },\n    {\n      delay,\n      texts: 'Numbered ',\n    },\n    {\n      delay,\n      texts: 'lists ',\n    },\n    {\n      delay,\n      texts: 'for ',\n    },\n    {\n      delay,\n      texts: 'sequential ',\n    },\n    {\n      delay,\n      texts: 'steps\\n\\n',\n    },\n    {\n      delay,\n      texts: '| **Plugin**  | **Element** | **Inline** | **Void** |\\n',\n    },\n    {\n      delay,\n      texts: '| ----------- | ----------- | ---------- | -------- |\\n',\n    },\n    {\n      delay,\n      texts: '| **Heading** |             |            | No       |\\n',\n    },\n    {\n      delay,\n      texts: '| **Image**   | Yes         | No         | Yes      |\\n',\n    },\n    {\n      delay,\n      texts: '| **Ment',\n    },\n    {\n      delay,\n      texts: 'ion** | Yes         | Yes        | Yes      |\\n\\n',\n    },\n    {\n      delay,\n      texts:\n        '![](https://images.unsplash.com/photo-1712688930249-98e1963af7bd?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D)\\n\\n',\n    },\n    {\n      delay,\n      texts: '- [x] Co',\n    },\n    {\n      delay,\n      texts: 'mpleted ',\n    },\n    {\n      delay,\n      texts: 'tasks\\n',\n    },\n    {\n      delay,\n      texts: '- [ ] Pe',\n    },\n    {\n      delay,\n      texts: 'nding ta',\n    },\n    {\n      delay,\n      texts: 'sks\\n\\n',\n    },\n    {\n      delay,\n      texts: '---\\n\\n## Advan',\n    },\n    {\n      delay,\n      texts: 'ced Feat',\n    },\n    {\n      delay,\n      texts: 'ures\\n\\n',\n    },\n    {\n      delay,\n      texts: '<callout> ',\n    },\n    {\n      delay,\n      texts: 'The ',\n    },\n    {\n      delay,\n      texts: 'following ',\n    },\n    {\n      delay,\n      texts: 'node ',\n    },\n    {\n      delay,\n      texts: 'and ',\n    },\n    {\n      delay,\n      texts: 'marks ',\n    },\n    {\n      delay,\n      texts: 'are ',\n    },\n    {\n      delay,\n      texts: 'not ',\n    },\n    {\n      delay,\n      texts: 'supported ',\n    },\n    {\n      delay,\n      texts: 'in ',\n    },\n    {\n      delay,\n      texts: 'Markdown ',\n    },\n    {\n      delay,\n      texts: 'but ',\n    },\n    {\n      delay,\n      texts: 'can ',\n    },\n    {\n      delay,\n      texts: 'be ',\n    },\n    {\n      delay,\n      texts: 'serialized ',\n    },\n    {\n      delay,\n      texts: 'and ',\n    },\n    {\n      delay,\n      texts: 'deserialized ',\n    },\n    {\n      delay,\n      texts: 'using ',\n    },\n    {\n      delay,\n      texts: 'MDX ',\n    },\n    {\n      delay,\n      texts: 'or ',\n    },\n    {\n      delay,\n      texts: 'specialized ',\n    },\n    {\n      delay,\n      texts: 'UnifiedJS ',\n    },\n    {\n      delay,\n      texts: 'plugins. ',\n    },\n    {\n      delay,\n      texts: '</callout>\\n\\n',\n    },\n    {\n      delay,\n      texts: 'Advanced ',\n    },\n    {\n      delay,\n      texts: 'marks: ',\n    },\n    {\n      delay,\n      texts: '<kbd>⌘ ',\n    },\n    {\n      delay,\n      texts: '+ ',\n    },\n    {\n      delay,\n      texts: 'B</kbd>,<u>underlined</u>, ',\n    },\n    {\n      delay,\n      texts: '<mark',\n    },\n    {\n      delay,\n      texts: '>highli',\n    },\n    {\n      delay,\n      texts: 'ghted</m',\n    },\n    {\n      delay,\n      texts: 'ark',\n    },\n    {\n      delay,\n      texts: '> text, ',\n    },\n    {\n      delay,\n      texts: '<span s',\n    },\n    {\n      delay,\n      texts: 'tyle=\"co',\n    },\n    {\n      delay,\n      texts: 'lor: #93',\n    },\n    {\n      delay,\n      texts: 'C47D;\">c',\n    },\n    {\n      delay,\n      texts: 'olored t',\n    },\n    {\n      delay,\n      texts: 'ext</spa',\n    },\n    {\n      delay,\n      texts: 'n> and ',\n    },\n    {\n      delay,\n      texts: '<spa',\n    },\n    {\n      delay,\n      texts: 'n',\n    },\n    {\n      delay,\n      texts: ' style=\"',\n    },\n    {\n      delay,\n      texts: 'backgrou',\n    },\n    {\n      delay,\n      texts: 'nd-color',\n    },\n    {\n      delay,\n      texts: ': #6C9EE',\n    },\n    {\n      delay,\n      texts: 'B;\">back',\n    },\n    {\n      delay,\n      texts: 'ground h',\n    },\n    {\n      delay,\n      texts: 'ighlight',\n    },\n    {\n      delay,\n      texts: 's</spa',\n    },\n    {\n      delay,\n      texts: 'n> for ',\n    },\n    {\n      delay,\n      texts: 'visual e',\n    },\n    {\n      delay,\n      texts: 'mphasis.\\n\\n',\n    },\n    {\n      delay,\n      texts: 'Superscript ',\n    },\n    {\n      delay,\n      texts: 'like ',\n    },\n    {\n      delay,\n      texts: 'E=mc<sup>2</sup> ',\n    },\n    {\n      delay,\n      texts: 'and ',\n    },\n    {\n      delay,\n      texts: 'subscript ',\n    },\n    {\n      delay,\n      texts: 'like ',\n    },\n    {\n      delay,\n      texts: 'H<sub>2</sub>O ',\n    },\n    {\n      delay,\n      texts: 'demonstrate ',\n    },\n    {\n      delay,\n      texts: 'mathematical ',\n    },\n    {\n      delay,\n      texts: 'and ',\n    },\n    {\n      delay,\n      texts: 'chemical ',\n    },\n    {\n      delay,\n      texts: 'notation ',\n    },\n    {\n      delay,\n      texts: 'capabilities.\\n\\n',\n    },\n    {\n      delay,\n      texts: 'Add ',\n    },\n    {\n      delay,\n      texts: 'mentions ',\n    },\n    {\n      delay,\n      texts: 'like ',\n    },\n    {\n      delay,\n      texts: '@BB-8, d',\n    },\n    {\n      delay,\n      texts: 'ates (<d',\n    },\n    {\n      delay,\n      texts: 'ate>2025',\n    },\n    {\n      delay,\n      texts: '-05-08</',\n    },\n    {\n      delay,\n      texts: 'date>), ',\n    },\n    {\n      delay,\n      texts: 'and math',\n    },\n    {\n      delay,\n      texts: ' formula',\n    },\n    {\n      delay,\n      texts: 's ($E=mc',\n    },\n    {\n      delay,\n      texts: '^2$).\\n\\n',\n    },\n    {\n      delay,\n      texts: 'The ',\n    },\n    {\n      delay,\n      texts: 'table ',\n    },\n    {\n      delay,\n      texts: 'of ',\n    },\n    {\n      delay,\n      texts: 'contents ',\n    },\n    {\n      delay,\n      texts: 'feature ',\n    },\n    {\n      delay,\n      texts: 'automatically ',\n    },\n    {\n      delay,\n      texts: 'generates ',\n    },\n    {\n      delay,\n      texts: 'document ',\n    },\n    {\n      delay,\n      texts: 'structure ',\n    },\n    {\n      delay,\n      texts: 'for ',\n    },\n    {\n      delay,\n      texts: 'easy ',\n    },\n    {\n      delay,\n      texts: 'navigation.\\n\\n',\n    },\n    {\n      delay,\n      texts: '<toc ',\n    },\n    {\n      delay,\n      texts: '/>\\n\\n',\n    },\n    {\n      delay,\n      texts: 'Math ',\n    },\n    {\n      delay,\n      texts: 'formula ',\n    },\n    {\n      delay,\n      texts: 'support ',\n    },\n    {\n      delay,\n      texts: 'makes ',\n    },\n    {\n      delay,\n      texts: 'displaying ',\n    },\n    {\n      delay,\n      texts: 'complex ',\n    },\n    {\n      delay,\n      texts: 'mathematical ',\n    },\n    {\n      delay,\n      texts: 'expressions ',\n    },\n    {\n      delay,\n      texts: 'simple.\\n\\n',\n    },\n    {\n      delay,\n      texts: '$$\\n',\n    },\n    {\n      delay,\n      texts: 'a^2',\n    },\n    {\n      delay,\n      texts: '+b^2',\n    },\n    {\n      delay,\n      texts: '=c^2\\n',\n    },\n    {\n      delay,\n      texts: '$$\\n\\n',\n    },\n    {\n      delay,\n      texts: 'Multi-co',\n    },\n    {\n      delay,\n      texts: 'lumn lay',\n    },\n    {\n      delay,\n      texts: 'out feat',\n    },\n    {\n      delay,\n      texts: 'ures ena',\n    },\n    {\n      delay,\n      texts: 'ble rich',\n    },\n    {\n      delay,\n      texts: 'er page ',\n    },\n    {\n      delay,\n      texts: 'designs ',\n    },\n    {\n      delay,\n      texts: 'and cont',\n    },\n    {\n      delay,\n      texts: 'ent layo',\n    },\n    {\n      delay,\n      texts: 'uts.\\n\\n',\n    },\n    // {\n    //  delay,\n    //   texts: '<column_group layout=\"[50,50]\">\\n',\n    // },\n    // {\n    //  delay,\n    //   texts: '<column width=\"50%\">\\n',\n    // },\n    // {\n    //  delay,\n    //   texts: '  left\\n',\n    // },\n    // {\n    //  delay,\n    //   texts: '</column>\\n',\n    // },\n    // {\n    //  delay,\n    //   texts: '<column width=\"50%\">\\n',\n    // },\n    // {\n    //  delay,\n    //   texts: '  right\\n',\n    // },\n    // {\n    //  delay,\n    //   texts: '</column>\\n',\n    // },\n    // {\n    //  delay,\n    //   texts: '</column_group>\\n\\n',\n    // },\n    {\n      delay,\n      texts: 'PDF ',\n    },\n    {\n      delay,\n      texts: 'embedding ',\n    },\n    {\n      delay,\n      texts: 'makes ',\n    },\n    {\n      delay,\n      texts: 'document ',\n    },\n    {\n      delay,\n      texts: 'referencing ',\n    },\n    {\n      delay,\n      texts: 'simple ',\n    },\n    {\n      delay,\n      texts: 'and ',\n    },\n    {\n      delay,\n      texts: 'intuitive.\\n\\n',\n    },\n    {\n      delay,\n      texts: '<file ',\n    },\n    {\n      delay,\n      texts: 'name=\"sample.pdf\" ',\n    },\n    {\n      delay,\n      texts: 'align=\"center\" ',\n    },\n    {\n      delay,\n      texts:\n        'src=\"https://s26.q4cdn.com/900411403/files/doc_downloads/test.pdf\" width=\"80%\" isUpload=\"true\" />\\n\\n',\n    },\n    {\n      delay,\n      texts: 'Audio ',\n    },\n    {\n      delay,\n      texts: 'players ',\n    },\n    {\n      delay,\n      texts: 'can ',\n    },\n    {\n      delay,\n      texts: 'be ',\n    },\n    {\n      delay,\n      texts: 'embedded ',\n    },\n    {\n      delay,\n      texts: 'directly ',\n    },\n    {\n      delay,\n      texts: 'into ',\n    },\n    {\n      delay,\n      texts: 'documents, ',\n    },\n    {\n      delay,\n      texts: 'supporting ',\n    },\n    {\n      delay,\n      texts: 'online ',\n    },\n    {\n      delay,\n      texts: 'audio ',\n    },\n    {\n      delay,\n      texts: 'resources.\\n\\n',\n    },\n    {\n      delay,\n      texts: '<audio ',\n    },\n    {\n      delay,\n      texts: 'align=\"center\" ',\n    },\n    {\n      delay,\n      texts:\n        'src=\"https://samplelib.com/lib/preview/mp3/sample-3s.mp3\" width=\"80%\" />\\n\\n',\n    },\n    {\n      delay,\n      texts: 'Video ',\n    },\n    {\n      delay,\n      texts: 'playback ',\n    },\n    {\n      delay,\n      texts: 'features ',\n    },\n    {\n      delay,\n      texts: 'support ',\n    },\n    {\n      delay,\n      texts: 'embedding ',\n    },\n    {\n      delay,\n      texts: 'various ',\n    },\n    {\n      delay,\n      texts: 'online ',\n    },\n    {\n      delay,\n      texts: 'video ',\n    },\n    {\n      delay,\n      texts: 'resources, ',\n    },\n    {\n      delay,\n      texts: 'enriching ',\n    },\n    {\n      delay,\n      texts: 'document ',\n    },\n    {\n      delay,\n      texts: 'content.\\n\\n',\n    },\n    {\n      delay,\n      texts: '<video ',\n    },\n    {\n      delay,\n      texts: 'align=\"center\" ',\n    },\n    {\n      delay,\n      texts:\n        'src=\"https://videos.pexels.com/video-files/6769791/6769791-uhd_2560_1440_24fps.mp4\" width=\"80%\" isUpload=\"true\" />',\n    },\n  ],\n];\n\nconst createCommentChunks = (editor: PlateEditor) => {\n  const selectedBlocksApi = editor.getApi(BlockSelectionPlugin).blockSelection;\n\n  const selectedBlocks = selectedBlocksApi\n    .getNodes({\n      selectionFallback: true,\n      sort: true,\n    })\n    .map(([block]) => block);\n\n  const isSelectingSome = editor.getOption(\n    BlockSelectionPlugin,\n    'isSelectingSome'\n  );\n\n  const blocks =\n    selectedBlocks.length > 0 && (editor.api.isExpanded() || isSelectingSome)\n      ? selectedBlocks\n      : editor.children;\n\n  const max = blocks.length;\n\n  const commentCount = Math.ceil(max / 2);\n\n  const result = new Set<number>();\n\n  while (result.size < commentCount) {\n    const num = Math.floor(Math.random() * max); // 0 to max-1 (fixed: was 1 to max)\n    result.add(num);\n  }\n\n  const indexes = Array.from(result).sort((a, b) => a - b);\n\n  const chunks = indexes\n    .map((index, i) => {\n      const block = blocks[index];\n      if (!block) {\n        return [];\n      }\n\n      const blockString = NodeApi.string(block);\n      const endIndex = blockString.indexOf('.');\n      const content =\n        endIndex === -1 ? blockString : blockString.slice(0, endIndex);\n\n      return [\n        {\n          delay: faker.number.int({ max: 500, min: 200 }),\n          texts: `{\"id\":\"${nanoid()}\",\"data\":{\"comment\":{\"blockId\":\"${block.id}\",\"comment\":\"${faker.lorem.sentence()}\",\"content\":\"${content}\"},\"status\":\"${i === indexes.length - 1 ? 'finished' : 'streaming'}\"},\"type\":\"data-comment\"}`,\n        },\n      ];\n    })\n    .filter((chunk) => chunk.length > 0);\n\n  const result_chunks = [\n    [{ delay: 50, texts: '{\"data\":\"comment\",\"type\":\"data-toolName\"}' }],\n    ...chunks,\n  ];\n\n  return result_chunks;\n};\n\nconst createTableCellChunks = (editor: PlateEditor) => {\n  // Get selected table cells from the TablePlugin\n  const selectedCells =\n    editor.getOption({ key: KEYS.table }, 'selectedCells') || [];\n\n  // If no cells selected, try to get cells from current selection\n  let cellIds: string[] = [];\n\n  if (selectedCells.length > 0) {\n    cellIds = selectedCells\n      .map((cell: { id?: string }) => cell.id)\n      .filter(Boolean);\n  } else {\n    // Try to find table cells in current selection\n    const cells = Array.from(\n      editor.api.nodes({\n        at: editor.selection ?? undefined,\n        match: (n) =>\n          (n as { type?: string }).type === KEYS.td ||\n          (n as { type?: string }).type === KEYS.th,\n      })\n    );\n    cellIds = cells\n      .map(([node]) => (node as { id?: string }).id)\n      .filter(Boolean) as string[];\n  }\n\n  // If still no cells, return empty chunks\n  if (cellIds.length === 0) {\n    return [\n      [{ delay: 50, texts: '{\"data\":\"edit\",\"type\":\"data-toolName\"}' }],\n      [\n        {\n          delay: 100,\n          texts: `{\"id\":\"${nanoid()}\",\"data\":{\"cellUpdate\":null,\"status\":\"finished\"},\"type\":\"data-table\"}`,\n        },\n      ],\n    ];\n  }\n\n  // Generate mock content for each cell\n  const chunks = cellIds.map((cellId, i) => [\n    {\n      delay: faker.number.int({ max: 300, min: 100 }),\n      texts: `{\"id\":\"${nanoid()}\",\"data\":{\"cellUpdate\":{\"id\":\"${cellId}\",\"content\":\"${faker.lorem.sentence()}\"},\"status\":\"${i === cellIds.length - 1 ? 'finished' : 'streaming'}\"},\"type\":\"data-table\"}`,\n    },\n  ]);\n\n  const result_chunks = [\n    [{ delay: 50, texts: '{\"data\":\"edit\",\"type\":\"data-toolName\"}' }],\n    ...chunks,\n  ];\n\n  return result_chunks;\n};\n",
      "type": "registry:component",
      "target": "@components/editor/use-chat.ts"
    }
  ],
  "type": "registry:component"
}