diff --git a/packages/api/ai/generate.mts b/packages/api/ai/generate.mts
index e0a3c011..2f02c2d2 100644
--- a/packages/api/ai/generate.mts
+++ b/packages/api/ai/generate.mts
@@ -6,6 +6,7 @@ import {
type CodeCellType,
randomid,
type CellWithPlaceholderType,
+ type MarkdownCellType,
} from '@srcbook/shared';
import { type SessionType } from '../types.mjs';
import { readFileSync } from 'node:fs';
@@ -85,11 +86,18 @@ const makeGenerateCellEditSystemPrompt = (language: CodeLanguageType) => {
const makeGenerateCellEditUserPrompt = (
query: string,
session: SessionType,
- cell: CodeCellType,
+ cell: CodeCellType | MarkdownCellType,
) => {
+ const cellLanguage = cell.type === 'markdown' ? 'markdown' : session.language;
+ const filteredCells =
+ cellLanguage === 'markdown'
+ ? session.cells.filter((cell) => !(cell.type === 'package.json'))
+ : session.cells;
+
// Intentionally not passing in tsconfig.json here as that doesn't need to be in the prompt.
+
const inlineSrcbook = encode(
- { cells: session.cells, language: session.language },
+ { cells: filteredCells, language: cellLanguage as CodeLanguageType },
{ inline: true },
);
@@ -97,9 +105,15 @@ const makeGenerateCellEditUserPrompt = (
${inlineSrcbook}
==== END SRCBOOK ====
-==== BEGIN CODE CELL ====
+${
+ cell.type === 'code'
+ ? `==== BEGIN CODE CELL ====
${cell.source}
-==== END CODE CELL ====
+==== END CODE CELL ====`
+ : `==== BEGIN MARKDOWN CELL ====
+${cell.text}
+==== END MARKDOWN CELL ====`
+}
==== BEGIN USER REQUEST ====
${query}
@@ -180,10 +194,14 @@ export async function generateCells(
}
}
-export async function generateCellEdit(query: string, session: SessionType, cell: CodeCellType) {
+export async function generateCellEdit(
+ query: string,
+ session: SessionType,
+ cell: CodeCellType | MarkdownCellType,
+) {
const model = await getModel();
-
- const systemPrompt = makeGenerateCellEditSystemPrompt(session.language);
+ const cellLanguage = cell.type === 'markdown' ? 'markdown' : session.language;
+ const systemPrompt = makeGenerateCellEditSystemPrompt(cellLanguage as CodeLanguageType);
const userPrompt = makeGenerateCellEditUserPrompt(query, session, cell);
const result = await generateText({
model,
diff --git a/packages/api/prompts/code-updater-markdown.txt b/packages/api/prompts/code-updater-markdown.txt
new file mode 100644
index 00000000..976440af
--- /dev/null
+++ b/packages/api/prompts/code-updater-markdown.txt
@@ -0,0 +1,85 @@
+
+
+## Instructions Context
+
+You are tasked with editing a **Markdown cell** in a Srcbook.
+
+A Srcbook is a **Markdown-compatible notebook**, used for documentation or text-based content.
+
+### Srcbook Spec
+
+The structure of a Srcbook:
+0. The language comment: ``
+1. Title cell (heading 1)
+2. N more cells, which are either:
+ - **Markdown cells** (GitHub flavored Markdown)
+ - Markdown cells, which have a filename and source content.
+
+
+# Important Note:
+Markdown cells cannot use h1 or h6 headings, as these are reserved for Srcbook. **Do not use h1 (#) or h6 (######) headings in the content.**
+
+The user is already working on an existing Srcbook and is asking you to edit a specific Markdown cell.
+The Srcbook contents will be passed to you as context, as well as the user's request about the intended edits for the Markdown cell.
+
+---
+
+## Example Srcbook
+
+
+
+### Getting Started
+
+#### What are Srcbooks?
+
+Srcbooks are an interactive way of organizing and presenting information. They are similar to other notebooks but unique in their flexibility and format.
+
+#### Dependencies
+
+You can include any necessary information, resources, or links to external content.
+
+##### Introduction
+
+This is a Markdown cell showcasing various Markdown features.
+
+#### Features Overview
+
+##### Text Formatting
+
+- **Bold text**
+- *Italic text*
+- ~~Strikethrough text~~
+
+##### Lists
+
+- **Unordered List:**
+ - Item 1
+ - Item 2
+
+- **Ordered List:**
+ 1. First item
+ 2. Second item
+
+##### Code Blocks
+
+Inline code: `console.log("Hello, Markdown!")`
+
+##### Links
+
+[Click here to visit Google](https://www.google.com)
+
+##### Images
+
+![Alt text](image.png)
+
+---
+
+## Final Instructions
+
+The user's Srcbook will be passed to you, surrounded with `==== BEGIN SRCBOOK ====` and `==== END SRCBOOK ====`.
+The specific **Markdown cell** they want updated will also be passed to you, surrounded with `==== BEGIN MARKDOWN CELL ====` and `==== END MARKDOWN CELL ====`.
+The user's intent will be passed to you between `==== BEGIN USER REQUEST ====` and `==== END USER REQUEST ====`.
+
+Your job is to edit the cell based on the contents of the Srcbook and the user's intent.
+Act as a **Markdown expert**, writing the best possible content you can. Focus on being **elegant, concise, and clear**.
+**ONLY RETURN THE MARKDOWN TEXT , NO PREAMBULE, NO SUFFIX, NO CODE FENCES (LIKE TRIPLE BACKTICKS) ONLY THE MARKDOWN**.
diff --git a/packages/api/srcmd/encoding.mts b/packages/api/srcmd/encoding.mts
index 332b7ac5..b6602171 100644
--- a/packages/api/srcmd/encoding.mts
+++ b/packages/api/srcmd/encoding.mts
@@ -22,7 +22,9 @@ export function encode(srcbook: SrcbookWithPlacebolderType, options: { inline: b
const encoded = [
encodeMetdata(srcbook),
encodeTitleCell(titleCell),
- encodePackageJsonCell(packageJsonCell, options),
+ ...((srcbook.language as string) !== 'markdown'
+ ? [encodePackageJsonCell(packageJsonCell, options)]
+ : []),
...cells.map((cell) => {
switch (cell.type) {
case 'code':
@@ -34,7 +36,6 @@ export function encode(srcbook: SrcbookWithPlacebolderType, options: { inline: b
}
}),
];
-
// End every file with exactly one newline.
return encoded.join('\n\n').trimEnd() + '\n';
}
diff --git a/packages/web/src/components/ai-prompt-input.tsx b/packages/web/src/components/ai-prompt-input.tsx
new file mode 100644
index 00000000..b885e483
--- /dev/null
+++ b/packages/web/src/components/ai-prompt-input.tsx
@@ -0,0 +1,53 @@
+import { Sparkles, MessageCircleWarning, X } from 'lucide-react';
+import TextareaAutosize from 'react-textarea-autosize';
+import { Button } from '@/components/ui/button';
+import AiGenerateTipsDialog from '@/components/ai-generate-tips-dialog';
+import { useNavigate } from 'react-router-dom';
+
+interface AiPromptInputProps {
+ prompt: string;
+ setPrompt: (prompt: string) => void;
+ onClose: () => void;
+ aiEnabled: boolean;
+}
+
+export function AiPromptInput({ prompt, setPrompt, onClose, aiEnabled }: AiPromptInputProps) {
+ const navigate = useNavigate();
+ return (
+
+
+
+
+ setPrompt(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+ {!aiEnabled && (
+
+
AI provider not configured.
+
+
+ )}
+
+ );
+}
diff --git a/packages/web/src/components/cells/code.tsx b/packages/web/src/components/cells/code.tsx
index d3a156b5..367f563e 100644
--- a/packages/web/src/components/cells/code.tsx
+++ b/packages/web/src/components/cells/code.tsx
@@ -4,24 +4,13 @@ import { useEffect, useRef, useState } from 'react';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import Shortcut from '@/components/keyboard-shortcut';
-import { useNavigate } from 'react-router-dom';
+
import { useHotkeys } from 'react-hotkeys-hook';
import CodeMirror, { keymap, Prec } from '@uiw/react-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
-import {
- Info,
- Play,
- Trash2,
- Sparkles,
- X,
- MessageCircleWarning,
- LoaderCircle,
- Maximize,
- Minimize,
-} from 'lucide-react';
-import TextareaAutosize from 'react-textarea-autosize';
-import AiGenerateTipsDialog from '@/components/ai-generate-tips-dialog';
+import { Info, Play, Trash2, Sparkles, LoaderCircle, Maximize, Minimize } from 'lucide-react';
+
import {
CellType,
CodeCellType,
@@ -46,6 +35,7 @@ import { EditorView } from 'codemirror';
import { EditorState } from '@codemirror/state';
import { unifiedMergeView } from '@codemirror/merge';
import { type Diagnostic, linter } from '@codemirror/lint';
+import { AiPromptInput } from '@/components/ai-prompt-input';
import { tsHover } from './hover';
import { mapTsServerLocationToCM } from './util';
import { toast } from 'sonner';
@@ -395,7 +385,6 @@ function Header(props: {
} = props;
const { aiEnabled } = useSettings();
- const navigate = useNavigate();
return (
<>
@@ -554,50 +543,12 @@ function Header(props: {
{['prompting', 'generating'].includes(cellMode) && (
-