From 81f909ac3a1bd6801fe2e3472647818e929082cc Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 13 Feb 2025 08:25:57 -0800 Subject: [PATCH] chore: rename blocks to artifacts (#793) --- app/(chat)/api/document/route.ts | 5 +- {blocks => artifacts}/actions.ts | 0 {blocks => artifacts}/code/client.tsx | 18 +-- {blocks => artifacts}/code/server.ts | 6 +- {blocks => artifacts}/image/client.tsx | 10 +- {blocks => artifacts}/image/server.ts | 2 +- {blocks => artifacts}/sheet/client.tsx | 12 +- {blocks => artifacts}/sheet/server.ts | 6 +- {blocks => artifacts}/text/client.tsx | 26 ++-- {blocks => artifacts}/text/server.ts | 6 +- ...block-actions.tsx => artifact-actions.tsx} | 49 ++++---- components/artifact-close-button.tsx | 29 +++++ ...ock-messages.tsx => artifact-messages.tsx} | 20 +-- components/{block.tsx => artifact.tsx} | 119 +++++++++--------- components/block-close-button.tsx | 29 ----- components/chat.tsx | 10 +- components/code-block.tsx | 33 +---- components/console.tsx | 8 +- .../{create-block.tsx => create-artifact.tsx} | 40 +++--- components/data-stream-handler.tsx | 38 +++--- components/document-preview.tsx | 68 +++++----- components/document-skeleton.tsx | 10 +- components/document.tsx | 16 +-- components/messages.tsx | 4 +- components/suggestion.tsx | 10 +- components/{editor.tsx => text-editor.tsx} | 0 components/toolbar.tsx | 28 ++--- components/version-footer.tsx | 9 +- docs/02-update-models.md | 4 +- docs/03-blocks.md | 90 ++++++------- hooks/use-artifact.ts | 85 +++++++++++++ hooks/use-block.ts | 84 ------------- lib/ai/models.ts | 2 +- lib/ai/prompts.ts | 14 +-- lib/ai/tools/create-document.ts | 13 +- lib/ai/tools/request-suggestions.ts | 2 +- lib/ai/tools/update-document.ts | 8 +- lib/{blocks => artifacts}/server.ts | 22 ++-- lib/db/queries.ts | 4 +- lib/db/schema.ts | 1 - lib/editor/suggestions.tsx | 6 +- 41 files changed, 473 insertions(+), 473 deletions(-) rename {blocks => artifacts}/actions.ts (100%) rename {blocks => artifacts}/code/client.tsx (94%) rename {blocks => artifacts}/code/server.ts (89%) rename {blocks => artifacts}/image/client.tsx (89%) rename {blocks => artifacts}/image/server.ts (93%) rename {blocks => artifacts}/sheet/client.tsx (90%) rename {blocks => artifacts}/sheet/server.ts (90%) rename {blocks => artifacts}/text/client.tsx (85%) rename {blocks => artifacts}/text/server.ts (90%) rename components/{block-actions.tsx => artifact-actions.tsx} (64%) create mode 100644 components/artifact-close-button.tsx rename components/{block-messages.tsx => artifact-messages.tsx} (81%) rename components/{block.tsx => artifact.tsx} (82%) delete mode 100644 components/block-close-button.tsx rename components/{create-block.tsx => create-artifact.tsx} (67%) rename components/{editor.tsx => text-editor.tsx} (100%) create mode 100644 hooks/use-artifact.ts delete mode 100644 hooks/use-block.ts rename lib/{blocks => artifacts}/server.ts (75%) diff --git a/app/(chat)/api/document/route.ts b/app/(chat)/api/document/route.ts index 5c84312..364eb9d 100644 --- a/app/(chat)/api/document/route.ts +++ b/app/(chat)/api/document/route.ts @@ -1,5 +1,5 @@ import { auth } from '@/app/(auth)/auth'; -import { BlockKind } from '@/components/block'; +import { ArtifactKind } from '@/components/artifact'; import { deleteDocumentsByIdAfterTimestamp, getDocumentsById, @@ -53,7 +53,8 @@ export async function POST(request: Request) { content, title, kind, - }: { content: string; title: string; kind: BlockKind } = await request.json(); + }: { content: string; title: string; kind: ArtifactKind } = + await request.json(); if (session.user?.id) { const document = await saveDocument({ diff --git a/blocks/actions.ts b/artifacts/actions.ts similarity index 100% rename from blocks/actions.ts rename to artifacts/actions.ts diff --git a/blocks/code/client.tsx b/artifacts/code/client.tsx similarity index 94% rename from blocks/code/client.tsx rename to artifacts/code/client.tsx index ca285c1..8223c9a 100644 --- a/blocks/code/client.tsx +++ b/artifacts/code/client.tsx @@ -1,4 +1,4 @@ -import { Block } from '@/components/create-block'; +import { Artifact } from '@/components/create-artifact'; import { CodeEditor } from '@/components/code-editor'; import { CopyIcon, @@ -66,7 +66,7 @@ interface Metadata { outputs: Array; } -export const codeBlock = new Block<'code', Metadata>({ +export const codeArtifact = new Artifact<'code', Metadata>({ kind: 'code', description: 'Useful for code generation; Code execution is only available for python code.', @@ -75,17 +75,17 @@ export const codeBlock = new Block<'code', Metadata>({ outputs: [], }); }, - onStreamPart: ({ streamPart, setBlock }) => { + onStreamPart: ({ streamPart, setArtifact }) => { if (streamPart.type === 'code-delta') { - setBlock((draftBlock) => ({ - ...draftBlock, + setArtifact((draftArtifact) => ({ + ...draftArtifact, content: streamPart.content as string, isVisible: - draftBlock.status === 'streaming' && - draftBlock.content.length > 300 && - draftBlock.content.length < 310 + draftArtifact.status === 'streaming' && + draftArtifact.content.length > 300 && + draftArtifact.content.length < 310 ? true - : draftBlock.isVisible, + : draftArtifact.isVisible, status: 'streaming', })); } diff --git a/blocks/code/server.ts b/artifacts/code/server.ts similarity index 89% rename from blocks/code/server.ts rename to artifacts/code/server.ts index b5e163b..141e217 100644 --- a/blocks/code/server.ts +++ b/artifacts/code/server.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { streamObject } from 'ai'; import { myProvider } from '@/lib/ai/models'; import { codePrompt, updateDocumentPrompt } from '@/lib/ai/prompts'; -import { createDocumentHandler } from '@/lib/blocks/server'; +import { createDocumentHandler } from '@/lib/artifacts/server'; export const codeDocumentHandler = createDocumentHandler<'code'>({ kind: 'code', @@ -10,7 +10,7 @@ export const codeDocumentHandler = createDocumentHandler<'code'>({ let draftContent = ''; const { fullStream } = streamObject({ - model: myProvider.languageModel('block-model'), + model: myProvider.languageModel('artifact-model'), system: codePrompt, prompt: title, schema: z.object({ @@ -42,7 +42,7 @@ export const codeDocumentHandler = createDocumentHandler<'code'>({ let draftContent = ''; const { fullStream } = streamObject({ - model: myProvider.languageModel('block-model'), + model: myProvider.languageModel('artifacts-model'), system: updateDocumentPrompt(document.content, 'code'), prompt: description, schema: z.object({ diff --git a/blocks/image/client.tsx b/artifacts/image/client.tsx similarity index 89% rename from blocks/image/client.tsx rename to artifacts/image/client.tsx index 96c82a9..a8f8827 100644 --- a/blocks/image/client.tsx +++ b/artifacts/image/client.tsx @@ -1,15 +1,15 @@ -import { Block } from '@/components/create-block'; +import { Artifact } from '@/components/create-artifact'; import { CopyIcon, RedoIcon, UndoIcon } from '@/components/icons'; import { ImageEditor } from '@/components/image-editor'; import { toast } from 'sonner'; -export const imageBlock = new Block({ +export const imageArtifact = new Artifact({ kind: 'image', description: 'Useful for image generation', - onStreamPart: ({ streamPart, setBlock }) => { + onStreamPart: ({ streamPart, setArtifact }) => { if (streamPart.type === 'image-delta') { - setBlock((draftBlock) => ({ - ...draftBlock, + setArtifact((draftArtifact) => ({ + ...draftArtifact, content: streamPart.content as string, isVisible: true, status: 'streaming', diff --git a/blocks/image/server.ts b/artifacts/image/server.ts similarity index 93% rename from blocks/image/server.ts rename to artifacts/image/server.ts index 81c3f7f..31e00ee 100644 --- a/blocks/image/server.ts +++ b/artifacts/image/server.ts @@ -1,5 +1,5 @@ import { myProvider } from '@/lib/ai/models'; -import { createDocumentHandler } from '@/lib/blocks/server'; +import { createDocumentHandler } from '@/lib/artifacts/server'; import { experimental_generateImage } from 'ai'; export const imageDocumentHandler = createDocumentHandler<'image'>({ diff --git a/blocks/sheet/client.tsx b/artifacts/sheet/client.tsx similarity index 90% rename from blocks/sheet/client.tsx rename to artifacts/sheet/client.tsx index 24e4873..c73b2ca 100644 --- a/blocks/sheet/client.tsx +++ b/artifacts/sheet/client.tsx @@ -1,4 +1,4 @@ -import { Block } from '@/components/create-block'; +import { Artifact } from '@/components/create-artifact'; import { CopyIcon, LineChartIcon, @@ -12,14 +12,14 @@ import { toast } from 'sonner'; type Metadata = any; -export const sheetBlock = new Block<'sheet', Metadata>({ +export const sheetArtifact = new Artifact<'sheet', Metadata>({ kind: 'sheet', description: 'Useful for working with spreadsheets', initialize: async () => {}, - onStreamPart: ({ setBlock, streamPart }) => { + onStreamPart: ({ setArtifact, streamPart }) => { if (streamPart.type === 'sheet-delta') { - setBlock((draftBlock) => ({ - ...draftBlock, + setArtifact((draftArtifact) => ({ + ...draftArtifact, content: streamPart.content as string, isVisible: true, status: 'streaming', @@ -107,7 +107,7 @@ export const sheetBlock = new Block<'sheet', Metadata>({ appendMessage({ role: 'user', content: - 'Can you please analyze and visualize the data by creating a new code block in python?', + 'Can you please analyze and visualize the data by creating a new code artifact in python?', }); }, }, diff --git a/blocks/sheet/server.ts b/artifacts/sheet/server.ts similarity index 90% rename from blocks/sheet/server.ts rename to artifacts/sheet/server.ts index d2de86e..f343e21 100644 --- a/blocks/sheet/server.ts +++ b/artifacts/sheet/server.ts @@ -1,6 +1,6 @@ import { myProvider } from '@/lib/ai/models'; import { sheetPrompt, updateDocumentPrompt } from '@/lib/ai/prompts'; -import { createDocumentHandler } from '@/lib/blocks/server'; +import { createDocumentHandler } from '@/lib/artifacts/server'; import { streamObject } from 'ai'; import { z } from 'zod'; @@ -10,7 +10,7 @@ export const sheetDocumentHandler = createDocumentHandler<'sheet'>({ let draftContent = ''; const { fullStream } = streamObject({ - model: myProvider.languageModel('block-model'), + model: myProvider.languageModel('artifact-model'), system: sheetPrompt, prompt: title, schema: z.object({ @@ -47,7 +47,7 @@ export const sheetDocumentHandler = createDocumentHandler<'sheet'>({ let draftContent = ''; const { fullStream } = streamObject({ - model: myProvider.languageModel('block-model'), + model: myProvider.languageModel('artifact-model'), system: updateDocumentPrompt(document.content, 'sheet'), prompt: description, schema: z.object({ diff --git a/blocks/text/client.tsx b/artifacts/text/client.tsx similarity index 85% rename from blocks/text/client.tsx rename to artifacts/text/client.tsx index 360bb51..b5f4170 100644 --- a/blocks/text/client.tsx +++ b/artifacts/text/client.tsx @@ -1,7 +1,7 @@ -import { Block } from '@/components/create-block'; +import { Artifact } from '@/components/create-artifact'; import { DiffView } from '@/components/diffview'; import { DocumentSkeleton } from '@/components/document-skeleton'; -import { Editor } from '@/components/editor'; +import { Editor } from '@/components/text-editor'; import { ClockRewind, CopyIcon, @@ -14,11 +14,11 @@ import { Suggestion } from '@/lib/db/schema'; import { toast } from 'sonner'; import { getSuggestions } from '../actions'; -interface TextBlockMetadata { +interface TextArtifactMetadata { suggestions: Array; } -export const textBlock = new Block<'text', TextBlockMetadata>({ +export const textArtifact = new Artifact<'text', TextArtifactMetadata>({ kind: 'text', description: 'Useful for text content, like drafting essays and emails.', initialize: async ({ documentId, setMetadata }) => { @@ -28,7 +28,7 @@ export const textBlock = new Block<'text', TextBlockMetadata>({ suggestions, }); }, - onStreamPart: ({ streamPart, setMetadata, setBlock }) => { + onStreamPart: ({ streamPart, setMetadata, setArtifact }) => { if (streamPart.type === 'suggestion') { setMetadata((metadata) => { return { @@ -41,16 +41,16 @@ export const textBlock = new Block<'text', TextBlockMetadata>({ } if (streamPart.type === 'text-delta') { - setBlock((draftBlock) => { + setArtifact((draftArtifact) => { return { - ...draftBlock, - content: draftBlock.content + (streamPart.content as string), + ...draftArtifact, + content: draftArtifact.content + (streamPart.content as string), isVisible: - draftBlock.status === 'streaming' && - draftBlock.content.length > 400 && - draftBlock.content.length < 450 + draftArtifact.status === 'streaming' && + draftArtifact.content.length > 400 && + draftArtifact.content.length < 450 ? true - : draftBlock.isVisible, + : draftArtifact.isVisible, status: 'streaming', }; }); @@ -68,7 +68,7 @@ export const textBlock = new Block<'text', TextBlockMetadata>({ metadata, }) => { if (isLoading) { - return ; + return ; } if (mode === 'diff') { diff --git a/blocks/text/server.ts b/artifacts/text/server.ts similarity index 90% rename from blocks/text/server.ts rename to artifacts/text/server.ts index 5618598..b35cb60 100644 --- a/blocks/text/server.ts +++ b/artifacts/text/server.ts @@ -1,6 +1,6 @@ import { smoothStream, streamText } from 'ai'; import { myProvider } from '@/lib/ai/models'; -import { createDocumentHandler } from '@/lib/blocks/server'; +import { createDocumentHandler } from '@/lib/artifacts/server'; import { updateDocumentPrompt } from '@/lib/ai/prompts'; export const textDocumentHandler = createDocumentHandler<'text'>({ @@ -9,7 +9,7 @@ export const textDocumentHandler = createDocumentHandler<'text'>({ let draftContent = ''; const { fullStream } = streamText({ - model: myProvider.languageModel('block-model'), + model: myProvider.languageModel('artifact-model'), system: 'Write about the given topic. Markdown is supported. Use headings wherever appropriate.', experimental_transform: smoothStream({ chunking: 'word' }), @@ -37,7 +37,7 @@ export const textDocumentHandler = createDocumentHandler<'text'>({ let draftContent = ''; const { fullStream } = streamText({ - model: myProvider.languageModel('block-model'), + model: myProvider.languageModel('artifact-model'), system: updateDocumentPrompt(document.content, 'text'), experimental_transform: smoothStream({ chunking: 'word' }), prompt: description, diff --git a/components/block-actions.tsx b/components/artifact-actions.tsx similarity index 64% rename from components/block-actions.tsx rename to components/artifact-actions.tsx index 47bf1c3..009cbed 100644 --- a/components/block-actions.tsx +++ b/components/artifact-actions.tsx @@ -1,13 +1,13 @@ import { Button } from './ui/button'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; -import { blockDefinitions, UIBlock } from './block'; +import { artifactDefinitions, UIArtifact } from './artifact'; import { Dispatch, memo, SetStateAction, useState } from 'react'; -import { BlockActionContext } from './create-block'; +import { ArtifactActionContext } from './create-artifact'; import { cn } from '@/lib/utils'; import { toast } from 'sonner'; -interface BlockActionsProps { - block: UIBlock; +interface ArtifactActionsProps { + artifact: UIArtifact; handleVersionChange: (type: 'next' | 'prev' | 'toggle' | 'latest') => void; currentVersionIndex: number; isCurrentVersion: boolean; @@ -16,27 +16,27 @@ interface BlockActionsProps { setMetadata: Dispatch>; } -function PureBlockActions({ - block, +function PureArtifactActions({ + artifact, handleVersionChange, currentVersionIndex, isCurrentVersion, mode, metadata, setMetadata, -}: BlockActionsProps) { +}: ArtifactActionsProps) { const [isLoading, setIsLoading] = useState(false); - const blockDefinition = blockDefinitions.find( - (definition) => definition.kind === block.kind, + const artifactDefinition = artifactDefinitions.find( + (definition) => definition.kind === artifact.kind, ); - if (!blockDefinition) { - throw new Error('Block definition not found!'); + if (!artifactDefinition) { + throw new Error('Artifact definition not found!'); } - const actionContext: BlockActionContext = { - content: block.content, + const actionContext: ArtifactActionContext = { + content: artifact.content, handleVersionChange, currentVersionIndex, isCurrentVersion, @@ -47,7 +47,7 @@ function PureBlockActions({ return (
- {blockDefinition.actions.map((action) => ( + {artifactDefinition.actions.map((action) => ( + ); +} + +export const ArtifactCloseButton = memo(PureArtifactCloseButton, () => true); diff --git a/components/block-messages.tsx b/components/artifact-messages.tsx similarity index 81% rename from components/block-messages.tsx rename to components/artifact-messages.tsx index 1e86879..de170f3 100644 --- a/components/block-messages.tsx +++ b/components/artifact-messages.tsx @@ -4,9 +4,9 @@ import { Vote } from '@/lib/db/schema'; import { ChatRequestOptions, Message } from 'ai'; import { memo } from 'react'; import equal from 'fast-deep-equal'; -import { UIBlock } from './block'; +import { UIArtifact } from './artifact'; -interface BlockMessagesProps { +interface ArtifactMessagesProps { chatId: string; isLoading: boolean; votes: Array | undefined; @@ -18,10 +18,10 @@ interface BlockMessagesProps { chatRequestOptions?: ChatRequestOptions, ) => Promise; isReadonly: boolean; - blockStatus: UIBlock['status']; + artifactStatus: UIArtifact['status']; } -function PureBlockMessages({ +function PureArtifactMessages({ chatId, isLoading, votes, @@ -29,7 +29,7 @@ function PureBlockMessages({ setMessages, reload, isReadonly, -}: BlockMessagesProps) { +}: ArtifactMessagesProps) { const [messagesContainerRef, messagesEndRef] = useScrollToBottom(); @@ -64,12 +64,12 @@ function PureBlockMessages({ } function areEqual( - prevProps: BlockMessagesProps, - nextProps: BlockMessagesProps, + prevProps: ArtifactMessagesProps, + nextProps: ArtifactMessagesProps, ) { if ( - prevProps.blockStatus === 'streaming' && - nextProps.blockStatus === 'streaming' + prevProps.artifactStatus === 'streaming' && + nextProps.artifactStatus === 'streaming' ) return true; @@ -81,4 +81,4 @@ function areEqual( return true; } -export const BlockMessages = memo(PureBlockMessages, areEqual); +export const ArtifactMessages = memo(PureArtifactMessages, areEqual); diff --git a/components/block.tsx b/components/artifact.tsx similarity index 82% rename from components/block.tsx rename to components/artifact.tsx index cf6298f..f031c03 100644 --- a/components/block.tsx +++ b/components/artifact.tsx @@ -21,24 +21,29 @@ import { fetcher } from '@/lib/utils'; import { MultimodalInput } from './multimodal-input'; import { Toolbar } from './toolbar'; import { VersionFooter } from './version-footer'; -import { BlockActions } from './block-actions'; -import { BlockCloseButton } from './block-close-button'; -import { BlockMessages } from './block-messages'; +import { ArtifactActions } from './artifact-actions'; +import { ArtifactCloseButton } from './artifact-close-button'; +import { ArtifactMessages } from './artifact-messages'; import { useSidebar } from './ui/sidebar'; -import { useBlock } from '@/hooks/use-block'; -import { imageBlock } from '@/blocks/image/client'; -import { codeBlock } from '@/blocks/code/client'; -import { sheetBlock } from '@/blocks/sheet/client'; -import { textBlock } from '@/blocks/text/client'; +import { useArtifact } from '@/hooks/use-artifact'; +import { imageArtifact } from '@/artifacts/image/client'; +import { codeArtifact } from '@/artifacts/code/client'; +import { sheetArtifact } from '@/artifacts/sheet/client'; +import { textArtifact } from '@/artifacts/text/client'; import equal from 'fast-deep-equal'; -export const blockDefinitions = [textBlock, codeBlock, imageBlock, sheetBlock]; -export type BlockKind = (typeof blockDefinitions)[number]['kind']; +export const artifactDefinitions = [ + textArtifact, + codeArtifact, + imageArtifact, + sheetArtifact, +]; +export type ArtifactKind = (typeof artifactDefinitions)[number]['kind']; -export interface UIBlock { +export interface UIArtifact { title: string; documentId: string; - kind: BlockKind; + kind: ArtifactKind; content: string; isVisible: boolean; status: 'streaming' | 'idle'; @@ -50,7 +55,7 @@ export interface UIBlock { }; } -function PureBlock({ +function PureArtifact({ chatId, input, setInput, @@ -91,15 +96,15 @@ function PureBlock({ ) => Promise; isReadonly: boolean; }) { - const { block, setBlock, metadata, setMetadata } = useBlock(); + const { artifact, setArtifact, metadata, setMetadata } = useArtifact(); const { data: documents, isLoading: isDocumentsFetching, mutate: mutateDocuments, } = useSWR>( - block.documentId !== 'init' && block.status !== 'streaming' - ? `/api/document?id=${block.documentId}` + artifact.documentId !== 'init' && artifact.status !== 'streaming' + ? `/api/document?id=${artifact.documentId}` : null, fetcher, ); @@ -117,27 +122,27 @@ function PureBlock({ if (mostRecentDocument) { setDocument(mostRecentDocument); setCurrentVersionIndex(documents.length - 1); - setBlock((currentBlock) => ({ - ...currentBlock, + setArtifact((currentArtifact) => ({ + ...currentArtifact, content: mostRecentDocument.content ?? '', })); } } - }, [documents, setBlock]); + }, [documents, setArtifact]); useEffect(() => { mutateDocuments(); - }, [block.status, mutateDocuments]); + }, [artifact.status, mutateDocuments]); const { mutate } = useSWRConfig(); const [isContentDirty, setIsContentDirty] = useState(false); const handleContentChange = useCallback( (updatedContent: string) => { - if (!block) return; + if (!artifact) return; mutate>( - `/api/document?id=${block.documentId}`, + `/api/document?id=${artifact.documentId}`, async (currentDocuments) => { if (!currentDocuments) return undefined; @@ -149,12 +154,12 @@ function PureBlock({ } if (currentDocument.content !== updatedContent) { - await fetch(`/api/document?id=${block.documentId}`, { + await fetch(`/api/document?id=${artifact.documentId}`, { method: 'POST', body: JSON.stringify({ - title: block.title, + title: artifact.title, content: updatedContent, - kind: block.kind, + kind: artifact.kind, }), }); @@ -173,7 +178,7 @@ function PureBlock({ { revalidate: false }, ); }, - [block, mutate], + [artifact, mutate], ); const debouncedHandleContentChange = useDebounceCallback( @@ -241,28 +246,28 @@ function PureBlock({ const { width: windowWidth, height: windowHeight } = useWindowSize(); const isMobile = windowWidth ? windowWidth < 768 : false; - const blockDefinition = blockDefinitions.find( - (definition) => definition.kind === block.kind, + const artifactDefinition = artifactDefinitions.find( + (definition) => definition.kind === artifact.kind, ); - if (!blockDefinition) { - throw new Error('Block definition not found!'); + if (!artifactDefinition) { + throw new Error('Artifact definition not found!'); } useEffect(() => { - if (block.documentId !== 'init') { - if (blockDefinition.initialize) { - blockDefinition.initialize({ - documentId: block.documentId, + if (artifact.documentId !== 'init') { + if (artifactDefinition.initialize) { + artifactDefinition.initialize({ + documentId: artifact.documentId, setMetadata, }); } } - }, [block.documentId, blockDefinition, setMetadata]); + }, [artifact.documentId, artifactDefinition, setMetadata]); return ( - {block.isVisible && ( + {artifact.isVisible && (
-
@@ -355,18 +360,18 @@ function PureBlock({ isMobile ? { opacity: 1, - x: block.boundingBox.left, - y: block.boundingBox.top, - height: block.boundingBox.height, - width: block.boundingBox.width, + x: artifact.boundingBox.left, + y: artifact.boundingBox.top, + height: artifact.boundingBox.height, + width: artifact.boundingBox.width, borderRadius: 50, } : { opacity: 1, - x: block.boundingBox.left, - y: block.boundingBox.top, - height: block.boundingBox.height, - width: block.boundingBox.width, + x: artifact.boundingBox.left, + y: artifact.boundingBox.top, + height: artifact.boundingBox.height, + width: artifact.boundingBox.width, borderRadius: 50, } } @@ -418,10 +423,10 @@ function PureBlock({ >
- +
-
{block.title}
+
{artifact.title}
{isContentDirty ? (
@@ -443,8 +448,8 @@ function PureBlock({
-
- @@ -484,7 +489,7 @@ function PureBlock({ isLoading={isLoading} stop={stop} setMessages={setMessages} - blockKind={block.kind} + artifactKind={artifact.kind} /> )} @@ -506,7 +511,7 @@ function PureBlock({ ); } -export const Block = memo(PureBlock, (prevProps, nextProps) => { +export const Artifact = memo(PureArtifact, (prevProps, nextProps) => { if (prevProps.isLoading !== nextProps.isLoading) return false; if (!equal(prevProps.votes, nextProps.votes)) return false; if (prevProps.input !== nextProps.input) return false; diff --git a/components/block-close-button.tsx b/components/block-close-button.tsx deleted file mode 100644 index 2d8c820..0000000 --- a/components/block-close-button.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { memo } from 'react'; -import { CrossIcon } from './icons'; -import { Button } from './ui/button'; -import { initialBlockData, useBlock } from '@/hooks/use-block'; - -function PureBlockCloseButton() { - const { setBlock } = useBlock(); - - return ( - - ); -} - -export const BlockCloseButton = memo(PureBlockCloseButton, () => true); diff --git a/components/chat.tsx b/components/chat.tsx index 6911e66..412ecf1 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -9,11 +9,11 @@ import { ChatHeader } from '@/components/chat-header'; import type { Vote } from '@/lib/db/schema'; import { fetcher, generateUUID } from '@/lib/utils'; -import { Block } from './block'; +import { Artifact } from './artifact'; import { MultimodalInput } from './multimodal-input'; import { Messages } from './messages'; import { VisibilityType } from './visibility-selector'; -import { useBlockSelector } from '@/hooks/use-block'; +import { useArtifactSelector } from '@/hooks/use-artifact'; import { toast } from 'sonner'; export function Chat({ @@ -62,7 +62,7 @@ export function Chat({ ); const [attachments, setAttachments] = useState>([]); - const isBlockVisible = useBlockSelector((state) => state.isVisible); + const isArtifactVisible = useArtifactSelector((state) => state.isVisible); return ( <> @@ -82,7 +82,7 @@ export function Chat({ setMessages={setMessages} reload={reload} isReadonly={isReadonly} - isBlockVisible={isBlockVisible} + isArtifactVisible={isArtifactVisible} /> @@ -104,7 +104,7 @@ export function Chat({
- (null); - const [pyodide, setPyodide] = useState(null); - const match = /language-(\w+)/.exec(className || ''); - const isPython = match && match[1] === 'python'; - const codeContent = String(children).replace(/\n$/, ''); - const [tab, setTab] = useState<'code' | 'run'>('code'); - if (!inline) { return (
- {tab === 'code' && ( -
-            {children}
-          
- )} - - {tab === 'run' && output && ( -
- {output} -
- )} +
+          {children}
+        
); } else { diff --git a/components/console.tsx b/components/console.tsx index 64f9baf..f4a06e9 100644 --- a/components/console.tsx +++ b/components/console.tsx @@ -9,7 +9,7 @@ import { useState, } from 'react'; import { cn } from '@/lib/utils'; -import { useBlockSelector } from '@/hooks/use-block'; +import { useArtifactSelector } from '@/hooks/use-artifact'; export interface ConsoleOutputContent { type: 'text' | 'image'; @@ -32,7 +32,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) { const [isResizing, setIsResizing] = useState(false); const consoleEndRef = useRef(null); - const isBlockVisible = useBlockSelector((state) => state.isVisible); + const isArtifactVisible = useArtifactSelector((state) => state.isVisible); const minHeight = 100; const maxHeight = 800; @@ -71,10 +71,10 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) { }, [consoleOutputs]); useEffect(() => { - if (!isBlockVisible) { + if (!isArtifactVisible) { setConsoleOutputs([]); } - }, [isBlockVisible, setConsoleOutputs]); + }, [isArtifactVisible, setConsoleOutputs]); return consoleOutputs.length > 0 ? ( <> diff --git a/components/create-block.tsx b/components/create-artifact.tsx similarity index 67% rename from components/create-block.tsx rename to components/create-artifact.tsx index b0e7d79..fdc3f6d 100644 --- a/components/create-block.tsx +++ b/components/create-artifact.tsx @@ -2,9 +2,9 @@ import { Suggestion } from '@/lib/db/schema'; import { UseChatHelpers } from 'ai/react'; import { ComponentType, Dispatch, ReactNode, SetStateAction } from 'react'; import { DataStreamDelta } from './data-stream-handler'; -import { UIBlock } from './block'; +import { UIArtifact } from './artifact'; -export type BlockActionContext = { +export type ArtifactActionContext = { content: string; handleVersionChange: (type: 'next' | 'prev' | 'toggle' | 'latest') => void; currentVersionIndex: number; @@ -14,25 +14,25 @@ export type BlockActionContext = { setMetadata: Dispatch>; }; -type BlockAction = { +type ArtifactAction = { icon: ReactNode; label?: string; description: string; - onClick: (context: BlockActionContext) => Promise | void; - isDisabled?: (context: BlockActionContext) => boolean; + onClick: (context: ArtifactActionContext) => Promise | void; + isDisabled?: (context: ArtifactActionContext) => boolean; }; -export type BlockToolbarContext = { +export type ArtifactToolbarContext = { appendMessage: UseChatHelpers['append']; }; -export type BlockToolbarItem = { +export type ArtifactToolbarItem = { description: string; icon: ReactNode; - onClick: (context: BlockToolbarContext) => void; + onClick: (context: ArtifactToolbarContext) => void; }; -interface BlockContent { +interface ArtifactContent { title: string; content: string; mode: 'edit' | 'diff'; @@ -53,34 +53,34 @@ interface InitializeParameters { setMetadata: Dispatch>; } -type BlockConfig = { +type ArtifactConfig = { kind: T; description: string; - content: ComponentType>; - actions: Array>; - toolbar: BlockToolbarItem[]; + content: ComponentType>; + actions: Array>; + toolbar: ArtifactToolbarItem[]; initialize?: (parameters: InitializeParameters) => void; onStreamPart: (args: { setMetadata: Dispatch>; - setBlock: Dispatch>; + setArtifact: Dispatch>; streamPart: DataStreamDelta; }) => void; }; -export class Block { +export class Artifact { readonly kind: T; readonly description: string; - readonly content: ComponentType>; - readonly actions: Array>; - readonly toolbar: BlockToolbarItem[]; + readonly content: ComponentType>; + readonly actions: Array>; + readonly toolbar: ArtifactToolbarItem[]; readonly initialize?: (parameters: InitializeParameters) => void; readonly onStreamPart: (args: { setMetadata: Dispatch>; - setBlock: Dispatch>; + setArtifact: Dispatch>; streamPart: DataStreamDelta; }) => void; - constructor(config: BlockConfig) { + constructor(config: ArtifactConfig) { this.kind = config.kind; this.description = config.description; this.content = config.content; diff --git a/components/data-stream-handler.tsx b/components/data-stream-handler.tsx index d46c84d..1ae6b46 100644 --- a/components/data-stream-handler.tsx +++ b/components/data-stream-handler.tsx @@ -2,9 +2,9 @@ import { useChat } from 'ai/react'; import { useEffect, useRef } from 'react'; -import { blockDefinitions, BlockKind } from './block'; +import { artifactDefinitions, ArtifactKind } from './artifact'; import { Suggestion } from '@/lib/db/schema'; -import { initialBlockData, useBlock } from '@/hooks/use-block'; +import { initialArtifactData, useArtifact } from '@/hooks/use-artifact'; export type DataStreamDelta = { type: @@ -23,7 +23,7 @@ export type DataStreamDelta = { export function DataStreamHandler({ id }: { id: string }) { const { data: dataStream } = useChat({ id }); - const { block, setBlock, setMetadata } = useBlock(); + const { artifact, setArtifact, setMetadata } = useArtifact(); const lastProcessedIndex = useRef(-1); useEffect(() => { @@ -33,64 +33,64 @@ export function DataStreamHandler({ id }: { id: string }) { lastProcessedIndex.current = dataStream.length - 1; (newDeltas as DataStreamDelta[]).forEach((delta: DataStreamDelta) => { - const blockDefinition = blockDefinitions.find( - (blockDefinition) => blockDefinition.kind === block.kind, + const artifactDefinition = artifactDefinitions.find( + (artifactDefinition) => artifactDefinition.kind === artifact.kind, ); - if (blockDefinition?.onStreamPart) { - blockDefinition.onStreamPart({ + if (artifactDefinition?.onStreamPart) { + artifactDefinition.onStreamPart({ streamPart: delta, - setBlock, + setArtifact, setMetadata, }); } - setBlock((draftBlock) => { - if (!draftBlock) { - return { ...initialBlockData, status: 'streaming' }; + setArtifact((draftArtifact) => { + if (!draftArtifact) { + return { ...initialArtifactData, status: 'streaming' }; } switch (delta.type) { case 'id': return { - ...draftBlock, + ...draftArtifact, documentId: delta.content as string, status: 'streaming', }; case 'title': return { - ...draftBlock, + ...draftArtifact, title: delta.content as string, status: 'streaming', }; case 'kind': return { - ...draftBlock, - kind: delta.content as BlockKind, + ...draftArtifact, + kind: delta.content as ArtifactKind, status: 'streaming', }; case 'clear': return { - ...draftBlock, + ...draftArtifact, content: '', status: 'streaming', }; case 'finish': return { - ...draftBlock, + ...draftArtifact, status: 'idle', }; default: - return draftBlock; + return draftArtifact; } }); }); - }, [dataStream, setBlock, setMetadata, block]); + }, [dataStream, setArtifact, setMetadata, artifact]); return null; } diff --git a/components/document-preview.tsx b/components/document-preview.tsx index cc95012..6f28190 100644 --- a/components/document-preview.tsx +++ b/components/document-preview.tsx @@ -8,16 +8,16 @@ import { useMemo, useRef, } from 'react'; -import { BlockKind, UIBlock } from './block'; +import { ArtifactKind, UIArtifact } from './artifact'; import { FileIcon, FullscreenIcon, ImageIcon, LoaderIcon } from './icons'; import { cn, fetcher } from '@/lib/utils'; import { Document } from '@/lib/db/schema'; import { InlineDocumentSkeleton } from './document-skeleton'; import useSWR from 'swr'; -import { Editor } from './editor'; +import { Editor } from './text-editor'; import { DocumentToolCall, DocumentToolResult } from './document'; import { CodeEditor } from './code-editor'; -import { useBlock } from '@/hooks/use-block'; +import { useArtifact } from '@/hooks/use-artifact'; import equal from 'fast-deep-equal'; import { SpreadsheetEditor } from './sheet-editor'; import { ImageEditor } from './image-editor'; @@ -33,7 +33,7 @@ export function DocumentPreview({ result, args, }: DocumentPreviewProps) { - const { block, setBlock } = useBlock(); + const { artifact, setArtifact } = useArtifact(); const { data: documents, isLoading: isDocumentsFetching } = useSWR< Array @@ -45,9 +45,9 @@ export function DocumentPreview({ useEffect(() => { const boundingBox = hitboxRef.current?.getBoundingClientRect(); - if (block.documentId && boundingBox) { - setBlock((block) => ({ - ...block, + if (artifact.documentId && boundingBox) { + setArtifact((artifact) => ({ + ...artifact, boundingBox: { left: boundingBox.x, top: boundingBox.y, @@ -56,9 +56,9 @@ export function DocumentPreview({ }, })); } - }, [block.documentId, setBlock]); + }, [artifact.documentId, setArtifact]); - if (block.isVisible) { + if (artifact.isVisible) { if (result) { return ( ; + return ; } const document: Document | null = previewDocument ? previewDocument - : block.status === 'streaming' + : artifact.status === 'streaming' ? { - title: block.title, - kind: block.kind, - content: block.content, - id: block.documentId, + title: artifact.title, + kind: artifact.kind, + content: artifact.content, + id: artifact.documentId, createdAt: new Date(), userId: 'noop', } : null; - if (!document) return ; + if (!document) return ; return (
- +
); } -const LoadingSkeleton = ({ blockKind }: { blockKind: BlockKind }) => ( +const LoadingSkeleton = ({ artifactKind }: { artifactKind: ArtifactKind }) => (
@@ -125,7 +129,7 @@ const LoadingSkeleton = ({ blockKind }: { blockKind: BlockKind }) => (
- {blockKind === 'image' ? ( + {artifactKind === 'image' ? (
@@ -140,21 +144,23 @@ const LoadingSkeleton = ({ blockKind }: { blockKind: BlockKind }) => ( const PureHitboxLayer = ({ hitboxRef, result, - setBlock, + setArtifact, }: { hitboxRef: React.RefObject; result: any; - setBlock: (updaterFn: UIBlock | ((currentBlock: UIBlock) => UIBlock)) => void; + setArtifact: ( + updaterFn: UIArtifact | ((currentArtifact: UIArtifact) => UIArtifact), + ) => void; }) => { const handleClick = useCallback( (event: MouseEvent) => { const boundingBox = event.currentTarget.getBoundingClientRect(); - setBlock((block) => - block.status === 'streaming' - ? { ...block, isVisible: true } + setArtifact((artifact) => + artifact.status === 'streaming' + ? { ...artifact, isVisible: true } : { - ...block, + ...artifact, title: result.title, documentId: result.id, kind: result.kind, @@ -168,7 +174,7 @@ const PureHitboxLayer = ({ }, ); }, - [setBlock, result], + [setArtifact, result], ); return ( @@ -199,7 +205,7 @@ const PureDocumentHeader = ({ isStreaming, }: { title: string; - kind: BlockKind; + kind: ArtifactKind; isStreaming: boolean; }) => (
@@ -229,7 +235,7 @@ const DocumentHeader = memo(PureDocumentHeader, (prevProps, nextProps) => { }); const DocumentContent = ({ document }: { document: Document }) => { - const { block } = useBlock(); + const { artifact } = useArtifact(); const containerClassName = cn( 'h-[257px] overflow-y-scroll border rounded-b-2xl dark:bg-muted border-t-0 dark:border-zinc-700', @@ -243,7 +249,7 @@ const DocumentContent = ({ document }: { document: Document }) => { content: document.content ?? '', isCurrentVersion: true, currentVersionIndex: 0, - status: block.status, + status: artifact.status, saveContent: () => {}, suggestions: [], }; @@ -270,7 +276,7 @@ const DocumentContent = ({ document }: { document: Document }) => { content={document.content ?? ''} isCurrentVersion={true} currentVersionIndex={0} - status={block.status} + status={artifact.status} isInline={true} /> ) : null} diff --git a/components/document-skeleton.tsx b/components/document-skeleton.tsx index ae8ace9..5a9a9f7 100644 --- a/components/document-skeleton.tsx +++ b/components/document-skeleton.tsx @@ -1,9 +1,13 @@ 'use client'; -import { BlockKind } from './block'; +import { ArtifactKind } from './artifact'; -export const DocumentSkeleton = ({ blockKind }: { blockKind: BlockKind }) => { - return blockKind === 'image' ? ( +export const DocumentSkeleton = ({ + artifactKind, +}: { + artifactKind: ArtifactKind; +}) => { + return artifactKind === 'image' ? (
diff --git a/components/document.tsx b/components/document.tsx index d3ee71a..9ebc1de 100644 --- a/components/document.tsx +++ b/components/document.tsx @@ -1,9 +1,9 @@ import { memo } from 'react'; -import type { BlockKind } from './block'; +import type { ArtifactKind } from './artifact'; import { FileIcon, LoaderIcon, MessageIcon, PencilEditIcon } from './icons'; import { toast } from 'sonner'; -import { useBlock } from '@/hooks/use-block'; +import { useArtifact } from '@/hooks/use-artifact'; const getActionText = ( type: 'create' | 'update' | 'request-suggestions', @@ -25,7 +25,7 @@ const getActionText = ( interface DocumentToolResultProps { type: 'create' | 'update' | 'request-suggestions'; - result: { id: string; title: string; kind: BlockKind }; + result: { id: string; title: string; kind: ArtifactKind }; isReadonly: boolean; } @@ -34,7 +34,7 @@ function PureDocumentToolResult({ result, isReadonly, }: DocumentToolResultProps) { - const { setBlock } = useBlock(); + const { setArtifact } = useArtifact(); return (