Rename to blocks (#502)

This commit is contained in:
Jeremy 2024-11-07 02:40:29 +03:00 committed by GitHub
parent 22a09de6e6
commit 729634e1d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 108 additions and 111 deletions

View file

@ -21,8 +21,8 @@ export const models: Array<Model> = [
description: 'For complex, multi-step tasks',
},
{
id: 'gpt-4o-canvas',
label: 'GPT 4o with Canvas',
id: 'gpt-4o-blocks',
label: 'GPT 4o with Blocks',
apiIdentifier: 'gpt-4o',
description: 'Collaborate with writing',
},

View file

@ -1,7 +1,7 @@
export const canvasPrompt = `
Canvas is a special user interface mode that helps users with writing, editing, and other content creation tasks. When canvas is open, it is on the right side of the screen, while the conversation is on the left side. When creating or updating documents, changes are reflected in real-time on the canvas and visible to the user.
export const blocksPrompt = `
Blocks is a special user interface mode that helps users with writing, editing, and other content creation tasks. When block is open, it is on the right side of the screen, while the conversation is on the left side. When creating or updating documents, changes are reflected in real-time on the blocks and visible to the user.
This is a guide for using canvas tools: \`createDocument\` and \`updateDocument\`, which render content on a canvas beside the conversation.
This is a guide for using blocks tools: \`createDocument\` and \`updateDocument\`, which render content on a blocks beside the conversation.
**When to use \`createDocument\`:**
- For substantial content (>10 lines)

View file

@ -9,7 +9,7 @@ import { z } from 'zod';
import { customModel } from '@/ai';
import { models } from '@/ai/models';
import { canvasPrompt, regularPrompt } from '@/ai/prompts';
import { blocksPrompt, regularPrompt } from '@/ai/prompts';
import { auth } from '@/app/(auth)/auth';
import {
deleteChatById,
@ -37,7 +37,7 @@ type AllowedTools =
| 'requestSuggestions'
| 'getWeather';
const canvasTools: AllowedTools[] = [
const blocksTools: AllowedTools[] = [
'createDocument',
'updateDocument',
'requestSuggestions',
@ -89,11 +89,11 @@ export async function POST(request: Request) {
const result = await streamText({
model: customModel(model.apiIdentifier),
system: modelId === 'gpt-4o-canvas' ? canvasPrompt : regularPrompt,
system: modelId === 'gpt-4o-blocks' ? blocksPrompt : regularPrompt,
messages: coreMessages,
maxSteps: 5,
experimental_activeTools:
modelId === 'gpt-4o-canvas' ? canvasTools : weatherTools,
modelId === 'gpt-4o-blocks' ? blocksTools : weatherTools,
tools: {
getWeather: {
description: 'Get the current weather at a location',

View file

@ -1,29 +1,29 @@
import { JSONValue } from 'ai';
import { Dispatch, memo, SetStateAction } from 'react';
import { UICanvas } from './canvas';
import { useCanvasStream } from './use-canvas-stream';
import { UIBlock } from './block';
import { useBlockStream } from './use-block-stream';
interface CanvasStreamHandlerProps {
setCanvas: Dispatch<SetStateAction<UICanvas>>;
interface BlockStreamHandlerProps {
setBlock: Dispatch<SetStateAction<UIBlock>>;
streamingData: JSONValue[] | undefined;
}
export function PureCanvasStreamHandler({
setCanvas,
export function PureBlockStreamHandler({
setBlock,
streamingData,
}: CanvasStreamHandlerProps) {
useCanvasStream({
}: BlockStreamHandlerProps) {
useBlockStream({
streamingData,
setCanvas,
setBlock,
});
return null;
}
function areEqual(
prevProps: CanvasStreamHandlerProps,
nextProps: CanvasStreamHandlerProps
prevProps: BlockStreamHandlerProps,
nextProps: BlockStreamHandlerProps
) {
if (!prevProps.streamingData && !nextProps.streamingData) {
return true;
@ -40,4 +40,4 @@ function areEqual(
return true;
}
export const CanvasStreamHandler = memo(PureCanvasStreamHandler, areEqual);
export const BlockStreamHandler = memo(PureBlockStreamHandler, areEqual);

View file

@ -31,7 +31,7 @@ import { useScrollToBottom } from './use-scroll-to-bottom';
import { VersionFooter } from './version-footer';
import { Button } from '../ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
export interface UICanvas {
export interface UIBlock {
title: string;
documentId: string;
content: string;
@ -45,7 +45,7 @@ export interface UICanvas {
};
}
export function Canvas({
export function Block({
chatId,
input,
setInput,
@ -55,8 +55,8 @@ export function Canvas({
attachments,
setAttachments,
append,
canvas,
setCanvas,
block,
setBlock,
messages,
setMessages,
votes,
@ -68,8 +68,8 @@ export function Canvas({
stop: () => void;
attachments: Array<Attachment>;
setAttachments: Dispatch<SetStateAction<Array<Attachment>>>;
canvas: UICanvas;
setCanvas: Dispatch<SetStateAction<UICanvas>>;
block: UIBlock;
setBlock: Dispatch<SetStateAction<UIBlock>>;
messages: Array<Message>;
setMessages: Dispatch<SetStateAction<Array<Message>>>;
votes: Array<Vote> | undefined;
@ -92,15 +92,15 @@ export function Canvas({
isLoading: isDocumentsFetching,
mutate: mutateDocuments,
} = useSWR<Array<Document>>(
canvas && canvas.status !== 'streaming'
? `/api/document?id=${canvas.documentId}`
block && block.status !== 'streaming'
? `/api/document?id=${block.documentId}`
: null,
fetcher
);
const { data: suggestions } = useSWR<Array<Suggestion>>(
documents && canvas && canvas.status !== 'streaming'
? `/api/suggestions?documentId=${canvas.documentId}`
documents && block && block.status !== 'streaming'
? `/api/suggestions?documentId=${block.documentId}`
: null,
fetcher,
{
@ -119,27 +119,27 @@ export function Canvas({
if (mostRecentDocument) {
setDocument(mostRecentDocument);
setCurrentVersionIndex(documents.length - 1);
setCanvas((currentCanvas) => ({
...currentCanvas,
setBlock((currentBlock) => ({
...currentBlock,
content: mostRecentDocument.content ?? '',
}));
}
}
}, [documents, setCanvas]);
}, [documents, setBlock]);
useEffect(() => {
mutateDocuments();
}, [canvas.status, mutateDocuments]);
}, [block.status, mutateDocuments]);
const { mutate } = useSWRConfig();
const [isContentDirty, setIsContentDirty] = useState(false);
const handleContentChange = useCallback(
(updatedContent: string) => {
if (!canvas) return;
if (!block) return;
mutate<Array<Document>>(
`/api/document?id=${canvas.documentId}`,
`/api/document?id=${block.documentId}`,
async (currentDocuments) => {
if (!currentDocuments) return undefined;
@ -151,10 +151,10 @@ export function Canvas({
}
if (currentDocument.content !== updatedContent) {
await fetch(`/api/document?id=${canvas.documentId}`, {
await fetch(`/api/document?id=${block.documentId}`, {
method: 'POST',
body: JSON.stringify({
title: canvas.title,
title: block.title,
content: updatedContent,
}),
});
@ -175,7 +175,7 @@ export function Canvas({
{ revalidate: false }
);
},
[canvas, mutate]
[block, mutate]
);
const debouncedHandleContentChange = useDebounceCallback(
@ -295,8 +295,8 @@ export function Canvas({
chatId={chatId}
key={message.id}
message={message}
canvas={canvas}
setCanvas={setCanvas}
block={block}
setBlock={setBlock}
isLoading={isLoading && index === messages.length - 1}
vote={
votes
@ -346,10 +346,10 @@ export function Canvas({
}
: {
opacity: 0,
x: canvas.boundingBox.left,
y: canvas.boundingBox.top,
height: canvas.boundingBox.height,
width: canvas.boundingBox.width,
x: block.boundingBox.left,
y: block.boundingBox.top,
height: block.boundingBox.height,
width: block.boundingBox.width,
borderRadius: 50,
}
}
@ -401,8 +401,8 @@ export function Canvas({
variant="outline"
className="h-fit p-2 dark:hover:bg-zinc-700"
onClick={() => {
setCanvas((currentCanvas) => ({
...currentCanvas,
setBlock((currentBlock) => ({
...currentBlock,
isVisible: false,
}));
}}
@ -412,7 +412,7 @@ export function Canvas({
<div className="flex flex-col">
<div className="font-medium">
{document?.title ?? canvas.title}
{document?.title ?? block.title}
</div>
{isContentDirty ? (
@ -442,10 +442,10 @@ export function Canvas({
variant="outline"
className="p-2 h-fit dark:hover:bg-zinc-700"
onClick={() => {
copyToClipboard(canvas.content);
copyToClipboard(block.content);
toast.success('Copied to clipboard!');
}}
disabled={canvas.status === 'streaming'}
disabled={block.status === 'streaming'}
>
<CopyIcon size={18} />
</Button>
@ -461,7 +461,7 @@ export function Canvas({
handleVersionChange('prev');
}}
disabled={
currentVersionIndex === 0 || canvas.status === 'streaming'
currentVersionIndex === 0 || block.status === 'streaming'
}
>
<UndoIcon size={18} />
@ -477,7 +477,7 @@ export function Canvas({
onClick={() => {
handleVersionChange('next');
}}
disabled={isCurrentVersion || canvas.status === 'streaming'}
disabled={isCurrentVersion || block.status === 'streaming'}
>
<RedoIcon size={18} />
</Button>
@ -498,7 +498,7 @@ export function Canvas({
handleVersionChange('toggle');
}}
disabled={
canvas.status === 'streaming' || currentVersionIndex === 0
block.status === 'streaming' || currentVersionIndex === 0
}
>
<DeltaIcon size={18} />
@ -511,18 +511,18 @@ export function Canvas({
<div className="prose dark:prose-invert dark:bg-muted bg-background h-full overflow-y-scroll px-4 py-8 md:p-20 !max-w-full pb-40 items-center">
<div className="flex flex-row max-w-[600px] mx-auto">
{isDocumentsFetching && !canvas.content ? (
{isDocumentsFetching && !block.content ? (
<DocumentSkeleton />
) : mode === 'edit' ? (
<Editor
content={
isCurrentVersion
? canvas.content
? block.content
: getDocumentContentById(currentVersionIndex)
}
isCurrentVersion={isCurrentVersion}
currentVersionIndex={currentVersionIndex}
status={canvas.status}
status={block.status}
saveContent={saveContent}
suggestions={isCurrentVersion ? (suggestions ?? []) : []}
/>
@ -555,7 +555,7 @@ export function Canvas({
<AnimatePresence>
{!isCurrentVersion && (
<VersionFooter
canvas={canvas}
block={block}
currentVersionIndex={currentVersionIndex}
documents={documents}
handleVersionChange={handleVersionChange}

View file

@ -13,8 +13,8 @@ import { useScrollToBottom } from '@/components/custom/use-scroll-to-bottom';
import { Vote } from '@/db/schema';
import { fetcher } from '@/lib/utils';
import { Canvas, UICanvas } from './canvas';
import { CanvasStreamHandler } from './canvas-stream-handler';
import { Block, UIBlock } from './block';
import { BlockStreamHandler } from './block-stream-handler';
import { MultimodalInput } from './multimodal-input';
import { Overview } from './overview';
@ -50,7 +50,7 @@ export function Chat({
const { width: windowWidth = 1920, height: windowHeight = 1080 } =
useWindowSize();
const [canvas, setCanvas] = useState<UICanvas>({
const [block, setBlock] = useState<UIBlock>({
documentId: 'init',
content: '',
title: '',
@ -89,8 +89,8 @@ export function Chat({
key={message.id}
chatId={id}
message={message}
canvas={canvas}
setCanvas={setCanvas}
block={block}
setBlock={setBlock}
isLoading={isLoading && messages.length - 1 === index}
vote={
votes
@ -129,8 +129,8 @@ export function Chat({
</div>
<AnimatePresence>
{canvas && canvas.isVisible && (
<Canvas
{block && block.isVisible && (
<Block
chatId={id}
input={input}
setInput={setInput}
@ -140,8 +140,8 @@ export function Chat({
attachments={attachments}
setAttachments={setAttachments}
append={append}
canvas={canvas}
setCanvas={setCanvas}
block={block}
setBlock={setBlock}
messages={messages}
setMessages={setMessages}
votes={votes}
@ -149,10 +149,7 @@ export function Chat({
)}
</AnimatePresence>
<CanvasStreamHandler
streamingData={streamingData}
setCanvas={setCanvas}
/>
<BlockStreamHandler streamingData={streamingData} setBlock={setBlock} />
</>
);
}

View file

@ -1,6 +1,6 @@
import { SetStateAction } from 'react';
import { UICanvas } from './canvas';
import { UIBlock } from './block';
import { FileIcon, LoaderIcon, MessageIcon, PencilEditIcon } from './icons';
const getActionText = (type: 'create' | 'update' | 'request-suggestions') => {
@ -19,15 +19,15 @@ const getActionText = (type: 'create' | 'update' | 'request-suggestions') => {
interface DocumentToolResultProps {
type: 'create' | 'update' | 'request-suggestions';
result: any;
canvas: UICanvas;
setCanvas: (value: SetStateAction<UICanvas>) => void;
block: UIBlock;
setBlock: (value: SetStateAction<UIBlock>) => void;
}
export function DocumentToolResult({
type,
result,
canvas,
setCanvas,
block,
setBlock,
}: DocumentToolResultProps) {
return (
<div
@ -42,7 +42,7 @@ export function DocumentToolResult({
height: rect.height,
};
setCanvas({
setBlock({
documentId: result.id,
content: '',
title: result.title,

View file

@ -7,7 +7,7 @@ import { Dispatch, SetStateAction } from 'react';
import { Vote } from '@/db/schema';
import { UICanvas } from './canvas';
import { UIBlock } from './block';
import { DocumentToolCall, DocumentToolResult } from './document';
import { SparklesIcon } from './icons';
import { Markdown } from './markdown';
@ -18,15 +18,15 @@ import { Weather } from './weather';
export const PreviewMessage = ({
chatId,
message,
canvas,
setCanvas,
block,
setBlock,
vote,
isLoading,
}: {
chatId: string;
message: Message;
canvas: UICanvas;
setCanvas: Dispatch<SetStateAction<UICanvas>>;
block: UIBlock;
setBlock: Dispatch<SetStateAction<UIBlock>>;
vote: Vote | undefined;
isLoading: boolean;
}) => {
@ -71,22 +71,22 @@ export const PreviewMessage = ({
<DocumentToolResult
type="create"
result={result}
canvas={canvas}
setCanvas={setCanvas}
block={block}
setBlock={setBlock}
/>
) : toolName === 'updateDocument' ? (
<DocumentToolResult
type="update"
result={result}
canvas={canvas}
setCanvas={setCanvas}
block={block}
setBlock={setBlock}
/>
) : toolName === 'requestSuggestions' ? (
<DocumentToolResult
type="request-suggestions"
result={result}
canvas={canvas}
setCanvas={setCanvas}
block={block}
setBlock={setBlock}
/>
) : (
<pre>{JSON.stringify(result, null, 2)}</pre>

View file

@ -4,19 +4,19 @@ import { useSWRConfig } from 'swr';
import { Suggestion } from '@/db/schema';
import { UICanvas } from './canvas';
import { UIBlock } from './block';
type StreamingDelta = {
type: 'text-delta' | 'title' | 'id' | 'suggestion' | 'clear' | 'finish';
content: string | Suggestion;
};
export function useCanvasStream({
export function useBlockStream({
streamingData,
setCanvas,
setBlock,
}: {
streamingData: JSONValue[] | undefined;
setCanvas: Dispatch<SetStateAction<UICanvas>>;
setBlock: Dispatch<SetStateAction<UIBlock>>;
}) {
const { mutate } = useSWRConfig();
const [optimisticSuggestions, setOptimisticSuggestions] = useState<
@ -37,30 +37,30 @@ export function useCanvasStream({
const delta = mostRecentDelta as StreamingDelta;
setCanvas((draftCanvas) => {
setBlock((draftBlock) => {
switch (delta.type) {
case 'id':
return {
...draftCanvas,
...draftBlock,
documentId: delta.content as string,
};
case 'title':
return {
...draftCanvas,
...draftBlock,
title: delta.content as string,
};
case 'text-delta':
return {
...draftCanvas,
content: draftCanvas.content + (delta.content as string),
...draftBlock,
content: draftBlock.content + (delta.content as string),
isVisible:
draftCanvas.status === 'streaming' &&
draftCanvas.content.length > 200 &&
draftCanvas.content.length < 250
draftBlock.status === 'streaming' &&
draftBlock.content.length > 200 &&
draftBlock.content.length < 250
? true
: draftCanvas.isVisible,
: draftBlock.isVisible,
status: 'streaming',
};
@ -72,24 +72,24 @@ export function useCanvasStream({
]);
}, 0);
return draftCanvas;
return draftBlock;
case 'clear':
return {
...draftCanvas,
...draftBlock,
content: '',
status: 'streaming',
};
case 'finish':
return {
...draftCanvas,
...draftBlock,
status: 'idle',
};
default:
return draftCanvas;
return draftBlock;
}
});
}, [streamingData, setCanvas]);
}, [streamingData, setBlock]);
}

View file

@ -9,19 +9,19 @@ import { useWindowSize } from 'usehooks-ts';
import { Document } from '@/db/schema';
import { getDocumentTimestampByIndex } from '@/lib/utils';
import { UICanvas } from './canvas';
import { UIBlock } from './block';
import { LoaderIcon } from './icons';
import { Button } from '../ui/button';
interface VersionFooterProps {
canvas: UICanvas;
block: UIBlock;
handleVersionChange: (type: 'next' | 'prev' | 'toggle' | 'latest') => void;
documents: Array<Document> | undefined;
currentVersionIndex: number;
}
export const VersionFooter = ({
canvas,
block,
handleVersionChange,
documents,
currentVersionIndex,
@ -56,8 +56,8 @@ export const VersionFooter = ({
setIsMutating(true);
mutate(
`/api/document?id=${canvas.documentId}`,
await fetch(`/api/document?id=${canvas.documentId}`, {
`/api/document?id=${block.documentId}`,
await fetch(`/api/document?id=${block.documentId}`, {
method: 'PATCH',
body: JSON.stringify({
timestamp: getDocumentTimestampByIndex(