+
)}
diff --git a/components/suggested-actions.tsx b/components/suggested-actions.tsx
index c5614752d..7fbc03d4a 100644
--- a/components/suggested-actions.tsx
+++ b/components/suggested-actions.tsx
@@ -21,9 +21,9 @@ function PureSuggestedActions({ chatId, append }: SuggestedActionsProps) {
action: 'What is the weather in San Francisco?',
},
{
- title: 'Help me draft an essay',
- label: 'about Silicon Valley',
- action: 'Help me draft a short essay about Silicon Valley',
+ title: 'Help me write code to',
+ label: 'demonstrate the djikstra algorithm!',
+ action: 'Help me write code to demonstrate the djikstra algorithm!',
},
];
diff --git a/components/suggestion.tsx b/components/suggestion.tsx
index 4cbf66d83..e0374a56c 100644
--- a/components/suggestion.tsx
+++ b/components/suggestion.tsx
@@ -8,13 +8,17 @@ import type { UISuggestion } from '@/lib/editor/suggestions';
import { CrossIcon, MessageIcon } from './icons';
import { Button } from './ui/button';
+import { cn } from '@/lib/utils';
+import { BlockKind } from './block';
export const Suggestion = ({
suggestion,
onApply,
+ blockKind,
}: {
suggestion: UISuggestion;
onApply: () => void;
+ blockKind: BlockKind;
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const { width: windowWidth } = useWindowSize();
@@ -23,7 +27,10 @@ export const Suggestion = ({
{!isExpanded ? (
{
setIsExpanded(true);
}}
@@ -34,7 +41,7 @@ export const Suggestion = ({
) : (
+> = {
+ text: [
+ {
+ type: 'final-polish',
+ description: 'Add final polish',
+ icon: ,
+ },
+ {
+ type: 'adjust-reading-level',
+ description: 'Adjust reading level',
+ icon: ,
+ },
+ {
+ type: 'request-suggestions',
+ description: 'Request suggestions',
+ icon: ,
+ },
+ ],
+ code: [
+ {
+ type: 'add-comments',
+ description: 'Add comments',
+ icon: ,
+ },
+ {
+ type: 'add-logs',
+ description: 'Add logs',
+ icon: ,
+ },
+ ],
+};
+
export const Tools = ({
isToolbarVisible,
selectedTool,
@@ -267,6 +336,7 @@ export const Tools = ({
append,
isAnimating,
setIsToolbarVisible,
+ blockKind,
}: {
isToolbarVisible: boolean;
selectedTool: string | null;
@@ -277,7 +347,10 @@ export const Tools = ({
) => Promise;
isAnimating: boolean;
setIsToolbarVisible: Dispatch>;
+ blockKind: 'text' | 'code';
}) => {
+ const [primaryTool, ...secondaryTools] = toolsByBlockKind[blockKind];
+
return (
- {isToolbarVisible && (
- <>
- }
- selectedTool={selectedTool}
- setSelectedTool={setSelectedTool}
- append={append}
- isAnimating={isAnimating}
- />
-
+ {isToolbarVisible &&
+ secondaryTools.map((secondaryTool) => (
}
+ key={secondaryTool.type}
+ type={secondaryTool.type}
+ description={secondaryTool.description}
+ icon={secondaryTool.icon}
selectedTool={selectedTool}
setSelectedTool={setSelectedTool}
append={append}
isAnimating={isAnimating}
/>
- >
- )}
+ ))}
}
+ type={primaryTool.type}
+ description={primaryTool.description}
+ icon={primaryTool.icon}
selectedTool={selectedTool}
setSelectedTool={setSelectedTool}
isToolbarVisible={isToolbarVisible}
@@ -333,6 +396,7 @@ const PureToolbar = ({
isLoading,
stop,
setMessages,
+ blockKind,
}: {
isToolbarVisible: boolean;
setIsToolbarVisible: Dispatch>;
@@ -343,6 +407,7 @@ const PureToolbar = ({
) => Promise;
stop: () => void;
setMessages: Dispatch>;
+ blockKind: 'text' | 'code';
}) => {
const toolbarRef = useRef(null);
const timeoutRef = useRef>();
@@ -404,7 +469,7 @@ const PureToolbar = ({
: {
opacity: 1,
y: 0,
- height: 3 * 45,
+ height: toolsByBlockKind[blockKind].length * 47,
transition: { delay: 0 },
scale: 1,
}
@@ -461,6 +526,7 @@ const PureToolbar = ({
selectedTool={selectedTool}
setIsToolbarVisible={setIsToolbarVisible}
setSelectedTool={setSelectedTool}
+ blockKind={blockKind}
/>
)}
@@ -471,6 +537,7 @@ const PureToolbar = ({
export const Toolbar = memo(PureToolbar, (prevProps, nextProps) => {
if (prevProps.isLoading !== nextProps.isLoading) return false;
if (prevProps.isToolbarVisible !== nextProps.isToolbarVisible) return false;
+ if (prevProps.blockKind !== nextProps.blockKind) return false;
return true;
});
diff --git a/components/use-block-stream.tsx b/components/use-block-stream.tsx
index 1386f4cc0..e9c88928c 100644
--- a/components/use-block-stream.tsx
+++ b/components/use-block-stream.tsx
@@ -4,18 +4,20 @@ import { useSWRConfig } from 'swr';
import type { Suggestion } from '@/lib/db/schema';
-import type { UIBlock } from './block';
+import type { BlockKind, UIBlock } from './block';
import { useUserMessageId } from '@/hooks/use-user-message-id';
type StreamingDelta = {
type:
| 'text-delta'
+ | 'code-delta'
| 'title'
| 'id'
| 'suggestion'
| 'clear'
| 'finish'
- | 'user-message-id';
+ | 'user-message-id'
+ | 'kind';
content: string | Suggestion;
};
@@ -67,6 +69,12 @@ export function useBlockStream({
title: delta.content as string,
};
+ case 'kind':
+ return {
+ ...draftBlock,
+ kind: delta.content as BlockKind,
+ };
+
case 'text-delta':
return {
...draftBlock,
@@ -80,6 +88,19 @@ export function useBlockStream({
status: 'streaming',
};
+ case 'code-delta':
+ return {
+ ...draftBlock,
+ content: delta.content as string,
+ isVisible:
+ draftBlock.status === 'streaming' &&
+ draftBlock.content.length > 20 &&
+ draftBlock.content.length < 30
+ ? true
+ : draftBlock.isVisible,
+ status: 'streaming',
+ };
+
case 'suggestion':
setTimeout(() => {
setOptimisticSuggestions((currentSuggestions) => [
@@ -107,5 +128,5 @@ export function useBlockStream({
return draftBlock;
}
});
- }, [streamingData, setBlock]);
+ }, [streamingData, setBlock, setUserMessageIdFromServer]);
}
diff --git a/lib/ai/prompts.ts b/lib/ai/prompts.ts
index c9eba6056..a0d20a2e4 100644
--- a/lib/ai/prompts.ts
+++ b/lib/ai/prompts.ts
@@ -1,12 +1,15 @@
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.
+ When writing code, specify the language in the backticks, e.g. \`\`\`python\`code here\`\`\`. The default language is Python. Other languages are not yet supported, so let the user know if they request a different language.
+
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)
+ - For substantial content (>10 lines) or code
- For content users will likely save/reuse (emails, code, essays, etc.)
- When explicitly requested to create a document
+ - For when content contains a single code snippet
**When NOT to use \`createDocument\`:**
- For informational/explanatory content
@@ -25,3 +28,37 @@ export const regularPrompt =
'You are a friendly assistant! Keep your responses concise and helpful.';
export const systemPrompt = `${regularPrompt}\n\n${blocksPrompt}`;
+
+export const codePrompt = `
+You are a Python code generator that creates self-contained, executable code snippets. When writing code:
+
+1. Each snippet should be complete and runnable on its own
+2. Prefer using print() statements to display outputs
+3. Include helpful comments explaining the code
+4. Keep snippets concise (generally under 15 lines)
+5. Avoid external dependencies - use Python standard library
+6. Handle potential errors gracefully
+7. Return meaningful output that demonstrates the code's functionality
+8. Don't use input() or other interactive functions
+9. Don't access files or network resources
+10. Don't use infinite loops
+
+Examples of good snippets:
+
+\`\`\`python
+# Calculate factorial iteratively
+def factorial(n):
+ result = 1
+ for i in range(1, n + 1):
+ result *= i
+ return result
+
+print(f"Factorial of 5 is: {factorial(5)}")
+\`\`\`
+`;
+
+export const updateDocumentPrompt = (currentContent: string | null) => `\
+Update the following contents of the document based on the given prompt.
+
+${currentContent}
+`;
diff --git a/lib/db/migrations/0004_odd_slayback.sql b/lib/db/migrations/0004_odd_slayback.sql
new file mode 100644
index 000000000..4186ce39c
--- /dev/null
+++ b/lib/db/migrations/0004_odd_slayback.sql
@@ -0,0 +1 @@
+ALTER TABLE "Document" ADD COLUMN "text" varchar DEFAULT 'text' NOT NULL;
\ No newline at end of file
diff --git a/lib/db/migrations/meta/0004_snapshot.json b/lib/db/migrations/meta/0004_snapshot.json
new file mode 100644
index 000000000..64df8a027
--- /dev/null
+++ b/lib/db/migrations/meta/0004_snapshot.json
@@ -0,0 +1,391 @@
+{
+ "id": "30ad8233-1432-428b-93fc-2bb1ba867ff1",
+ "prevId": "011efa9e-42c7-4ff6-830a-02106f6638c9",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.Chat": {
+ "name": "Chat",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'private'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Chat_userId_User_id_fk": {
+ "name": "Chat_userId_User_id_fk",
+ "tableFrom": "Chat",
+ "tableTo": "User",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.Document": {
+ "name": "Document",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text'"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Document_userId_User_id_fk": {
+ "name": "Document_userId_User_id_fk",
+ "tableFrom": "Document",
+ "tableTo": "User",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "Document_id_createdAt_pk": {
+ "name": "Document_id_createdAt_pk",
+ "columns": [
+ "id",
+ "createdAt"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "public.Message": {
+ "name": "Message",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "chatId": {
+ "name": "chatId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "varchar",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Message_chatId_Chat_id_fk": {
+ "name": "Message_chatId_Chat_id_fk",
+ "tableFrom": "Message",
+ "tableTo": "Chat",
+ "columnsFrom": [
+ "chatId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.Suggestion": {
+ "name": "Suggestion",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "documentId": {
+ "name": "documentId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "documentCreatedAt": {
+ "name": "documentCreatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "originalText": {
+ "name": "originalText",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "suggestedText": {
+ "name": "suggestedText",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isResolved": {
+ "name": "isResolved",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Suggestion_userId_User_id_fk": {
+ "name": "Suggestion_userId_User_id_fk",
+ "tableFrom": "Suggestion",
+ "tableTo": "User",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "Suggestion_documentId_documentCreatedAt_Document_id_createdAt_fk": {
+ "name": "Suggestion_documentId_documentCreatedAt_Document_id_createdAt_fk",
+ "tableFrom": "Suggestion",
+ "tableTo": "Document",
+ "columnsFrom": [
+ "documentId",
+ "documentCreatedAt"
+ ],
+ "columnsTo": [
+ "id",
+ "createdAt"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "Suggestion_id_pk": {
+ "name": "Suggestion_id_pk",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "public.User": {
+ "name": "User",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.Vote": {
+ "name": "Vote",
+ "schema": "",
+ "columns": {
+ "chatId": {
+ "name": "chatId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "messageId": {
+ "name": "messageId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isUpvoted": {
+ "name": "isUpvoted",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Vote_chatId_Chat_id_fk": {
+ "name": "Vote_chatId_Chat_id_fk",
+ "tableFrom": "Vote",
+ "tableTo": "Chat",
+ "columnsFrom": [
+ "chatId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "Vote_messageId_Message_id_fk": {
+ "name": "Vote_messageId_Message_id_fk",
+ "tableFrom": "Vote",
+ "tableTo": "Message",
+ "columnsFrom": [
+ "messageId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "Vote_chatId_messageId_pk": {
+ "name": "Vote_chatId_messageId_pk",
+ "columns": [
+ "chatId",
+ "messageId"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/lib/db/migrations/meta/_journal.json b/lib/db/migrations/meta/_journal.json
index 84dfebe21..591f010f0 100644
--- a/lib/db/migrations/meta/_journal.json
+++ b/lib/db/migrations/meta/_journal.json
@@ -29,6 +29,13 @@
"when": 1733403031014,
"tag": "0003_cloudy_glorian",
"breakpoints": true
+ },
+ {
+ "idx": 4,
+ "version": "7",
+ "when": 1733945232355,
+ "tag": "0004_odd_slayback",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/lib/db/queries.ts b/lib/db/queries.ts
index 79b7037fe..4a85e297a 100644
--- a/lib/db/queries.ts
+++ b/lib/db/queries.ts
@@ -16,6 +16,7 @@ import {
message,
vote,
} from './schema';
+import { BlockKind } from '@/components/block';
// Optionally, if not using email/pass login, you can
// use the Drizzle adapter for Auth.js / NextAuth
@@ -169,11 +170,13 @@ export async function getVotesByChatId({ id }: { id: string }) {
export async function saveDocument({
id,
title,
+ kind,
content,
userId,
}: {
id: string;
title: string;
+ kind: BlockKind;
content: string;
userId: string;
}) {
@@ -181,6 +184,7 @@ export async function saveDocument({
return await db.insert(document).values({
id,
title,
+ kind,
content,
userId,
createdAt: new Date(),
diff --git a/lib/db/schema.ts b/lib/db/schema.ts
index 23d9e82e4..712cc3627 100644
--- a/lib/db/schema.ts
+++ b/lib/db/schema.ts
@@ -72,6 +72,9 @@ export const document = pgTable(
createdAt: timestamp('createdAt').notNull(),
title: text('title').notNull(),
content: text('content'),
+ kind: varchar('text', { enum: ['text', 'code'] })
+ .notNull()
+ .default('text'),
userId: uuid('userId')
.notNull()
.references(() => user.id),
diff --git a/lib/editor/suggestions.tsx b/lib/editor/suggestions.tsx
index 773a41f47..37885e747 100644
--- a/lib/editor/suggestions.tsx
+++ b/lib/editor/suggestions.tsx
@@ -9,6 +9,7 @@ import { createRoot } from 'react-dom/client';
import { Suggestion as PreviewSuggestion } from '@/components/suggestion';
import type { Suggestion } from '@/lib/db/schema';
+import { BlockKind } from '@/components/block';
export interface UISuggestion extends Suggestion {
selectionStart: number;
@@ -69,6 +70,7 @@ export function projectWithPositions(
export function createSuggestionWidget(
suggestion: UISuggestion,
view: EditorView,
+ blockKind: BlockKind = 'text',
): { dom: HTMLElement; destroy: () => void } {
const dom = document.createElement('span');
const root = createRoot(dom);
@@ -111,7 +113,13 @@ export function createSuggestionWidget(
dispatch(textTransaction);
};
- root.render();
+ root.render(
+ ,
+ );
return {
dom,
diff --git a/package.json b/package.json
index ac1f645d9..2ffe24b3f 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,11 @@
},
"dependencies": {
"@ai-sdk/openai": "1.0.6",
+ "@codemirror/lang-javascript": "^6.2.2",
+ "@codemirror/lang-python": "^6.1.6",
+ "@codemirror/state": "^6.5.0",
+ "@codemirror/theme-one-dark": "^6.1.2",
+ "@codemirror/view": "^6.35.3",
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
@@ -37,6 +42,7 @@
"class-variance-authority": "^0.7.0",
"classnames": "^2.5.1",
"clsx": "^2.1.1",
+ "codemirror": "^6.0.1",
"date-fns": "^4.1.0",
"diff-match-patch": "^1.0.5",
"dotenv": "^16.4.5",
@@ -62,6 +68,7 @@
"react": "19.0.0-rc-45804af1-20241021",
"react-dom": "19.0.0-rc-45804af1-20241021",
"react-markdown": "^9.0.1",
+ "react-resizable-panels": "^2.1.7",
"remark-gfm": "^4.0.0",
"server-only": "^0.0.1",
"sonner": "^1.5.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0656532f4..edf901bfc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,6 +11,21 @@ importers:
'@ai-sdk/openai':
specifier: 1.0.6
version: 1.0.6(zod@3.23.8)
+ '@codemirror/lang-javascript':
+ specifier: ^6.2.2
+ version: 6.2.2
+ '@codemirror/lang-python':
+ specifier: ^6.1.6
+ version: 6.1.6(@codemirror/view@6.35.3)
+ '@codemirror/state':
+ specifier: ^6.5.0
+ version: 6.5.0
+ '@codemirror/theme-one-dark':
+ specifier: ^6.1.2
+ version: 6.1.2
+ '@codemirror/view':
+ specifier: ^6.35.3
+ version: 6.35.3
'@radix-ui/react-alert-dialog':
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)
@@ -65,6 +80,9 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
+ codemirror:
+ specifier: ^6.0.1
+ version: 6.0.1(@lezer/common@1.2.3)
date-fns:
specifier: ^4.1.0
version: 4.1.0
@@ -140,6 +158,9 @@ importers:
react-markdown:
specifier: ^9.0.1
version: 9.0.1(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021)
+ react-resizable-panels:
+ specifier: ^2.1.7
+ version: 2.1.7(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)
remark-gfm:
specifier: ^4.0.0
version: 4.0.0
@@ -330,6 +351,41 @@ packages:
cpu: [x64]
os: [win32]
+ '@codemirror/autocomplete@6.18.3':
+ resolution: {integrity: sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==}
+ peerDependencies:
+ '@codemirror/language': ^6.0.0
+ '@codemirror/state': ^6.0.0
+ '@codemirror/view': ^6.0.0
+ '@lezer/common': ^1.0.0
+
+ '@codemirror/commands@6.7.1':
+ resolution: {integrity: sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==}
+
+ '@codemirror/lang-javascript@6.2.2':
+ resolution: {integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==}
+
+ '@codemirror/lang-python@6.1.6':
+ resolution: {integrity: sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==}
+
+ '@codemirror/language@6.10.6':
+ resolution: {integrity: sha512-KrsbdCnxEztLVbB5PycWXFxas4EOyk/fPAfruSOnDDppevQgid2XZ+KbJ9u+fDikP/e7MW7HPBTvTb8JlZK9vA==}
+
+ '@codemirror/lint@6.8.4':
+ resolution: {integrity: sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==}
+
+ '@codemirror/search@6.5.8':
+ resolution: {integrity: sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==}
+
+ '@codemirror/state@6.5.0':
+ resolution: {integrity: sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==}
+
+ '@codemirror/theme-one-dark@6.1.2':
+ resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==}
+
+ '@codemirror/view@6.35.3':
+ resolution: {integrity: sha512-ScY7L8+EGdPl4QtoBiOzE4FELp7JmNUsBvgBcCakXWM2uiv/K89VAzU3BMDscf0DsACLvTKePbd5+cFDTcei6g==}
+
'@drizzle-team/brocli@0.10.2':
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
@@ -935,6 +991,24 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+ '@lezer/common@1.2.3':
+ resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
+
+ '@lezer/highlight@1.2.1':
+ resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==}
+
+ '@lezer/javascript@1.4.21':
+ resolution: {integrity: sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==}
+
+ '@lezer/lr@1.4.2':
+ resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==}
+
+ '@lezer/python@1.1.15':
+ resolution: {integrity: sha512-aVQ43m2zk4FZYedCqL0KHPEUsqZOrmAvRhkhHlVPnDD1HODDyyQv5BRIuod4DadkgBEZd53vQOtXTonNbEgjrQ==}
+
+ '@marijn/find-cluster-break@1.0.2':
+ resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
+
'@neondatabase/serverless@0.9.5':
resolution: {integrity: sha512-siFas6gItqv6wD/pZnvdu34wEqgG3nSE6zWZdq5j2DEsa+VvX8i/5HXJOo06qrw5axPXn+lGCxeR+NLaSPIXug==}
@@ -1743,6 +1817,9 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
+ codemirror@6.0.1:
+ resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -3168,6 +3245,12 @@ packages:
'@types/react':
optional: true
+ react-resizable-panels@2.1.7:
+ resolution: {integrity: sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==}
+ peerDependencies:
+ react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
react-style-singleton@2.2.1:
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
engines: {node: '>=10'}
@@ -3377,6 +3460,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ style-mod@4.1.2:
+ resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
+
style-to-object@1.0.8:
resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
@@ -3731,6 +3817,78 @@ snapshots:
'@biomejs/cli-win32-x64@1.9.4':
optional: true
+ '@codemirror/autocomplete@6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)':
+ dependencies:
+ '@codemirror/language': 6.10.6
+ '@codemirror/state': 6.5.0
+ '@codemirror/view': 6.35.3
+ '@lezer/common': 1.2.3
+
+ '@codemirror/commands@6.7.1':
+ dependencies:
+ '@codemirror/language': 6.10.6
+ '@codemirror/state': 6.5.0
+ '@codemirror/view': 6.35.3
+ '@lezer/common': 1.2.3
+
+ '@codemirror/lang-javascript@6.2.2':
+ dependencies:
+ '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)
+ '@codemirror/language': 6.10.6
+ '@codemirror/lint': 6.8.4
+ '@codemirror/state': 6.5.0
+ '@codemirror/view': 6.35.3
+ '@lezer/common': 1.2.3
+ '@lezer/javascript': 1.4.21
+
+ '@codemirror/lang-python@6.1.6(@codemirror/view@6.35.3)':
+ dependencies:
+ '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)
+ '@codemirror/language': 6.10.6
+ '@codemirror/state': 6.5.0
+ '@lezer/common': 1.2.3
+ '@lezer/python': 1.1.15
+ transitivePeerDependencies:
+ - '@codemirror/view'
+
+ '@codemirror/language@6.10.6':
+ dependencies:
+ '@codemirror/state': 6.5.0
+ '@codemirror/view': 6.35.3
+ '@lezer/common': 1.2.3
+ '@lezer/highlight': 1.2.1
+ '@lezer/lr': 1.4.2
+ style-mod: 4.1.2
+
+ '@codemirror/lint@6.8.4':
+ dependencies:
+ '@codemirror/state': 6.5.0
+ '@codemirror/view': 6.35.3
+ crelt: 1.0.6
+
+ '@codemirror/search@6.5.8':
+ dependencies:
+ '@codemirror/state': 6.5.0
+ '@codemirror/view': 6.35.3
+ crelt: 1.0.6
+
+ '@codemirror/state@6.5.0':
+ dependencies:
+ '@marijn/find-cluster-break': 1.0.2
+
+ '@codemirror/theme-one-dark@6.1.2':
+ dependencies:
+ '@codemirror/language': 6.10.6
+ '@codemirror/state': 6.5.0
+ '@codemirror/view': 6.35.3
+ '@lezer/highlight': 1.2.1
+
+ '@codemirror/view@6.35.3':
+ dependencies:
+ '@codemirror/state': 6.5.0
+ style-mod: 4.1.2
+ w3c-keyname: 2.2.8
+
'@drizzle-team/brocli@0.10.2': {}
'@emnapi/runtime@1.3.1':
@@ -4110,6 +4268,30 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
+ '@lezer/common@1.2.3': {}
+
+ '@lezer/highlight@1.2.1':
+ dependencies:
+ '@lezer/common': 1.2.3
+
+ '@lezer/javascript@1.4.21':
+ dependencies:
+ '@lezer/common': 1.2.3
+ '@lezer/highlight': 1.2.1
+ '@lezer/lr': 1.4.2
+
+ '@lezer/lr@1.4.2':
+ dependencies:
+ '@lezer/common': 1.2.3
+
+ '@lezer/python@1.1.15':
+ dependencies:
+ '@lezer/common': 1.2.3
+ '@lezer/highlight': 1.2.1
+ '@lezer/lr': 1.4.2
+
+ '@marijn/find-cluster-break@1.0.2': {}
+
'@neondatabase/serverless@0.9.5':
dependencies:
'@types/pg': 8.11.6
@@ -4899,6 +5081,18 @@ snapshots:
clsx@2.1.1: {}
+ codemirror@6.0.1(@lezer/common@1.2.3):
+ dependencies:
+ '@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)
+ '@codemirror/commands': 6.7.1
+ '@codemirror/language': 6.10.6
+ '@codemirror/lint': 6.8.4
+ '@codemirror/search': 6.5.8
+ '@codemirror/state': 6.5.0
+ '@codemirror/view': 6.35.3
+ transitivePeerDependencies:
+ - '@lezer/common'
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -6681,6 +6875,11 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.12
+ react-resizable-panels@2.1.7(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021):
+ dependencies:
+ react: 19.0.0-rc-45804af1-20241021
+ react-dom: 19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021)
+
react-style-singleton@2.2.1(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021):
dependencies:
get-nonce: 1.0.1
@@ -6961,6 +7160,8 @@ snapshots:
strip-json-comments@3.1.1: {}
+ style-mod@4.1.2: {}
+
style-to-object@1.0.8:
dependencies:
inline-style-parser: 0.2.4