Skip to content

Commit

Permalink
Merge branch 'main' into feat/srcbookdev#218-Add-AI-generation-to-mar…
Browse files Browse the repository at this point in the history
…kdown-cells
  • Loading branch information
Aswanth-c committed Sep 4, 2024
2 parents ea84608 + 35a5e25 commit f28bdc5
Show file tree
Hide file tree
Showing 59 changed files with 932 additions and 246 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: Lint
run: pnpm check-format

- name: Build
run: pnpm build

- name: Lint & Check
run: pnpm lint && pnpm check-format

- name: Test
run: pnpm test
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

### 0.0.1-alpha.18

- Add TS-Server on-hover functionality to editor
- Add output to package.json cell in settings [#205](https://github.com/srcbookdev/srcbook/issues/205)
- Improved home page layout
- Limit title length [#207](https://github.com/srcbookdev/srcbook/issues/207)
- UI tweaks & minor bug fixes

### 0.0.1-alpha.17

- Overhaul monorepo tooling [#201](https://github.com/srcbookdev/srcbook/pull/201)
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ From the root level:

From the relevant package directory:

- update the CHANELOG.md in the root directory
- bump the version in the package.json, commit and push the changes. Example: `git commit -m "Release srcbook version 0.0.1-alpha.10`
- `pnpm publish`
- push git tags & add release notes to GitHub
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"devDependencies": {
"@types/node": "catalog:",
"prettier": "catalog:",
"typescript": "catalog:"
"typescript": "catalog:",
"@srcbook/configs": "workspace:*"
},
"dependencies": {
"turbo": "^2.0.14"
Expand Down
13 changes: 13 additions & 0 deletions packages/api/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
root: true,
extends: [require.resolve('@srcbook/configs/eslint/library.js')],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.lint.json',
tsconfigRootDir: __dirname,
},
globals: {
Bun: false,
},
};
13 changes: 10 additions & 3 deletions packages/api/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ export async function getConfig(): Promise<Config> {
if (results.length !== 1) {
console.warn('Expected exactly one config record, found:', results.length);
}

return results[0];
if (results.length === 0) {
throw new Error('No config found');
}
// explicitly known that a config exists here
return results[0] as Config;
}

export async function updateConfig(attrs: Partial<Config>) {
Expand All @@ -55,7 +58,11 @@ export async function addSecret(name: string, value: string): Promise<Secret> {
.values({ name, value })
.onConflictDoUpdate({ target: secrets.name, set: { value } })
.returning();
return result[0];
if (result.length === 0) {
throw new Error('No secret returned');
}
// explicitly known that a config exists here
return result[0] as Secret;
}

export async function removeSecret(name: string) {
Expand Down
3 changes: 2 additions & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@srcbook/api",
"version": "0.0.1-alpha.2",
"version": "0.0.1-alpha.3",
"type": "module",
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
Expand All @@ -12,6 +12,7 @@
"test": "vitest",
"prebuild": "rm -rf ./dist",
"build": "tsc && cp -R ./drizzle ./dist/drizzle && cp -R ./srcbook/examples ./dist/srcbook/examples && cp -R ./prompts ./dist/prompts",
"lint": "eslint . --max-warnings 0",
"check-types": "tsc",
"depcheck": "depcheck",
"generate": "drizzle-kit generate",
Expand Down
53 changes: 51 additions & 2 deletions packages/api/server/ws.mts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {
AiGenerateCellPayloadType,
TsConfigUpdatePayloadType,
AiFixDiagnosticsPayloadType,
TsServerQuickInfoRequestPayloadType,
} from '@srcbook/shared';
import {
CellErrorPayloadSchema,
Expand All @@ -57,6 +58,8 @@ import {
TsConfigUpdatePayloadSchema,
TsConfigUpdatedPayloadSchema,
TsServerCellSuggestionsPayloadSchema,
TsServerQuickInfoRequestPayloadSchema,
TsServerQuickInfoResponsePayloadSchema,
} from '@srcbook/shared';
import tsservers from '../tsservers.mjs';
import { TsServer } from '../tsserver/tsserver.mjs';
Expand Down Expand Up @@ -259,12 +262,13 @@ async function depsInstall(payload: DepsInstallPayloadType) {
output: { type: 'stderr', data: data.toString('utf8') },
});
},
async onExit() {
async onExit(exitCode) {
const updatedJsonSource = await readPackageJsonContentsFromDisk(session);

const updatedCell: PackageJsonCellType = {
...cell,
source: updatedJsonSource,
status: 'idle',
status: exitCode === 0 ? 'idle' : 'failed',
};

const updatedSession = await updateSession(
Expand Down Expand Up @@ -682,6 +686,45 @@ async function tsconfigUpdate(payload: TsConfigUpdatePayloadType) {
});
}

async function tsserverQuickInfo(payload: TsServerQuickInfoRequestPayloadType) {
const session = await findSession(payload.sessionId);

if (!session) {
throw new Error(`No session exists for session '${payload.sessionId}'`);
}

if (session.language !== 'typescript') {
throw new Error(`tsserver can only be used with TypeScript Srcbooks.`);
}

const tsserver = tsservers.has(session.id) ? tsservers.get(session.id) : createTsServer(session);

const cell = session.cells.find((c) => payload.cellId == c.id);

if (!cell || cell.type !== 'code') {
throw new Error(`No code cell found for cellId '${payload.cellId}'`);
}

const filename = cell.filename;

const tsserverResponse = await tsserver.quickinfo({
file: pathToCodeFile(session.dir, filename),
line: payload.request.location.line,
offset: payload.request.location.offset,
});

const body = tsserverResponse.body;
if (!body) {
return null;
}

wss.broadcast(`session:${session.id}`, 'tsserver:cell:quickinfo:response', {
response: {
...body,
},
});
}

wss
.channel('session:*')
.incoming('cell:exec', CellExecPayloadSchema, cellExec)
Expand All @@ -697,6 +740,12 @@ wss
.incoming('tsserver:start', TsServerStartPayloadSchema, tsserverStart)
.incoming('tsserver:stop', TsServerStopPayloadSchema, tsserverStop)
.incoming('tsconfig.json:update', TsConfigUpdatePayloadSchema, tsconfigUpdate)
.incoming(
'tsserver:cell:quickinfo:request',
TsServerQuickInfoRequestPayloadSchema,
tsserverQuickInfo,
)
.outgoing('tsserver:cell:quickinfo:response', TsServerQuickInfoResponsePayloadSchema)
.outgoing('cell:updated', CellUpdatedPayloadSchema)
.outgoing('cell:error', CellErrorPayloadSchema)
.outgoing('cell:output', CellOutputPayloadSchema)
Expand Down
6 changes: 5 additions & 1 deletion packages/api/session.mts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ export async function exportSrcmdFile(session: SessionType, destinationPath: str
}

export async function findSession(id: string): Promise<SessionType> {
return sessions[id];
if (!sessions[id]) {
throw new Error(`Session with id ${id} not found`);
}
// explicitly known that a session exists here
return sessions[id] as SessionType;
}

type UpdateResultType =
Expand Down
94 changes: 72 additions & 22 deletions packages/api/srcbook/examples/getting-started.src.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,73 +11,123 @@
"random-words": "^2.0.1"
}
}

```

## What are Srcbooks?

Srcbooks are an interactive way of programming in JavaScript or TypeScript. They are similar to other notebooks like python's [jupyter notebooks](https://jupyter.org/), but unique in their own ways.
Srcbooks are an interactive way of programming in JavaScript or TypeScript. They are similar to other notebooks, but unique in their own ways.
They are based on the [node](https://nodejs.org/en) runtime.

A Srcbook is composed of **cells**. Currently, there are 4 types of cells:
1. **Title cell**: this is "Getting started" above. There is one per Srcbook.
2. **package.json cell**: this is a special cell that manages dependencies for the Srcbook.
3. **markdown cell**: what you're reading is a markdown cell. It allows you to easily express ideas with rich markup, rather than code comments, an idea called [literate programming](https://en.wikipedia.org/wiki/Literate_programming).
4. **code cell**: think of these as JS or TS files. You can run them or export objects to be used in other cells.
A Srcbook is composed of **cells**. Currently, there are 2 types of cells:
1. **markdown cell**: what you're reading is a markdown cell. It allows you to easily express ideas with rich markup, rather than code comments, an idea called [literate programming](https://en.wikipedia.org/wiki/Literate_programming).
2. **code cell**: think of these as JS or TS files. You can run them or export objects to be used in other cells.

Srcbooks also hold dependencies in a package.json, and if they are TypeScript Srcbooks, they have a tsconfig.json. You can view these in the ⚙️ Settings menu on the left.

###### simple-code.js

```javascript
// This is a trivial code cell. You can run me by
// clicking 'Run' or using the shortcut `cmd` + `enter`.
// You can run me by clicking 'Run' or using the shortcut `cmd` + `enter`.
console.log("Hello, Srcbook!")
```

## Dependencies
## Using npm libraries

You can add any external node.js-compatible dependency from [npm](https://www.npmjs.com/). Let's look at an example below by importing the `random-words` library.

You'll need to make sure you install dependencies, which you can do by running the `package.json` cell above.
You'll need to make sure you install dependencies, which you can do by clicking the ⚙️ settings in the left menu.

You can install dependencies (think of this as running `npm install` for your Srcbook), by clicking the toast when prompted, or going in the settings and clicking "`npm install`".

###### generate-random-word.js

```javascript
import {generate} from 'random-words';
import { generate } from 'random-words';

console.log(generate())
for (let i = 0; i < 4; i++) {
console.log(`Word ${i + 1}: ${generate()}`);
}
```

## Importing other cells

Behind the scenes, cells are files of JavaScript or TypeScript code. They are ECMAScript 6 modules. Therefore you can export variables from one file and import them in another.
Behind the scenes, cells are ECMAScript 6 modules. Therefore you can export variables from one cell and import them in another:

###### star-wars.js

```javascript
export const func = (name) => `I am your father, ${name}`
export const vaderLine = (name) => `I am your father, ${name}`
```

###### logger.js
###### star-liner.js

```javascript
import {func} from './star-wars.js';
import { vaderLine } from './star-wars.js';

console.log(func("Luke"));
console.log(vaderLine("Luke"));
console.log(vaderLine("Leia"));
```

## Using secrets

For security purposes, you should avoid pasting secrets directly into Srcbooks. The mechanism you should leverage is [secrets](/secrets). These are stored securely and are accessed at runtime as environment variables.
For security purposes, you should avoid pasting secrets directly into Srcbook cells. The mechanism you should leverage is [secrets](/secrets). These are stored securely and are accessed at runtime as environment variables through `process.env`.

Secrets can then be imported in Srcbooks using `process.env.SECRET_NAME`. Try it out by setting the SECRET_API_KEY and getting it to print to the console below:

###### secret-message.js

```javascript
const secret = process.env.SECRET_API_KEY

Secrets can then be imported in Srcbooks using `process.env.SECRET_NAME`:
console.log(secret ? `The secret is: ${secret}` : 'SECRET_API_KEY not set')
```
const API_KEY = process.env.SECRET_API_KEY;
const token = auth(API_KEY);

## Using AI

Srcbook has lots of productive AI features. We designed the AI to be a copilot, leaving you in the driver's seat while allowing you to iterate on code and markdown using natural language if you want to.

First, you'll have to configure a provider in the [global settings](./settings). You can select openAI or anthropic, and pass an API_KEY, or configure a local model through software like [Ollama](https://ollama.com/).

Once set up, you can:
- generate entire Srcbooks from a prompt, like "I want to learn about CRDTs and Y.js" from the home page
- create new cells using AI by giving it a description of what you want
- edit a cell by prompting what changes you want. The AI will present you with a diff that you can approve or reject.

You can see the AI features in action in [this video](https://www.loom.com/share/a212e1fd49a04c548c09125a96a1836f?sid=7bc506ca-fdd1-44b9-b51e-5006ae4248f4).

Try it out by setting up an AI provider, and then click on the Sparkles icon to edit the cell below with AI and ask it to "fix the bug".

###### fix-me.js

```javascript
function factorial(n) {
if (n === 0 || n === 1) {
return 1;
}
return n * factorial(n - 2);
}

console.assert(factorial(5) === 120, 'Factorial of 5 should be 120');
```

## Exporting and sharing Srcbooks

Srcbooks are meant to be collaborative. They export to a Markdown file with the `.src.md` extension, which can be rendered in any editor or UI that supports Markdown.
Srcbooks are meant to be collaborative. They export to a Markdown file format with the `.src.md` extension, which can be rendered in any editor or UI that supports Markdown.

You can export Srcbooks by clicking the `Export` link in the top level menu on the left.

You can also import `.src.md` files directly in this application if you want to run, modify, or re-export them.

## The hub & feedback

We have created a [hub](./https://hub.srcbook.com) with some example Srcbooks that you can import really easily with one command.

You can for example import a Srcbook teaching you how to programmatically take screenshots of websites using pupeteer by running:
```
npx srcbook@latest import web-scraping-with-puppeteer
```

If you want to add your Srcbook to the hub or give us feedback, just email us at feedback@srcbook.com, we love hearing from our users!

Enjoy Srcbook, time to build 🏗️!
Loading

0 comments on commit f28bdc5

Please sign in to comment.