feat: add code and text block types (#609)

This commit is contained in:
Jeremy 2024-12-16 18:14:40 +05:30 committed by GitHub
parent 3df0fd4c0f
commit 9778631d6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 1754 additions and 290 deletions

View file

@ -2,6 +2,7 @@ import {
type Message,
StreamData,
convertToCoreMessages,
generateObject,
streamObject,
streamText,
} from 'ai';
@ -10,7 +11,11 @@ import { z } from 'zod';
import { auth } from '@/app/(auth)/auth';
import { customModel } from '@/lib/ai';
import { models } from '@/lib/ai/models';
import { systemPrompt } from '@/lib/ai/prompts';
import {
codePrompt,
systemPrompt,
updateDocumentPrompt,
} from '@/lib/ai/prompts';
import {
deleteChatById,
getChatById,
@ -119,11 +124,12 @@ export async function POST(request: Request) {
},
},
createDocument: {
description: 'Create a document for a writing activity',
description: 'Create a document for a writing activity.',
parameters: z.object({
title: z.string(),
kind: z.enum(['text', 'code']),
}),
execute: async ({ title }) => {
execute: async ({ title, kind }) => {
const id = generateUUID();
let draftText = '';
@ -137,38 +143,75 @@ export async function POST(request: Request) {
content: title,
});
streamingData.append({
type: 'kind',
content: kind,
});
streamingData.append({
type: 'clear',
content: '',
});
const { fullStream } = streamText({
model: customModel(model.apiIdentifier),
system:
'Write about the given topic. Markdown is supported. Use headings wherever appropriate.',
prompt: title,
});
if (kind === 'text') {
const { fullStream } = streamText({
model: customModel(model.apiIdentifier),
system:
'Write about the given topic. Markdown is supported. Use headings wherever appropriate.',
prompt: title,
});
for await (const delta of fullStream) {
const { type } = delta;
for await (const delta of fullStream) {
const { type } = delta;
if (type === 'text-delta') {
const { textDelta } = delta;
if (type === 'text-delta') {
const { textDelta } = delta;
draftText += textDelta;
streamingData.append({
type: 'text-delta',
content: textDelta,
});
draftText += textDelta;
streamingData.append({
type: 'text-delta',
content: textDelta,
});
}
}
}
streamingData.append({ type: 'finish', content: '' });
streamingData.append({ type: 'finish', content: '' });
} else if (kind === 'code') {
const { fullStream } = streamObject({
model: customModel(model.apiIdentifier),
system: codePrompt,
prompt: title,
schema: z.object({
code: z.string(),
}),
});
for await (const delta of fullStream) {
const { type } = delta;
if (type === 'object') {
const { object } = delta;
const { code } = object;
if (code) {
streamingData.append({
type: 'code-delta',
content: code ?? '',
});
draftText = code;
}
}
}
streamingData.append({ type: 'finish', content: '' });
}
if (session.user?.id) {
await saveDocument({
id,
title,
kind,
content: draftText,
userId: session.user.id,
});
@ -177,6 +220,7 @@ export async function POST(request: Request) {
return {
id,
title,
kind,
content: 'A document was created and is now visible to the user.',
};
},
@ -206,48 +250,73 @@ export async function POST(request: Request) {
content: document.title,
});
const { fullStream } = streamText({
model: customModel(model.apiIdentifier),
system:
'You are a helpful writing assistant. Based on the description, please update the piece of writing.',
experimental_providerMetadata: {
openai: {
prediction: {
type: 'content',
content: currentContent,
if (document.kind === 'text') {
const { fullStream } = streamText({
model: customModel(model.apiIdentifier),
system: updateDocumentPrompt(currentContent),
prompt: description,
experimental_providerMetadata: {
openai: {
prediction: {
type: 'content',
content: currentContent,
},
},
},
},
messages: [
{
role: 'user',
content: description,
},
{ role: 'user', content: currentContent },
],
});
});
for await (const delta of fullStream) {
const { type } = delta;
for await (const delta of fullStream) {
const { type } = delta;
if (type === 'text-delta') {
const { textDelta } = delta;
if (type === 'text-delta') {
const { textDelta } = delta;
draftText += textDelta;
streamingData.append({
type: 'text-delta',
content: textDelta,
});
draftText += textDelta;
streamingData.append({
type: 'text-delta',
content: textDelta,
});
}
}
}
streamingData.append({ type: 'finish', content: '' });
streamingData.append({ type: 'finish', content: '' });
} else if (document.kind === 'code') {
const { fullStream } = streamObject({
model: customModel(model.apiIdentifier),
system: updateDocumentPrompt(currentContent),
prompt: description,
schema: z.object({
code: z.string(),
}),
});
for await (const delta of fullStream) {
const { type } = delta;
if (type === 'object') {
const { object } = delta;
const { code } = object;
if (code) {
streamingData.append({
type: 'code-delta',
content: code ?? '',
});
draftText = code;
}
}
}
streamingData.append({ type: 'finish', content: '' });
}
if (session.user?.id) {
await saveDocument({
id,
title: document.title,
content: draftText,
kind: document.kind,
userId: session.user.id,
});
}
@ -255,6 +324,7 @@ export async function POST(request: Request) {
return {
id,
title: document.title,
kind: document.kind,
content: 'The document has been updated successfully.',
};
},
@ -328,6 +398,7 @@ export async function POST(request: Request) {
return {
id: documentId,
title: document.title,
kind: document.kind,
message: 'Suggestions have been added to the document',
};
},

View file

@ -1,4 +1,5 @@
import { auth } from '@/app/(auth)/auth';
import { BlockKind } from '@/components/block';
import {
deleteDocumentsByIdAfterTimestamp,
getDocumentsById,
@ -48,14 +49,18 @@ export async function POST(request: Request) {
return new Response('Unauthorized', { status: 401 });
}
const { content, title }: { content: string; title: string } =
await request.json();
const {
content,
title,
kind,
}: { content: string; title: string; kind: BlockKind } = await request.json();
if (session.user?.id) {
const document = await saveDocument({
id,
content,
title,
kind,
userId: session.user.id,
});

View file

@ -4,6 +4,7 @@ import { AppSidebar } from '@/components/app-sidebar';
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
import { auth } from '../(auth)/auth';
import Script from 'next/script';
export const experimental_ppr = true;
@ -16,9 +17,15 @@ export default async function Layout({
const isCollapsed = cookieStore.get('sidebar:state')?.value !== 'true';
return (
<SidebarProvider defaultOpen={!isCollapsed}>
<AppSidebar user={session?.user} />
<SidebarInset>{children}</SidebarInset>
</SidebarProvider>
<>
<Script
src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"
strategy="beforeInteractive"
/>
<SidebarProvider defaultOpen={!isCollapsed}>
<AppSidebar user={session?.user} />
<SidebarInset>{children}</SidebarInset>
</SidebarProvider>
</>
);
}

View file

@ -3,144 +3,176 @@
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 217 100% 45%;
--primary-foreground: 0 0% 98%;
--secondary: 213 100% 96%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 214 32% 91%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 208.69 100% 24.18%;
--primary-foreground: 207 100% 96%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 217 100% 45%;
--primary-foreground: 0 0% 98%;
--secondary: 213 100% 96%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 214 32% 91%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 208.69 100% 24.18%;
--primary-foreground: 207 100% 96%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
@layer base {
* {
@apply border-border;
}
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
body {
@apply bg-background text-foreground;
}
@font-face {
font-family: "geist";
font-style: normal;
font-weight: 100 900;
src: url(/fonts/geist.woff2) format("woff2");
}
@font-face {
font-family: "geist";
font-style: normal;
font-weight: 100 900;
src: url(/fonts/geist.woff2) format("woff2");
}
@font-face {
font-family: "geist-mono";
font-style: normal;
font-weight: 100 900;
src: url(/fonts/geist-mono.woff2) format("woff2");
}
@font-face {
font-family: "geist-mono";
font-style: normal;
font-weight: 100 900;
src: url(/fonts/geist-mono.woff2) format("woff2");
}
}
.skeleton {
* {
pointer-events: none !important;
}
* {
pointer-events: none !important;
}
*[class^="text-"] {
color: transparent;
@apply rounded-md bg-foreground/20 select-none animate-pulse;
}
*[class^="text-"] {
color: transparent;
@apply rounded-md bg-foreground/20 select-none animate-pulse;
}
.skeleton-bg {
@apply bg-foreground/10;
}
.skeleton-bg {
@apply bg-foreground/10;
}
.skeleton-div {
@apply bg-foreground/20 animate-pulse;
}
.skeleton-div {
@apply bg-foreground/20 animate-pulse;
}
}
.ProseMirror {
outline: none;
outline: none;
}
.cm-editor,
.cm-gutters {
@apply bg-background dark:bg-zinc-800 outline-none selection:bg-zinc-900 !important;
}
.ͼo.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground,
.ͼo.cm-selectionBackground,
.ͼo.cm-content::selection {
@apply bg-zinc-200 dark:bg-zinc-900 !important;
}
.cm-activeLine,
.cm-activeLineGutter {
@apply bg-transparent !important;
}
.cm-activeLine {
@apply rounded-r-sm !important;
}
.cm-lineNumbers {
@apply min-w-7;
}
.cm-foldGutter {
@apply min-w-3;
}
.cm-lineNumbers .cm-activeLineGutter {
@apply rounded-l-sm !important;
}
.suggestion-highlight {
@apply bg-blue-200 hover:bg-blue-300 dark:hover:bg-blue-400/50 dark:text-blue-50 dark:bg-blue-500/40;
@apply bg-blue-200 hover:bg-blue-300 dark:hover:bg-blue-400/50 dark:text-blue-50 dark:bg-blue-500/40;
}