├── .devcontainer
├── devcontainer.json
└── dockerfile
├── .example.env
├── .gitignore
├── LICENSE
├── Makefile
├── dist
├── smol_dev-0.0.3-py3-none-any.whl
└── smol_dev-0.0.3.tar.gz
├── examples
└── v1_pong_game
│ ├── ai.js
│ ├── index.html
│ ├── main.js
│ ├── shared_deps.md
│ └── style.css
├── main.py
├── poetry.lock
├── prompt.md
├── pyproject.toml
├── readme.md
├── smol_dev
├── __init__.py
├── api.py
├── main.py
├── prompts.py
└── utils.py
└── v0
├── code2prompt.py
├── code2prompt
├── code2prompt-gpt3.md
└── code2prompt-gpt4.md
├── code2prompt2code
├── background.js
├── content_script.js
├── icons
│ ├── icon128.png
│ ├── icon16.png
│ └── icon48.png
├── manifest.json
├── popup.html
├── popup.js
├── shared_dependencies.md
└── styles.css
├── constants.py
├── debugger.py
├── debugger_no_modal.py
├── exampleChromeExtension
├── background.js
├── content_script.js
├── icon128.png
├── icon16.png
├── icon48.png
├── manifest.json
├── popup.html
├── popup.js
├── prompt used for this extension.md
├── shared_dependencies.md
└── styles.css
├── main.py
├── main_no_modal.py
├── readme.md
├── static
├── icon128.png
├── icon16.png
├── icon48.png
└── readme.md
├── utils.py
└── v0_readme.md
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Python Project", // The name of your dev container setup, can be anything you choose.
3 | "dockerFile": "Dockerfile", // The path to the Dockerfile that describes your development environment.
4 | "settings": {
5 | "terminal.integrated.shell.linux": "/bin/bash", // Specifies the shell to be used in the integrated terminal in VS Code.
6 | "python.pythonPath": "/usr/local/bin/python", // Specifies the path to the Python interpreter.
7 | "python.linting.pylintEnabled": true, // Enables linting using pylint for Python files.
8 | "python.linting.enabled": true // Enables linting for Python files in general.
9 | },
10 | "extensions": ["ms-python.python"], // Specifies VS Code extensions that should be installed in the dev container when it is created, in this case, the Microsoft Python extension.
11 | "forwardPorts": [], // Specifies any ports that should be forwarded from the dev container to the host.
12 | "postCreateCommand": "pip install -r requirements.txt" // Specifies a command or series of commands to run after the dev container is created.
13 | }
14 |
--------------------------------------------------------------------------------
/.devcontainer/dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1 # Specifies Dockerfile version to use. In this case, version 1.
2 |
3 | FROM python:3.9-slim-buster # Defines the base image to use for your Docker image. Here, it's the slim-buster version of the official Python 3.9 image.
4 |
5 | WORKDIR /app # Sets the working directory in the Docker container. Any command that follows in the Dockerfile will be run in this directory.
6 |
7 | COPY requirements.txt . # Copies the requirements.txt file from your project to the working directory in the Docker image.
8 |
9 | RUN pip install -r requirements.txt # Runs pip install command in your Docker image to install Python dependencies listed in your requirements.txt file.
10 |
11 | COPY . . # Copies everything else in your project (denoted by '.') to the working directory in the Docker image.
12 |
--------------------------------------------------------------------------------
/.example.env:
--------------------------------------------------------------------------------
1 | OPENAI_API_KEY=sk-xxxxxx
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | env
3 | __pycache__
4 | .vscode
5 | .idea
6 | /.idea/
7 | venv/
8 |
9 | # Ignore everything in the generated directory
10 | /generated/*
11 |
12 | # Don't ignore .gitkeep files in the generated directory
13 | !/generated/.gitkeep
14 |
15 | workspace
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License Copyright (c) 2023 swyx
2 |
3 | Permission is hereby granted, free of
4 | charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use, copy, modify, merge,
7 | publish, distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to the
9 | following conditions:
10 |
11 | The above copyright notice and this permission notice
12 | (including the next paragraph) shall be included in all copies or substantial
13 | portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: clean build publish
2 |
3 | build: clean
4 | python -m pip install --upgrade --quiet setuptools wheel twine
5 | python3 -m build
6 | # python setup.py --quiet sdist bdist_wheel
7 |
8 | publish: build
9 | python -m twine check dist/*
10 | # python -m twine upload dist/*
11 | python3 -m twine upload dist/*
12 |
13 | clean:
14 | rm -r build dist *.egg-info || true
--------------------------------------------------------------------------------
/dist/smol_dev-0.0.3-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/dist/smol_dev-0.0.3-py3-none-any.whl
--------------------------------------------------------------------------------
/dist/smol_dev-0.0.3.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/dist/smol_dev-0.0.3.tar.gz
--------------------------------------------------------------------------------
/examples/v1_pong_game/ai.js:
--------------------------------------------------------------------------------
1 | // Define AI's paddle speed
2 | var aiSpeed = 2;
3 |
4 | // Define AI's error rate
5 | var aiError = 0.05;
6 |
7 | /**
8 | * Function to make a decision on the direction to move the AI paddle based on the ball's position and the error factor.
9 | * @param {object} ball - The ball object
10 | * @param {object} aiPaddle - The AI paddle object
11 | */
12 | function aiDecision(ball, aiPaddle) {
13 | // If ball is above the AI paddle and random number is greater than error rate, move the paddle up
14 | if (ball.y < aiPaddle.y && Math.random() > aiError) {
15 | aiPaddle.y -= aiSpeed;
16 | }
17 |
18 | // If ball is below the AI paddle and random number is greater than error rate, move the paddle down
19 | else if (ball.y > aiPaddle.y && Math.random() > aiError) {
20 | aiPaddle.y += aiSpeed;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/v1_pong_game/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
27 |
28 | {summary of the section with important keywords, people, numbers, and facts bolded and key quotes repeated}
29 |
30 |
{first point}: {short explanation with important keywords, people, numbers, and facts bolded}
31 |
{second point}: {same as above}
32 |
{third point}: {same as above}
33 |
34 |
35 |
36 |
{second section here}
37 |
{summary of the section with important keywords, people, numbers, and facts bolded and key quotes repeated}
38 |
39 | {summary of the section with important keywords, people, numbers, and facts bolded and key quotes repeated}
40 |
41 |
42 |
43 |
44 |
{third section here}
45 |
46 |
47 | With all the words in brackets replaced by the summary of the content. sanitize non visual HTML tags with HTML entities, so becomes <template> but stays the same. Only draw from the source content, do not hallucinate. Finally, end with other questions that the user might want answered based on this source content:
48 |
49 |
50 |
Next prompts
51 |
52 |
{question 1}
53 |
{question 2}
54 |
{question 3}
55 |
`;
56 | ```js
57 | - and in the last row, on either side,
58 | - and a nicely styled submit button with an id of `sendButton` (tactile styling that "depresses" on click)
59 | - only when `sendButton` is clicked, calls the Anthropic model endpoint https://api.anthropic.com/v1/complete with:
60 | - append the page title
61 | - append the page content
62 | - add the prompt which is a concatenation of
63 | ```js
64 | finalPrompt = `Human: ${userPrompt} \n\n ${stylePrompt} \n\n Assistant:`
65 | ```
66 | - and use the `claude-instant-v1` model (if `pageContent` is <70k words) or the `claude-instant-v1-100k` model (if more)
67 | - requesting max tokens = the higher of (25% of the length of the page content, or 750 words)
68 | - if another submit event is hit while the previous api call is still inflight, cancel that and start the new one
69 | - renders the Anthropic-generated result at the top of the popup in a div with an id of `content`
70 |
71 | Important Details:
72 |
73 | - It has to run in a browser environment, so no Nodejs APIs allowed.
74 |
75 | - the return signature of the anthropic api is curl https://api.anthropic.com/v1/complete\
76 | -H "x-api-key: $API_KEY"\
77 | -H 'content-type: application/json'\
78 | -d '{
79 | "prompt": "\n\nHuman: Tell me a haiku about trees\n\nAssistant: ",
80 | "model": "claude-v1", "max_tokens_to_sample": 1000, "stop_sequences": ["\n\nHuman:"]
81 | }'
82 | {"completion":" Here is a haiku about trees:\n\nSilent sentinels, \nStanding solemn in the woods,\nBranches reaching sky.","stop":"\n\nHuman:","stop_reason":"stop_sequence","truncated":false,"log_id":"f5d95cf326a4ac39ee36a35f434a59d5","model":"claude-v1","exception":null}
83 |
84 | - in the string prompt sent to Anthropic, first include the page title and page content, and finally append the prompt, clearly vertically separated by spacing.
85 |
86 | - if the Anthropic api call is a 401, handle that by clearing the stored anthropic api key and asking for it again.
87 |
88 | - add styles to make sure the popup's styling follows the basic rules of web design, for example having margins around the body, and a system font stack.
89 |
90 | - style the popup body with but insist on body margins of 16 and a minimum width of 400 and height of 600.
91 |
92 | ## debugging notes
93 |
94 | inside of background.js, just take the getPageContent response directly
95 |
96 | ```js
97 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
98 | if (request.action === 'storePageContent') {
99 | // dont access request.pageContent
100 | chrome.storage.local.set({ pageContent: request }, () => {
101 | sendResponse({ success: true });
102 | });
103 | } else if (request.action === 'getPageContent') {
104 | chrome.storage.local.get(['pageContent'], (result) => {
105 | // dont access request.pageContent
106 | sendResponse(result);
107 | });
108 | }
109 | return true;
110 | });
111 | ```
112 |
113 | inside of popup.js, Update the function calls to `requestAnthropicSummary`
114 | in `popup.js` to pass the `apiKey`:
115 |
116 | ```javascript
117 | chrome.storage.local.get(['apiKey'], (result) => {
118 | const apiKey = result.apiKey;
119 | requestAnthropicSummary(defaultPrompt, apiKey);
120 | });
121 |
122 | sendButton.addEventListener('click', () => {
123 | chrome.storage.local.get(['apiKey'], (result) => {
124 | const apiKey = result.apiKey;
125 | requestAnthropicSummary(userPrompt.value, apiKey);
126 | });
127 | });
128 | ```
129 |
130 | in `popup.js`, store the defaultPrompt at the top level.
131 | also, give a HTML format to the anthropic prompt
132 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "smol_dev"
3 | version = "0.0.3"
4 | description = "python module of smol developer"
5 | authors = ["swyx "]
6 | license = "MIT"
7 | readme = "readme.md"
8 | packages = [{ include = "smol_dev" }]
9 |
10 | [tool.poetry.dependencies]
11 | python = ">=3.10,<4.0.0"
12 | openai = "^0.27.8"
13 | openai-function-call = "^0.0.5"
14 | tenacity = "^8.2.2"
15 | agent-protocol = "^1.0.0"
16 |
17 | [build-system]
18 | requires = ["poetry-core"]
19 | build-backend = "poetry.core.masonry.api"
20 |
21 | [tool.poetry.scripts]
22 | src = "src.__main__:main"
23 | api = "smol_dev.api:main"
24 |
25 | [project.urls]
26 | "Homepage" = "https://github.com/smol-ai/developer"
27 | "Bug Tracker" = "https://github.com/smol-ai/developer/issues"
28 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # 🐣 smol developer
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Morph
12 |
13 |
14 | ***Human-centric & Coherent Whole Program Synthesis*** aka your own personal junior developer
15 |
16 | > [Build the thing that builds the thing!](https://twitter.com/swyx/status/1657578738345979905) a `smol dev` for every dev in every situation
17 |
18 | This is a "junior developer" agent (aka `smol dev`) that either:
19 |
20 | 1. scaffolds an entire codebase out for you once you give it a product spec
21 | 2. gives you basic building blocks to have a smol developer inside of your own app.
22 |
23 | Instead of making and maintaining specific, rigid, one-shot starters, like `create-react-app`, or `create-nextjs-app`, this is basically is or helps you make [`create-anything-app`](https://news.ycombinator.com/item?id=35942352) where you develop your scaffolding prompt in a tight loop with your smol dev.
24 |
25 | After the [successful initial v0 launch](https://twitter.com/swyx/status/1657578738345979905), smol developer was rewritten to be **even smol-ler**, and importable from a library!
26 |
27 | ## Basic Usage
28 |
29 | ### In Git Repo mode
30 |
31 | ```bash
32 | # install
33 | git clone https://github.com/smol-ai/developer.git
34 | cd developer
35 | poetry install # install dependencies. pip install poetry if you need
36 |
37 | # run
38 | python main.py "a HTML/JS/CSS Tic Tac Toe Game" # defaults to gpt-4-0613
39 | # python main.py "a HTML/JS/CSS Tic Tac Toe Game" --model=gpt-3.5-turbo-0613
40 |
41 | # other cli flags
42 | python main.py --prompt prompt.md # for longer prompts, move them into a markdown file
43 | python main.py --prompt prompt.md --debug True # for debugging
44 | ```
45 |
46 |
47 |
48 | This lets you develop apps as a human in the loop, as per the original version of smol developer.
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | *engineering with prompts, rather than prompt engineering*
57 |
58 | The demo example in `prompt.md` shows the potential of AI-enabled, but still firmly human developer centric, workflow:
59 |
60 | - Human writes a basic prompt for the app they want to build
61 | - `main.py` generates code
62 | - Human runs/reads the code
63 | - Human can:
64 | - simply add to the prompt as they discover underspecified parts of the prompt
65 | - manually runs the code and identifies errors
66 | - *paste the error into the prompt* just like they would file a GitHub issue
67 | - for extra help, they can use `debugger.py` which reads the whole codebase to make specific code change suggestions
68 |
69 | Loop until happiness is attained. Notice that AI is only used as long as it is adding value - once it gets in your way, just take over the codebase from your smol junior developer with no fuss and no hurt feelings. (*we could also have smol-dev take over an existing codebase and bootstrap its own prompt... but that's a Future Direction*)
70 |
71 |
72 |
73 | In this way you can use your clone of this repo itself to prototype/develop your app.
74 |
75 | ### In Library mode
76 |
77 | This is the new thing in smol developer v1! Add `smol developer` to your own projects!
78 |
79 | ```bash
80 | pip install smol_dev
81 | ```
82 |
83 | Here you can basically look at the contents of `main.py` as our "documentation" of how you can use these functions and prompts in your own app:
84 |
85 | ```python
86 | from smol_dev.prompts import plan, specify_file_paths, generate_code_sync
87 |
88 | prompt = "a HTML/JS/CSS Tic Tac Toe Game"
89 |
90 | shared_deps = plan(prompt) # returns a long string representing the coding plan
91 |
92 | # do something with the shared_deps plan if you wish, for example ask for user confirmation/edits and iterate in a loop
93 |
94 | file_paths = specify_file_paths(prompt, shared_deps) # returns an array of strings representing the filenames it needs to write based on your prompt and shared_deps. Relies on OpenAI's new Function Calling API to guarantee JSON.
95 |
96 | # do something with the filepaths if you wish, for example display a plan
97 |
98 | # loop through file_paths array and generate code for each file
99 | for file_path in file_paths:
100 | code = generate_code_sync(prompt, shared_deps, file_path) # generates the source code of each file
101 |
102 | # do something with the source code of the file, eg. write to disk or display in UI
103 | # there is also an async `generate_code()` version of this
104 | ```
105 |
106 | ### In API mode (via [Agent Protocol](https://github.com/e2b-dev/agent-protocol))
107 | To start the server run:
108 | ```bash
109 | poetry run api
110 | ```
111 | or
112 | ```bash
113 | python smol_dev/api.py
114 | ```
115 |
116 | and then you can call the API using either the following commands:
117 |
118 | To **create a task** run:
119 | ```bash
120 | curl --request POST \
121 | --url http://localhost:8000/agent/tasks \
122 | --header 'Content-Type: application/json' \
123 | --data '{
124 | "input": "Write simple script in Python. It should write '\''Hello world!'\'' to hi.txt"
125 | }'
126 | ```
127 |
128 | You will get a response like this:
129 | ```json
130 | {"input":"Write simple script in Python. It should write 'Hello world!' to hi.txt","task_id":"d2c4e543-ae08-4a97-9ac5-5f9a4459cb19","artifacts":[]}
131 | ```
132 |
133 | Then to **execute one step of the task** copy the `task_id` you got from the previous request and run:
134 |
135 | ```bash
136 | curl --request POST \
137 | --url http://localhost:8000/agent/tasks//steps
138 | ```
139 |
140 | or you can use [Python client library](https://github.com/e2b-dev/agent-protocol/tree/main/agent_client/python):
141 |
142 | ```python
143 | from agent_protocol_client import AgentApi, ApiClient, TaskRequestBody
144 |
145 | ...
146 |
147 | prompt = "Write simple script in Python. It should write 'Hello world!' to hi.txt"
148 |
149 | async with ApiClient() as api_client:
150 | # Create an instance of the API class
151 | api_instance = AgentApi(api_client)
152 | task_request_body = TaskRequestBody(input=prompt)
153 |
154 | task = await api_instance.create_agent_task(
155 | task_request_body=task_request_body
156 | )
157 | task_id = task.task_id
158 | response = await api_instance.execute_agent_task_step(task_id=task_id)
159 |
160 | ...
161 |
162 | ```
163 |
164 | ## examples/prompt gallery
165 |
166 | - [6 minute video demo](https://youtu.be/UCo7YeTy-aE) - (sorry for sped up audio, we were optimizing for twitter, bad call)
167 | - this was the original smol developer demo - going from prompt to full chrome extension that requests and stores and apikey, generates a popup window, reads and transmits page content, and usefully summarizes any website with Anthropic Claude, switching models up to the 100k one based on length of input
168 | - the prompt is located in [prompt.md](https://github.com/smol-ai/developer/blob/main/prompt.md) and it outputs [/exampleChromeExtension](https://github.com/smol-ai/developer/tree/main/examples/exampleChromeExtension)
169 | - `smol-plugin` - prompt to ChatGPT plugin ([tweet](https://twitter.com/ultrasoundchad/status/1659366507409985536?s=20), [fork](https://github.com/gmchad/smol-plugin))
170 |
171 |
172 |
173 | - [Prompt to Pokemon App](https://twitter.com/RobertCaracaus/status/1659312419485761536?s=20)
174 |
175 |
176 |
177 | - [Political Campaign CRM Program example](https://github.com/smol-ai/developer/pull/22/files)
178 | - [Lessons from Creating a VSCode Extension with GPT-4](https://bit.kevinslin.com/p/leveraging-gpt-4-to-automate-the) (also on [HN](https://news.ycombinator.com/item?id=36071342))
179 | - [7 min Video: Smol AI Developer - Build ENTIRE Codebases With A Single Prompt](https://www.youtube.com/watch?v=DzRoYc2UGKI) produces a full working OpenAI CLI python app from a prompt
180 |
181 |
182 |
183 | - [12 min Video: SMOL AI - Develop Large Scale Apps with AGI in one click](https://www.youtube.com/watch?v=zsxyqz6SYp8) scaffolds a surprisingly complex React/Node/MongoDB full stack app in 40 minutes and $9
184 |
185 |
186 |
187 | I'm actively seeking more examples, please PR yours!
188 |
189 | sorry for the lack of examples, I know that is frustrating but I wasnt ready for so many of you lol
190 |
191 | ## major forks/alternatives
192 |
193 | please send in alternative implementations, and deploy strategies on alternative stacks!
194 |
195 | - **JS/TS**: https://github.com/PicoCreator/smol-dev-js A pure JS variant of smol-dev, allowing even smoler incremental changes via prompting (if you dun want to do the whole spec2code thing), allowing you to plug it into any project live (for better or worse)
196 | - **C#/Dotnet**: https://github.com/colhountech/smol-ai-dotnet in C#!
197 | - **Golang**: https://github.com/tmc/smol-dev-go in Go
198 | - https://github.com/gmchad/smol-plugin automatically generate @openai plugins by specifying your API in markdown in smol-developer style
199 | - your fork here!
200 |
201 |
202 | ### innovations and insights
203 |
204 | > Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections
205 |
206 | - **Markdown is all you need** - Markdown is the perfect way to prompt for whole program synthesis because it is easy to mix english and code (whether `variable_names` or entire \`\`\` code fenced code samples)
207 | - turns out you can specify prompts in code in prompts and gpt4 obeys that to the letter
208 | - **Copy and paste programming**
209 | - teaching the program to understand how to code around a new API (Anthropic's API is after GPT3's knowledge cutoff) by just pasting in the `curl` input and output
210 | - pasting error messages into the prompt and vaguely telling the program how you'd like it handled. it kind of feels like "logbook driven programming".
211 | - **Debugging by `cat`ing** the whole codebase with your error message and getting specific fix suggestions - particularly delightful!
212 | - **Tricks for whole program coherence** - our chosen example usecase, Chrome extensions, have a lot of indirect dependencies across files. Any hallucination of cross dependencies causes the whole program to error.
213 | - We solved this by adding an intermediate step asking GPT to think through `shared_dependencies.md`, and then insisting on using that in generating each file. This basically means GPT is able to talk to itself...
214 | - ... but it's not perfect, yet. `shared_dependencies.md` is sometimes not comperehensive in understanding what are hard dependencies between files. So we just solved it by specifying a specific `name` in the prompt. felt dirty at first but it works, and really it's just clear unambiguous communication at the end of the day.
215 | - see `prompt.md` for SOTA smol-dev prompting
216 | - **Low activation energy for unfamiliar APIs**
217 | - we have never really learned css animations, but now can just say we want a "juicy css animated red and white candy stripe loading indicator" and it does the thing.
218 | - ditto for Chrome Extension Manifest v3 - the docs are an abject mess, but fortunately we don't have to read them now to just get a basic thing done
219 | - the Anthropic docs (bad bad) were missing guidance on what return signature they have. so just curl it and dump it in the prompt lol.
220 | - **Modal is all you need** - we chose Modal to solve 4 things:
221 | - solve python dependency hell in dev and prod
222 | - parallelizable code generation
223 | - simple upgrade path from local dev to cloud hosted endpoints (in future)
224 | - fault tolerant openai api calls with retries/backoff, and attached storage (for future use)
225 |
226 | > Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections
227 |
228 | ### caveats
229 |
230 | We were working on a Chrome Extension, which requires images to be generated, so we added some usecase specific code in there to skip destroying/regenerating them, that we haven't decided how to generalize.
231 |
232 | We dont have access to GPT4-32k, but if we did, we'd explore dumping entire API/SDK documentation into context.
233 |
234 | The feedback loop is very slow right now (`time` says about 2-4 mins to generate a program with GPT4, even with parallelization due to Modal (occasionally spiking higher)), but it's a safe bet that it will go down over time (see also "future directions" below).
235 |
236 |
237 | ## future directions
238 |
239 | things to try/would accept open issue discussions and PRs:
240 |
241 | - **specify .md files for each generated file**, with further prompts that could finetune the output in each of them
242 | - so basically like `popup.html.md` and `content_script.js.md` and so on
243 | - **bootstrap the `prompt.md`** for existing codebases - write a script to read in a codebase and write a descriptive, bullet pointed prompt that generates it
244 | - done by `smol pm`, but its not very good yet - would love for some focused polish/effort until we have quine smol developer that can generate itself lmao
245 | - **ability to install its own dependencies**
246 | - this leaks into depending on the execution environment, which we all know is the path to dependency madness. how to avoid? dockerize? nix? [web container](https://twitter.com/litbid/status/1658154530385670150)?
247 | - Modal has an interesting possibility: generate functions that speak modal which also solves the dependency thing https://twitter.com/akshat_b/status/1658146096902811657
248 | - **self-heal** by running the code itself and use errors as information for reprompting
249 | - however its a bit hard to get errors from the chrome extension environment so we did not try this
250 | - **using anthropic as the coding layer**
251 | - you can run `modal run anthropic.py --prompt prompt.md --outputdir=anthropic` to try it
252 | - but it doesnt work because anthropic doesnt follow instructions to generate file code very well.
253 | - **make agents that autonomously run this code in a loop/watch the prompt file** and regenerate code each time, on a new git branch
254 | - the code could be generated on 5 simultaneous git branches and checking their output would just involve switching git branches
255 |
--------------------------------------------------------------------------------
/smol_dev/__init__.py:
--------------------------------------------------------------------------------
1 | from smol_dev.prompts import *
2 | __author__ = "morph"
3 |
--------------------------------------------------------------------------------
/smol_dev/api.py:
--------------------------------------------------------------------------------
1 | import enum
2 | import os
3 | from pathlib import Path
4 |
5 | from smol_dev.prompts import plan, specify_file_paths, generate_code
6 | from smol_dev.utils import write_file
7 |
8 | from agent_protocol import Agent, Step, Task
9 |
10 |
11 | class StepTypes(str, enum.Enum):
12 | PLAN = "plan"
13 | SPECIFY_FILE_PATHS = "specify_file_paths"
14 | GENERATE_CODE = "generate_code"
15 |
16 |
17 | async def _generate_shared_deps(step: Step) -> Step:
18 | task = await Agent.db.get_task(step.task_id)
19 | shared_deps = plan(task.input)
20 | await Agent.db.create_step(
21 | step.task_id,
22 | StepTypes.SPECIFY_FILE_PATHS,
23 | additional_properties={
24 | "shared_deps": shared_deps,
25 | },
26 | )
27 | step.output = shared_deps
28 | return step
29 |
30 |
31 | async def _generate_file_paths(task: Task, step: Step) -> Step:
32 | shared_deps = step.additional_properties["shared_deps"]
33 | file_paths = specify_file_paths(task.input, shared_deps)
34 | for file_path in file_paths[:-1]:
35 | await Agent.db.create_step(
36 | task.task_id,
37 | f"Generate code for {file_path}",
38 | additional_properties={
39 | "shared_deps": shared_deps,
40 | "file_path": file_paths[-1],
41 | },
42 | )
43 |
44 | await Agent.db.create_step(
45 | task.task_id,
46 | f"Generate code for {file_paths[-1]}",
47 | is_last=True,
48 | additional_properties={
49 | "shared_deps": shared_deps,
50 | "file_path": file_paths[-1],
51 | },
52 | )
53 |
54 | step.output = f"File paths are: {str(file_paths)}"
55 | return step
56 |
57 |
58 | async def _generate_code(task: Task, step: Step) -> Step:
59 | shared_deps = step.additional_properties["shared_deps"]
60 | file_path = step.additional_properties["file_path"]
61 |
62 | code = await generate_code(task.input, shared_deps, file_path)
63 | step.output = code
64 |
65 | write_file(os.path.join(Agent.get_workspace(task.task_id), file_path), code)
66 | path = Path("./" + file_path)
67 | await Agent.db.create_artifact(
68 | task_id=task.task_id,
69 | step_id=step.step_id,
70 | relative_path=str(path.parent),
71 | file_name=path.name,
72 | )
73 |
74 | return step
75 |
76 |
77 | async def task_handler(task: Task) -> None:
78 | if not task.input:
79 | raise Exception("No task prompt")
80 | await Agent.db.create_step(task.task_id, StepTypes.PLAN)
81 |
82 |
83 | async def step_handler(step: Step):
84 | task = await Agent.db.get_task(step.task_id)
85 | if step.name == StepTypes.PLAN:
86 | return await _generate_shared_deps(step)
87 | elif step.name == StepTypes.SPECIFY_FILE_PATHS:
88 | return await _generate_file_paths(task, step)
89 | else:
90 | return await _generate_code(task, step)
91 |
92 |
93 | Agent.setup_agent(task_handler, step_handler).start()
94 |
--------------------------------------------------------------------------------
/smol_dev/main.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import time
3 |
4 | from smol_dev.prompts import plan, specify_file_paths, generate_code_sync
5 | from smol_dev.utils import generate_folder, write_file
6 | import argparse
7 |
8 | # model = "gpt-3.5-turbo-0613"
9 | defaultmodel = "gpt-4-0613"
10 |
11 | def main(prompt, generate_folder_path="generated", debug=False, model: str = defaultmodel):
12 | # create generateFolder folder if doesnt exist
13 | generate_folder(generate_folder_path)
14 |
15 | # plan shared_deps
16 | if debug:
17 | print("--------shared_deps---------")
18 | with open(f"{generate_folder_path}/shared_deps.md", "wb") as f:
19 |
20 | start_time = time.time()
21 | def stream_handler(chunk):
22 | f.write(chunk)
23 | if debug:
24 | end_time = time.time()
25 |
26 | sys.stdout.write("\r \033[93mChars streamed\033[0m: {}. \033[93mChars per second\033[0m: {:.2f}".format(stream_handler.count, stream_handler.count / (end_time - start_time)))
27 | sys.stdout.flush()
28 | stream_handler.count += len(chunk)
29 |
30 | stream_handler.count = 0
31 | stream_handler.onComplete = lambda x: sys.stdout.write("\033[0m\n") # remove the stdout line when streaming is complete
32 |
33 | shared_deps = plan(prompt, stream_handler, model=model)
34 | if debug:
35 | print(shared_deps)
36 | write_file(f"{generate_folder_path}/shared_deps.md", shared_deps)
37 | if debug:
38 | print("--------shared_deps---------")
39 |
40 | # specify file_paths
41 | if debug:
42 | print("--------specify_filePaths---------")
43 | file_paths = specify_file_paths(prompt, shared_deps, model=model)
44 | if debug:
45 | print(file_paths)
46 | if debug:
47 | print("--------file_paths---------")
48 |
49 | # loop through file_paths array and generate code for each file
50 | for file_path in file_paths:
51 | file_path = f"{generate_folder_path}/{file_path}" # just append prefix
52 | if debug:
53 | print(f"--------generate_code: {file_path} ---------")
54 |
55 | start_time = time.time()
56 | def stream_handler(chunk):
57 | if debug:
58 | end_time = time.time()
59 | sys.stdout.write("\r \033[93mChars streamed\033[0m: {}. \033[93mChars per second\033[0m: {:.2f}".format(stream_handler.count, stream_handler.count / (end_time - start_time)))
60 | sys.stdout.flush()
61 | stream_handler.count += len(chunk)
62 | stream_handler.count = 0
63 | stream_handler.onComplete = lambda x: sys.stdout.write("\033[0m\n") # remove the stdout line when streaming is complete
64 | code = generate_code_sync(prompt, shared_deps, file_path, stream_handler, model=model)
65 | if debug:
66 | print(code)
67 | if debug:
68 | print(f"--------generate_code: {file_path} ---------")
69 | # create file with code content
70 | write_file(file_path, code)
71 |
72 | print("--------smol dev done!---------")
73 |
74 |
75 | # for local testing
76 | # python main.py --prompt "a simple JavaScript/HTML/CSS/Canvas app that is a one player game of PONG..." --generate_folder_path "generated" --debug True
77 |
78 | if __name__ == "__main__":
79 | prompt = """
80 | a simple JavaScript/HTML/CSS/Canvas app that is a one player game of PONG.
81 | The left paddle is controlled by the player, following where the mouse goes.
82 | The right paddle is controlled by a simple AI algorithm, which slowly moves the paddle toward the ball at every frame, with some probability of error.
83 | Make the canvas a 400 x 400 black square and center it in the app.
84 | Make the paddles 100px long, yellow and the ball small and red.
85 | Make sure to render the paddles and name them so they can controlled in javascript.
86 | Implement the collision detection and scoring as well.
87 | Every time the ball bouncess off a paddle, the ball should move faster.
88 | It is meant to run in Chrome browser, so dont use anything that is not supported by Chrome, and don't use the import and export keywords.
89 | """
90 | if len(sys.argv) == 2:
91 | prompt = sys.argv[1]
92 | else:
93 |
94 | parser = argparse.ArgumentParser()
95 | parser.add_argument("--prompt", type=str, required=True, help="Prompt for the app to be created.")
96 | parser.add_argument("--generate_folder_path", type=str, default="generated", help="Path of the folder for generated code.")
97 | parser.add_argument("--debug", type=bool, default=False, help="Enable or disable debug mode.")
98 | args = parser.parse_args()
99 | if args.prompt:
100 | prompt = args.prompt
101 |
102 | print(prompt)
103 |
104 | main(prompt=prompt, generate_folder_path=args.generate_folder_path, debug=args.debug)
105 |
--------------------------------------------------------------------------------
/smol_dev/prompts.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import re
3 | import time
4 | from typing import List, Optional, Callable, Any
5 |
6 | import openai
7 | from openai_function_call import openai_function
8 | from tenacity import (
9 | retry,
10 | stop_after_attempt,
11 | wait_random_exponential,
12 | )
13 | import logging
14 |
15 | logger = logging.getLogger(__name__)
16 |
17 |
18 | SMOL_DEV_SYSTEM_PROMPT = """
19 | You are a top tier AI developer who is trying to write a program that will generate code for the user based on their intent.
20 | Do not leave any todos, fully implement every feature requested.
21 |
22 | When writing code, add comments to explain what you intend to do and why it aligns with the program plan and specific instructions from the original prompt.
23 | """
24 |
25 |
26 | @openai_function
27 | def file_paths(files_to_edit: List[str]) -> List[str]:
28 | """
29 | Construct a list of strings.
30 | """
31 | # print("filesToEdit", files_to_edit)
32 | return files_to_edit
33 |
34 |
35 | def specify_file_paths(prompt: str, plan: str, model: str = 'gpt-3.5-turbo-0613'):
36 | completion = openai.ChatCompletion.create(
37 | model=model,
38 | temperature=0.7,
39 | functions=[file_paths.openai_schema],
40 | function_call={"name": "file_paths"},
41 | messages=[
42 | {
43 | "role": "system",
44 | "content": f"""{SMOL_DEV_SYSTEM_PROMPT}
45 | Given the prompt and the plan, return a list of strings corresponding to the new files that will be generated.
46 | """,
47 | },
48 | {
49 | "role": "user",
50 | "content": f""" I want a: {prompt} """,
51 | },
52 | {
53 | "role": "user",
54 | "content": f""" The plan we have agreed on is: {plan} """,
55 | },
56 | ],
57 | )
58 | result = file_paths.from_response(completion)
59 | return result
60 |
61 |
62 | def plan(prompt: str, stream_handler: Optional[Callable[[bytes], None]] = None, model: str='gpt-3.5-turbo-0613', extra_messages: List[Any] = []):
63 | completion = openai.ChatCompletion.create(
64 | model=model,
65 | temperature=0.7,
66 | stream=True,
67 | messages=[
68 | {
69 | "role": "system",
70 | "content": f"""{SMOL_DEV_SYSTEM_PROMPT}
71 |
72 | In response to the user's prompt, write a plan using GitHub Markdown syntax. Begin with a YAML description of the new files that will be created.
73 | In this plan, please name and briefly describe the structure of code that will be generated, including, for each file we are generating, what variables they export, data schemas, id names of every DOM elements that javascript functions will use, message names, and function names.
74 | Respond only with plans following the above schema.
75 | """,
76 | },
77 | {
78 | "role": "user",
79 | "content": f""" the app prompt is: {prompt} """,
80 | },
81 | *extra_messages,
82 | ],
83 | )
84 |
85 | collected_messages = []
86 | for chunk in completion:
87 | chunk_message_dict = chunk["choices"][0]
88 | chunk_message = chunk_message_dict["delta"] # extract the message
89 | if chunk_message_dict["finish_reason"] is None:
90 | collected_messages.append(chunk_message) # save the message
91 | if stream_handler:
92 | try:
93 | stream_handler(chunk_message["content"].encode("utf-8"))
94 | except Exception as err:
95 | logger.info("\nstream_handler error:", err)
96 | logger.info(chunk_message)
97 | # if stream_handler and hasattr(stream_handler, "onComplete"): stream_handler.onComplete('done')
98 | full_reply_content = "".join([m.get("content", "") for m in collected_messages])
99 | return full_reply_content
100 |
101 |
102 | @retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
103 | async def generate_code(prompt: str, plan: str, current_file: str, stream_handler: Optional[Callable[Any, Any]] = None,
104 | model: str = 'gpt-3.5-turbo-0613') -> str:
105 | first = True
106 | chunk_count = 0
107 | start_time = time.time()
108 | completion = openai.ChatCompletion.acreate(
109 | model=model,
110 | temperature=0.7,
111 | messages=[
112 | {
113 | "role": "system",
114 | "content": f"""{SMOL_DEV_SYSTEM_PROMPT}
115 |
116 | In response to the user's prompt,
117 | Please name and briefly describe the structure of the app we will generate, including, for each file we are generating, what variables they export, data schemas, id names of every DOM elements that javascript functions will use, message names, and function names.
118 |
119 | We have broken up the program into per-file generation.
120 | Now your job is to generate only the code for the file: {current_file}
121 |
122 | only write valid code for the given filepath and file type, and return only the code.
123 | do not add any other explanation, only return valid code for that file type.
124 | """,
125 | },
126 | {
127 | "role": "user",
128 | "content": f""" the plan we have agreed on is: {plan} """,
129 | },
130 | {
131 | "role": "user",
132 | "content": f""" the app prompt is: {prompt} """,
133 | },
134 | {
135 | "role": "user",
136 | "content": f"""
137 | Make sure to have consistent filenames if you reference other files we are also generating.
138 |
139 | Remember that you must obey 3 things:
140 | - you are generating code for the file {current_file}
141 | - do not stray from the names of the files and the plan we have decided on
142 | - MOST IMPORTANT OF ALL - every line of code you generate must be valid code. Do not include code fences in your response, for example
143 |
144 | Bad response (because it contains the code fence):
145 | ```javascript
146 | console.log("hello world")
147 | ```
148 |
149 | Good response (because it only contains the code):
150 | console.log("hello world")
151 |
152 | Begin generating the code now.
153 |
154 | """,
155 | },
156 | ],
157 | stream=True,
158 | )
159 |
160 | collected_messages = []
161 | async for chunk in await completion:
162 | chunk_message_dict = chunk["choices"][0]
163 | chunk_message = chunk_message_dict["delta"] # extract the message
164 | if chunk_message_dict["finish_reason"] is None:
165 | collected_messages.append(chunk_message) # save the message
166 | if stream_handler:
167 | try:
168 | stream_handler(chunk_message["content"].encode("utf-8"))
169 | except Exception as err:
170 | logger.info("\nstream_handler error:", err)
171 | logger.info(chunk_message)
172 |
173 | # if stream_handler and hasattr(stream_handler, "onComplete"): stream_handler.onComplete('done')
174 | code_file = "".join([m.get("content", "") for m in collected_messages])
175 |
176 | pattern = r"```[\w\s]*\n([\s\S]*?)```" # codeblocks at start of the string, less eager
177 | code_blocks = re.findall(pattern, code_file, re.MULTILINE)
178 | return code_blocks[0] if code_blocks else code_file
179 |
180 |
181 | def generate_code_sync(prompt: str, plan: str, current_file: str,
182 | stream_handler: Optional[Callable[Any, Any]] = None,
183 | model: str = 'gpt-3.5-turbo-0613') -> str:
184 | loop = asyncio.get_event_loop()
185 | return loop.run_until_complete(generate_code(prompt, plan, current_file, stream_handler, model))
186 |
--------------------------------------------------------------------------------
/smol_dev/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 |
4 |
5 | def generate_folder(folder_path: str):
6 | if not os.path.exists(folder_path):
7 | os.makedirs(folder_path)
8 | else:
9 | shutil.rmtree(folder_path)
10 | os.makedirs(folder_path)
11 |
12 |
13 | def write_file(file_path: str, content: str):
14 | # if filepath doesn't exist, create it
15 | if not os.path.exists(os.path.dirname(file_path)):
16 | os.makedirs(os.path.dirname(file_path))
17 | with open(file_path, "w") as f:
18 | f.write(content)
19 |
--------------------------------------------------------------------------------
/v0/code2prompt.py:
--------------------------------------------------------------------------------
1 | import modal
2 | import os
3 | from constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, EXTENSION_TO_SKIP
4 |
5 | stub = modal.Stub("smol-codetoprompt-v1")
6 | openai_image = modal.Image.debian_slim().pip_install("openai")
7 |
8 |
9 |
10 | def read_file(filename):
11 | with open(filename, 'r') as file:
12 | return file.read()
13 |
14 | def walk_directory(directory):
15 | code_contents = {}
16 | for root, dirs, files in os.walk(directory):
17 | for file in files:
18 | if not any(file.endswith(ext) for ext in EXTENSION_TO_SKIP):
19 | try:
20 | relative_filepath = os.path.relpath(os.path.join(root, file), directory)
21 | code_contents[relative_filepath] = read_file(os.path.join(root, file))
22 | except Exception as e:
23 | code_contents[relative_filepath] = f"Error reading file {file}: {str(e)}"
24 | return code_contents
25 |
26 |
27 |
28 | @stub.local_entrypoint()
29 | def main(prompt=None, directory=DEFAULT_DIR, model=DEFAULT_MODEL):
30 | code_contents = walk_directory(directory)
31 |
32 | # Now, `code_contents` is a dictionary that contains the content of all your non-image files
33 | # You can send this to OpenAI's text-davinci-003 for help
34 |
35 | context = "\n".join(f"{path}:\n{contents}" for path, contents in code_contents.items())
36 | system = "You are an AI debugger who is trying to fully describe a program, in order for another AI program to reconstruct every file, data structure, function and functionality. The user has provided you with the following files and their contents:"
37 | prompt = "My files are as follows: " + context + "\n\n" + (("Take special note of the following: " + prompt) if prompt else "")
38 | prompt += "\n\nDescribe the program in markdown using specific language that will help another AI program reconstruct the given program in as high fidelity as possible."
39 | res = generate_response.call(system, prompt, model)
40 | # print res in teal
41 | print("\033[96m" + res + "\033[0m")
42 |
43 |
44 | @stub.function(
45 | image=openai_image,
46 | secret=modal.Secret.from_dotenv(),
47 | retries=modal.Retries(
48 | max_retries=3,
49 | backoff_coefficient=2.0,
50 | initial_delay=1.0,
51 | ),
52 | concurrency_limit=5,
53 | timeout=120,
54 | )
55 | def generate_response(system_prompt, user_prompt, model=DEFAULT_MODEL, *args):
56 | import openai
57 |
58 | # Set up your OpenAI API credentials
59 | openai.api_key = os.environ["OPENAI_API_KEY"]
60 |
61 | messages = []
62 | messages.append({"role": "system", "content": system_prompt})
63 | messages.append({"role": "user", "content": user_prompt})
64 | # loop thru each arg and add it to messages alternating role between "assistant" and "user"
65 | role = "assistant"
66 | for value in args:
67 | messages.append({"role": role, "content": value})
68 | role = "user" if role == "assistant" else "assistant"
69 |
70 | params = {
71 | 'model': model,
72 | "messages": messages,
73 | "max_tokens": 2500,
74 | "temperature": 0,
75 | }
76 |
77 | # Send the API request
78 | response = openai.ChatCompletion.create(**params)
79 |
80 | # Get the reply from the API response
81 | reply = response.choices[0]["message"]["content"]
82 | return reply
--------------------------------------------------------------------------------
/v0/code2prompt/code2prompt-gpt3.md:
--------------------------------------------------------------------------------
1 | The program is a Chrome extension that summarizes web pages using the Anthropic Claude API. It consists of several files: popup.js, styles.css,
2 | background.js, popup.html, shared_dependencies.md, and content_script.js.
3 |
4 | popup.html is the main user interface for the extension, containing a form with several input fields and a submit button. popup.js handles the logic
5 | for the form, including retrieving the user's input, calling the Anthropic API, and rendering the summary in the content div. styles.css provides
6 | styling for the UI elements.
7 |
8 | background.js is responsible for executing content_script.js, which retrieves the page content (title and body text) and sends it to popup.js for
9 | processing. It also handles storing and retrieving the page content data using Chrome's storage API.
10 |
11 | shared_dependencies.md lists the shared variables, data schemas, DOM element IDs, message names, and function names used across the various files.
12 |
13 | Overall, the program uses a combination of JavaScript, HTML, and CSS to provide a user-friendly interface for summarizing web pages using the
14 | Anthropic Claude API.
--------------------------------------------------------------------------------
/v0/code2prompt/code2prompt-gpt4.md:
--------------------------------------------------------------------------------
1 | # Anthropic Claude Summary Extension
2 |
3 | A Chrome extension that summarizes web pages using the
4 | Anthropic Claude API.
5 |
6 | ## Files
7 |
8 | ### popup.js
9 |
10 | This file contains the main logic for the popup window of the
11 | extension. It listens for the DOMContentLoaded event and
12 | initializes the following DOM elements:
13 |
14 | - `userPrompt`: A textarea for the user to input a prompt.
15 | - `stylePrompt`: A textarea for the user to input a style
16 | prompt.
17 | - `maxTokens`: An input field for the user to set the maximum
18 | number of tokens for the summary.
19 | - `sendButton`: A button to submit the form and request a
20 | summary.
21 | - `content`: A div to display the summary.
22 | - `loadingIndicator`: A div to display a loading indicator
23 | while waiting for the summary.
24 |
25 | The `sendButton` has a click event listener that calls the
26 | `requestAnthropicSummary` function. This function sends a
27 | message to the background script to get the page content,
28 | constructs a prompt using the user input and page content,
29 | and sends a POST request to the Anthropic Claude API. The
30 | response is then displayed in the `content` div.
31 |
32 | ### styles.css
33 |
34 | This file contains the CSS styles for the popup window. It
35 | defines styles for the body, textarea, input, button,
36 | content, and loadingIndicator elements.
37 |
38 | ### background.js
39 |
40 | This file contains the background script for the extension.
41 | It listens for the following events:
42 |
43 | - `chrome.action.onClicked`: Executes the content_script.js
44 | file on the active tab when the extension icon is clicked.
45 | - `chrome.runtime.onMessage`: Listens for messages with the
46 | following actions:
47 | - `storePageContent`: Stores the page title and content in
48 | the local storage using the tab ID as the key.
49 | - `getPageContent`: Retrieves the page title and content
50 | from the local storage using the tab ID and sends it as a
51 | response.
52 |
53 | ### popup.html
54 |
55 | This file contains the HTML structure for the popup window.
56 | It includes the following elements:
57 |
58 | - A form with labels and inputs for userPrompt, stylePrompt,
59 | and maxTokens.
60 | - A submit button with the ID "sendButton".
61 | - A div with the ID "content" to display the summary.
62 | - A div with the ID "loadingIndicator" to display a loading
63 | indicator.
64 |
65 | The file also includes a link to the styles.css file and a
66 | script tag for the popup.js file.
67 |
68 | ### shared_dependencies.md
69 |
70 | This file lists the shared dependencies, variables, DOM
71 | element IDs, message names, and function names used in the
72 | extension.
73 |
74 | ### content_script.js
75 |
76 | This file contains a function called `storePageContent` that
77 | retrieves the page title and content and sends a message to
78 | the background script with the action "storePageContent" and
79 | the data as an object containing the title and content.
80 |
81 | It also listens for messages with the action
82 | "storePageContent" and calls the `storePageContent` function
83 | when received.
84 |
85 | ### manifest.json
86 |
87 | This file contains the manifest for the extension. It
88 | includes the following properties:
89 |
90 | - `manifest_version`: Set to 2.
91 | - `name`: Set to "Anthropic Claude Summary Extension".
92 | - `version`: Set to "1.0".
93 | - `description`: Set to "A Chrome extension that summarizes
94 | web pages using the Anthropic Claude API."
95 | - `permissions`: Includes "activeTab" and "storage".
96 | - `action`: Defines the default_popup, and default_icon for
97 | the extension.
98 | - `background`: Includes the background.js script and sets
99 | the "persistent" property to false.
100 | - `content_scripts`: Includes the content_script.js file and
101 | matches all URLs.
102 | - `icons`: Defines the icons for the extension.
103 |
104 | **Note**: Ensure that all the IDs of the DOM elements and the
105 | data structure of `pageContent` referenced/shared by the
106 | JavaScript files match up exactly. Use only Chrome Manifest
107 | V3 APIs. Rename the extension to "code2prompt2code".
--------------------------------------------------------------------------------
/v0/code2prompt2code/background.js:
--------------------------------------------------------------------------------
1 | chrome.action.onClicked.addListener((tab) => {
2 | chrome.scripting.executeScript({
3 | target: { tabId: tab.id },
4 | files: ["content_script.js"],
5 | });
6 | });
7 |
8 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
9 | if (request.action === "storePageContent") {
10 | const tabId = sender.tab.id;
11 | chrome.storage.local.set({ [tabId]: request.data }, () => {
12 | sendResponse({ success: true });
13 | });
14 | return true;
15 | } else if (request.action === "getPageContent") {
16 | const tabId = sender.tab.id;
17 | chrome.storage.local.get(tabId, (data) => {
18 | sendResponse(data[tabId]);
19 | });
20 | return true;
21 | }
22 | });
--------------------------------------------------------------------------------
/v0/code2prompt2code/content_script.js:
--------------------------------------------------------------------------------
1 | function storePageContent() {
2 | const pageTitle = document.title;
3 | const pageContent = document.body.innerText;
4 |
5 | chrome.runtime.sendMessage({
6 | action: "storePageContent",
7 | data: { title: pageTitle, content: pageContent },
8 | });
9 | }
10 |
11 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
12 | if (request.action === "storePageContent") {
13 | storePageContent();
14 | }
15 | });
--------------------------------------------------------------------------------
/v0/code2prompt2code/icons/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/v0/code2prompt2code/icons/icon128.png
--------------------------------------------------------------------------------
/v0/code2prompt2code/icons/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/v0/code2prompt2code/icons/icon16.png
--------------------------------------------------------------------------------
/v0/code2prompt2code/icons/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/v0/code2prompt2code/icons/icon48.png
--------------------------------------------------------------------------------
/v0/code2prompt2code/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "code2prompt2code",
4 | "version": "1.0",
5 | "description": "A Chrome extension that summarizes web pages using the Anthropic Claude API.",
6 | "permissions": ["activeTab", "storage"],
7 | "action": {
8 | "default_popup": "popup.html",
9 | "default_icon": {
10 | "16": "icons/icon16.png",
11 | "48": "icons/icon48.png",
12 | "128": "icons/icon128.png"
13 | }
14 | },
15 | "background": {
16 | "service_worker": "background.js",
17 | "type": "module"
18 | },
19 | "content_scripts": [
20 | {
21 | "matches": [""],
22 | "js": ["content_script.js"]
23 | }
24 | ],
25 | "icons": {
26 | "16": "icons/icon16.png",
27 | "48": "icons/icon48.png",
28 | "128": "icons/icon128.png"
29 | }
30 | }
--------------------------------------------------------------------------------
/v0/code2prompt2code/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Anthropic Claude Summary Extension
7 |
8 |
9 |
10 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/v0/code2prompt2code/popup.js:
--------------------------------------------------------------------------------
1 | document.addEventListener("DOMContentLoaded", () => {
2 | const userPrompt = document.getElementById("userPrompt");
3 | const stylePrompt = document.getElementById("stylePrompt");
4 | const maxTokens = document.getElementById("maxTokens");
5 | const sendButton = document.getElementById("sendButton");
6 | const content = document.getElementById("content");
7 | const loadingIndicator = document.getElementById("loadingIndicator");
8 |
9 | sendButton.addEventListener("click", requestAnthropicSummary);
10 |
11 | async function requestAnthropicSummary() {
12 | loadingIndicator.style.display = "block";
13 | content.innerHTML = "";
14 |
15 | const tab = await new Promise(resolve => chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => resolve(tab)));
16 | chrome.runtime.sendMessage({ action: "getPageContent", tabId: tab.id }, async (pageContent) => {
17 | const prompt = `${userPrompt.value}\n\n${pageContent.title}\n\n${pageContent.content}`;
18 | const style = stylePrompt.value;
19 | const tokens = parseInt(maxTokens.value, 10);
20 |
21 | const response = await fetch("https://api.anthropic.com/claude", {
22 | method: "POST",
23 | headers: {
24 | "Content-Type": "application/json"
25 | },
26 | body: JSON.stringify({ prompt, style, tokens })
27 | });
28 |
29 | const summary = await response.json();
30 | content.innerHTML = summary;
31 | loadingIndicator.style.display = "none";
32 | });
33 | }
34 | });
--------------------------------------------------------------------------------
/v0/code2prompt2code/shared_dependencies.md:
--------------------------------------------------------------------------------
1 | ## Shared dependencies:
2 |
3 | 1. DOM element IDs:
4 | - userPrompt
5 | - stylePrompt
6 | - maxTokens
7 | - sendButton
8 | - content
9 | - loadingIndicator
10 |
11 | 2. Message names:
12 | - storePageContent
13 | - getPageContent
14 |
15 | 3. Function names:
16 | - requestAnthropicSummary
17 | - storePageContent
18 |
19 | 4. Data schemas:
20 | - pageContent: { title: string, content: string }
--------------------------------------------------------------------------------
/v0/code2prompt2code/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Arial, sans-serif;
3 | margin: 0;
4 | padding: 15px;
5 | }
6 |
7 | textarea, input {
8 | display: block;
9 | width: 100%;
10 | margin-bottom: 10px;
11 | padding: 5px;
12 | font-size: 14px;
13 | }
14 |
15 | button {
16 | background-color: #4CAF50;
17 | border: none;
18 | color: white;
19 | padding: 10px 20px;
20 | text-align: center;
21 | text-decoration: none;
22 | display: inline-block;
23 | font-size: 14px;
24 | margin: 10px 0;
25 | cursor: pointer;
26 | }
27 |
28 | button:hover {
29 | background-color: #45a049;
30 | }
31 |
32 | #content {
33 | margin-top: 20px;
34 | font-size: 14px;
35 | line-height: 1.5;
36 | }
37 |
38 | #loadingIndicator {
39 | display: none;
40 | font-size: 14px;
41 | color: #999;
42 | }
--------------------------------------------------------------------------------
/v0/constants.py:
--------------------------------------------------------------------------------
1 | EXTENSION_TO_SKIP = [".png",".jpg",".jpeg",".gif",".bmp",".svg",".ico",".tif",".tiff"]
2 | DEFAULT_DIR = "generated"
3 |
4 | # we use the 0613 version of the models because we rely on the function calling API
5 | DEFAULT_MODEL = "gpt-4-0613" # we recommend 'gpt-4-0613' if you have it, it will be slower but better at coding. use gpt-4-32k if you have it
6 | # DEFAULT_MODEL = "gpt-3.5-turbo-0613" # gpt3.5 is going to be worse at generating code but faster.
7 | DEFAULT_MAX_TOKENS = 2000 # i wonder how to tweak this properly. we dont want it to be max length as it encourages verbosity of code. but too short and code also truncates suddenly.
--------------------------------------------------------------------------------
/v0/debugger.py:
--------------------------------------------------------------------------------
1 | import modal
2 | import os
3 | from constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, EXTENSION_TO_SKIP
4 |
5 | stub = modal.Stub("smol-debugger-v1")
6 | openai_image = modal.Image.debian_slim().pip_install("openai")
7 |
8 |
9 |
10 | def read_file(filename):
11 | with open(filename, 'r') as file:
12 | return file.read()
13 |
14 | def walk_directory(directory):
15 | code_contents = {}
16 | for dirpath, _, filenames in os.walk(directory):
17 | for filename in filenames:
18 | if not any(filename.endswith(ext) for ext in EXTENSION_TO_SKIP):
19 | try:
20 | relative_filepath = os.path.relpath(os.path.join(dirpath, filename), directory)
21 | code_contents[relative_filepath] = read_file(os.path.join(dirpath, filename))
22 | except Exception as e:
23 | code_contents[relative_filepath] = f"Error reading file {filename}: {str(e)}"
24 | return code_contents
25 |
26 |
27 |
28 | @stub.local_entrypoint()
29 | def main(prompt, directory=DEFAULT_DIR, model="gpt-3.5-turbo"):
30 | code_contents = walk_directory(directory)
31 |
32 | # Now, `code_contents` is a dictionary that contains the content of all your non-image files
33 | # You can send this to OpenAI's text-davinci-003 for help
34 |
35 | context = "\n".join(f"{path}:\n{contents}" for path, contents in code_contents.items())
36 | system = "You are an AI debugger who is trying to debug a program for a user based on their file system. The user has provided you with the following files and their contents, finally folllowed by the error message or issue they are facing."
37 | prompt = "My files are as follows: " + context + "\n\n" + "My issue is as follows: " + prompt
38 | prompt += "\n\nGive me ideas for what could be wrong and what fixes to do in which files."
39 | res = generate_response.call(system, prompt, model)
40 | # print res in teal
41 | print("\033[96m" + res + "\033[0m")
42 |
43 |
44 | @stub.function(
45 | image=openai_image,
46 | secret=modal.Secret.from_dotenv(),
47 | retries=modal.Retries(
48 | max_retries=3,
49 | backoff_coefficient=2.0,
50 | initial_delay=1.0,
51 | ),
52 | concurrency_limit=5,
53 | timeout=120,
54 | )
55 | def generate_response(system_prompt, user_prompt, model="gpt-3.5-turbo", *args):
56 | import openai
57 |
58 | # Set up your OpenAI API credentials
59 | openai.api_key = os.environ["OPENAI_API_KEY"]
60 |
61 | messages = []
62 | messages.append({"role": "system", "content": system_prompt})
63 | messages.append({"role": "user", "content": user_prompt})
64 | # loop thru each arg and add it to messages alternating role between "assistant" and "user"
65 | role = "assistant"
66 | for value in args:
67 | messages.append({"role": role, "content": value})
68 | role = "user" if role == "assistant" else "assistant"
69 |
70 | params = {
71 | 'model': model,
72 | # "model": "gpt-4",
73 | "messages": messages,
74 | "max_tokens": 1500,
75 | "temperature": 0,
76 | }
77 |
78 | # Send the API request
79 | response = openai.ChatCompletion.create(**params)
80 |
81 | # Get the reply from the API response
82 | reply = response.choices[0]["message"]["content"]
83 | return reply
--------------------------------------------------------------------------------
/v0/debugger_no_modal.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | from time import sleep
4 | from constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, EXTENSION_TO_SKIP
5 | import argparse
6 | def read_file(filename):
7 | with open(filename, "r") as file:
8 | return file.read()
9 |
10 |
11 | def walk_directory(directory):
12 | image_extensions = [
13 | ".png",
14 | ".jpg",
15 | ".jpeg",
16 | ".gif",
17 | ".bmp",
18 | ".svg",
19 | ".ico",
20 | ".tif",
21 | ".tiff",
22 | ]
23 | code_contents = {}
24 | for root, dirs, files in os.walk(directory):
25 | for file in files:
26 | if not any(file.endswith(ext) for ext in image_extensions):
27 | try:
28 | relative_filepath = os.path.relpath(
29 | os.path.join(root, file), directory
30 | )
31 | code_contents[relative_filepath] = read_file(
32 | os.path.join(root, file)
33 | )
34 | except Exception as e:
35 | code_contents[
36 | relative_filepath
37 | ] = f"Error reading file {file}: {str(e)}"
38 | return code_contents
39 |
40 |
41 | def main(args):
42 | prompt=args.prompt
43 | directory= args.directory
44 | model=args.model
45 | code_contents = walk_directory(directory)
46 |
47 | # Now, `code_contents` is a dictionary that contains the content of all your non-image files
48 | # You can send this to OpenAI's text-davinci-003 for help
49 |
50 | context = "\n".join(
51 | f"{path}:\n{contents}" for path, contents in code_contents.items()
52 | )
53 | system = "You are an AI debugger who is trying to debug a program for a user based on their file system. The user has provided you with the following files and their contents, finally folllowed by the error message or issue they are facing."
54 | prompt = (
55 | "My files are as follows: "
56 | + context
57 | + "\n\n"
58 | + "My issue is as follows: "
59 | + prompt
60 | )
61 | prompt += (
62 | "\n\nGive me ideas for what could be wrong and what fixes to do in which files."
63 | )
64 | res = generate_response(system, prompt, model)
65 | # print res in teal
66 | print("\033[96m" + res + "\033[0m")
67 |
68 |
69 | def generate_response(system_prompt, user_prompt, model=DEFAULT_MODEL, *args):
70 | import openai
71 |
72 | # Set up your OpenAI API credentials
73 | openai.api_key = os.environ["OPENAI_API_KEY"]
74 |
75 | messages = []
76 | messages.append({"role": "system", "content": system_prompt})
77 | messages.append({"role": "user", "content": user_prompt})
78 | # loop thru each arg and add it to messages alternating role between "assistant" and "user"
79 | role = "assistant"
80 | for value in args:
81 | messages.append({"role": role, "content": value})
82 | role = "user" if role == "assistant" else "assistant"
83 |
84 | params = {
85 | "model": model,
86 | # "model": "gpt-4",
87 | "messages": messages,
88 | "max_tokens": 1500,
89 | "temperature": 0,
90 | }
91 |
92 | # Send the API request
93 | keep_trying = True
94 | while keep_trying:
95 | try:
96 | response = openai.ChatCompletion.create(**params)
97 | keep_trying = False
98 | except Exception as e:
99 | # e.g. when the API is too busy, we don't want to fail everything
100 | print("Failed to generate response. Error: ", e)
101 | sleep(30)
102 | print("Retrying...")
103 |
104 | # Get the reply from the API response
105 | reply = response.choices[0]["message"]["content"]
106 | return reply
107 |
108 |
109 | if __name__ == "__main__":
110 | parser = argparse.ArgumentParser()
111 | parser.add_argument(
112 | "prompt",
113 | help="The prompt to use for the AI. This should be the error message or issue you are facing.",
114 |
115 | )
116 | parser.add_argument(
117 | "--directory",
118 | "-d",
119 | help="The directory to use for the AI. This should be the directory containing the files you want to debug.",
120 | default=DEFAULT_DIR,
121 | )
122 | parser.add_argument(
123 | "--model",
124 | "-m",
125 | help="The model to use for the AI. This should be the model ID of the model you want to use.",
126 | default=DEFAULT_MODEL,
127 | )
128 | args = parser.parse_args()
129 | main(args)
130 |
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/background.js:
--------------------------------------------------------------------------------
1 | chrome.runtime.onInstalled.addListener(() => {
2 | chrome.action.onClicked.addListener((tab) => {
3 | chrome.scripting.executeScript({
4 | target: { tabId: tab.id },
5 | files: ['content_script.js'],
6 | });
7 | });
8 | });
9 |
10 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
11 | if (request.action === 'storePageContent') {
12 | chrome.storage.local.set({ pageContent: request }, () => {
13 | sendResponse({ success: true });
14 | });
15 | } else if (request.action === 'getPageContent') {
16 | chrome.storage.local.get(['pageContent'], (result) => {
17 | sendResponse(result);
18 | });
19 | }
20 | return true;
21 | });
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/content_script.js:
--------------------------------------------------------------------------------
1 | function extractPageContent() {
2 | const pageTitle = document.title;
3 | const pageContent = document.body.innerText;
4 | return { pageTitle, pageContent };
5 | }
6 |
7 | function sendMessageToBackground(action, data) {
8 | chrome.runtime.sendMessage({ action, ...data }, (response) => {
9 | if (response.success) {
10 | console.log('Page content sent to background.');
11 | } else {
12 | console.error('Failed to send page content to background.');
13 | }
14 | });
15 | }
16 |
17 | const pageData = extractPageContent();
18 | sendMessageToBackground('storePageContent', pageData);
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/v0/exampleChromeExtension/icon128.png
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/v0/exampleChromeExtension/icon16.png
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/v0/exampleChromeExtension/icon48.png
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "Anthropic Claude Summary Extension",
4 | "version": "1.0",
5 | "description": "A Chrome extension that summarizes web pages using the Anthropic Claude API.",
6 | "permissions": [
7 | "activeTab",
8 | "storage"
9 | ],
10 | "action": {
11 | "default_popup": "popup.html",
12 | "default_icon": {
13 | "16": "icon16.png",
14 | "48": "icon48.png",
15 | "128": "icon128.png"
16 | }
17 | },
18 | "background": {
19 | "service_worker": "background.js"
20 | },
21 | "content_scripts": [
22 | {
23 | "matches": [""],
24 | "js": ["content_script.js"]
25 | }
26 | ],
27 | "icons": {
28 | "16": "icon16.png",
29 | "48": "icon48.png",
30 | "128": "icon128.png"
31 | }
32 | }
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Anthropic Summary
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/popup.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', () => {
2 | const content = document.getElementById('content');
3 | const userPrompt = document.getElementById('userPrompt');
4 | const sendButton = document.getElementById('sendButton');
5 | const loadingIndicator = document.getElementById('loadingIndicator');
6 |
7 | const defaultPrompt = `Human: Please provide a detailed, easy to read HTML summary of the given content with 3-4 highlights per section with important keywords bolded and important links preserved, in this format:
8 |
9 |
{title here}
10 |
{section title here}
11 |
12 |
{first point}: {short explanation with details, and important links}
13 |
{second point}: {short explanation with details, and important links}
14 |
15 |
16 |
{second section here}
17 |
18 |
19 |
20 |
21 |
22 | With all the words in brackets replaced by the summary of the content.
23 |
24 | Assistant:`;
25 |
26 | function requestAnthropicSummary(prompt, apiKey) {
27 | chrome.runtime.sendMessage({ action: 'getPageContent' }, (response) => {
28 | const { pageTitle, pageContent } = response.pageContent;
29 | const fullPrompt = `${pageTitle}\n\n${pageContent}\n\n${prompt}`;
30 |
31 | loadingIndicator.innerHTML = `Read page content of ${pageTitle}`;
32 | loadingIndicator.style.display = 'block';
33 | content.innerHTML = '';
34 | userPrompt.disabled = true;
35 | sendButton.disabled = true;
36 |
37 | fetch('https://api.anthropic.com/v1/complete', {
38 | method: 'POST',
39 | headers: {
40 | 'x-api-key': apiKey,
41 | 'content-type': 'application/json',
42 | },
43 | body: JSON.stringify({
44 | prompt: fullPrompt,
45 | model: 'claude-instant-v1-100k',
46 | max_tokens_to_sample: 1000,
47 | stop_sequences: ['\n\nHuman:'],
48 | }),
49 | })
50 | .then((res) => {
51 | if (res.status === 401) {
52 | chrome.storage.local.remove(['apiKey']);
53 | throw new Error('Invalid API key');
54 | }
55 | return res.json();
56 | })
57 | .then((data) => {
58 | content.innerHTML = data.completion;
59 | loadingIndicator.style.display = 'none';
60 | userPrompt.disabled = false;
61 | sendButton.disabled = false;
62 | })
63 | .catch((error) => {
64 | console.error(error);
65 | loadingIndicator.style.display = 'none';
66 | userPrompt.disabled = false;
67 | sendButton.disabled = false;
68 | });
69 | });
70 | }
71 |
72 | chrome.storage.local.get(['apiKey'], (result) => {
73 | if (!result.apiKey) {
74 | const apiKey = prompt('Please enter your Anthropic Claude API key:');
75 | chrome.storage.local.set({ apiKey }, () => {
76 | requestAnthropicSummary(defaultPrompt, apiKey);
77 | });
78 | } else {
79 | requestAnthropicSummary(defaultPrompt, result.apiKey);
80 | }
81 | });
82 |
83 | sendButton.addEventListener('click', () => {
84 | chrome.storage.local.get(['apiKey'], (result) => {
85 | requestAnthropicSummary(userPrompt.value, result.apiKey);
86 | });
87 | });
88 | });
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/prompt used for this extension.md:
--------------------------------------------------------------------------------
1 | a Chrome Manifest V3 extension that reads the current page, and offers a popup UI that sends the page content to the Anthropic Claude API along with a prompt to summarize it, and lets the user modify that prompt and re-send the prompt+content to get another summary view of the content.
2 |
3 |
4 | - When clicked:
5 | - it injects a content script `content_script.js` on the currently open tab,
6 | and accesses the title `pageTitle` and main content `pageContent` of the currently open page
7 | (extracted via an injected content script, and sent over using a `storePageContent` action)
8 | - in the background, receives the `storePageContent` data and stores it
9 | - pops up a small window with a simple, modern, slick, minimalistic styled html popup
10 | - in the popup script
11 | - retrieves the page content data using a `getPageContent` action (and the background listens for the `getPageContent` action and retrieves that data)
12 | - check extension storage for an `apiKey`, and if it isn't stored, asks for an API key to Anthropic Claude and stores it.
13 | - calls the Anthropic model endpoint https://api.anthropic.com/v1/complete with the `claude-instant-v1-100k` model with:
14 | - append the page title
15 | - append the page content
16 | - append a prompt to ask for a detailed, easy to read HTML summary of the given content with 3-4 highlights per section with important keywords bolded and important links preserved.
17 | in this format:
18 | ```js
19 | defaultPrompt = `Human: Please provide a detailed, easy to read HTML summary of the given content
20 | with 3-4 highlights per section with important keywords bolded and important links preserved, in this format:
21 |
22 |
{title here}
23 |
{section title here}
24 |
25 |
26 |
{first point}: {short explanation with details, with any relevant links included}
27 |
{second point}: {short explanation with details, with any relevant links included}
28 |
{third point}:
29 |
30 |
31 |
{second section here}
32 |
33 |
34 |
35 |
36 |
37 | With all the words in brackets replaced by the summary of the content. Only draw from the source content, do not hallucinate.
38 |
39 | Assistant:`;
40 | ```js
41 | - renders the Anthropic-generated HTML summary inside of the popup in a div with an id of content
42 | - at the bottom of the popup, show a textarea with an id of `userPrompt` with a short default value prompt, and a submit button with an id of `sendButton`.
43 | - when `sendButton` is clicked, lets the user re-ask Anthropic with the same page title and page content but different prompt (from `userPrompt`).
44 | - disable these inputs while it waits for the Anthropic api call to complete
45 |
46 | Important Details:
47 |
48 | - It has to run in a browser environment, so no Nodejs APIs allowed.
49 |
50 | - the popup should show a "Read page content of {TITLE}" message with a big fat attractive juicy css animated candy stripe loading indicator `loadingIndicator` while waiting for the anthropic api to return, with the TITLE being the page title. do not show it until the api call begins, and clear it when it ends.
51 |
52 | - the return signature of the anthropic api is curl https://api.anthropic.com/v1/complete\
53 | -H "x-api-key: $API_KEY"\
54 | -H 'content-type: application/json'\
55 | -d '{
56 | "prompt": "\n\nHuman: Tell me a haiku about trees\n\nAssistant: ",
57 | "model": "claude-v1", "max_tokens_to_sample": 1000, "stop_sequences": ["\n\nHuman:"]
58 | }'
59 | {"completion":" Here is a haiku about trees:\n\nSilent sentinels, \nStanding solemn in the woods,\nBranches reaching sky.","stop":"\n\nHuman:","stop_reason":"stop_sequence","truncated":false,"log_id":"f5d95cf326a4ac39ee36a35f434a59d5","model":"claude-v1","exception":null}
60 |
61 | - in the string prompt sent to Anthropic, first include the page title and page content, and finally append the prompt, clearly vertically separated by spacing.
62 |
63 | - if the Anthropic api call is a 401, handle that by clearing the stored anthropic api key and asking for it again.
64 |
65 | - add styles to make sure the popup's styling follows the basic rules of web design, for example having margins around the body, and a system font stack.
66 |
67 | - style the popup body with a minimum width of 400 and height of 600.
68 |
69 | ## debugging notes
70 |
71 | inside of background.js, just take the getPageContent response directly
72 |
73 | ```js
74 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
75 | if (request.action === 'storePageContent') {
76 | // dont access request.pageContent
77 | chrome.storage.local.set({ pageContent: request }, () => {
78 | sendResponse({ success: true });
79 | });
80 | } else if (request.action === 'getPageContent') {
81 | chrome.storage.local.get(['pageContent'], (result) => {
82 | // dont access request.pageContent
83 | sendResponse(result);
84 | });
85 | }
86 | return true;
87 | });
88 | ```
89 |
90 | inside of popup.js, Update the function calls to `requestAnthropicSummary`
91 | in `popup.js` to pass the `apiKey`:
92 |
93 | ```javascript
94 | chrome.storage.local.get(['apiKey'], (result) => {
95 | const apiKey = result.apiKey;
96 | requestAnthropicSummary(defaultPrompt, apiKey);
97 | });
98 |
99 | sendButton.addEventListener('click', () => {
100 | chrome.storage.local.get(['apiKey'], (result) => {
101 | const apiKey = result.apiKey;
102 | requestAnthropicSummary(userPrompt.value, apiKey);
103 | });
104 | });
105 | ```
106 |
107 | in `popup.js`, store the defaultPrompt at the top level.
108 | also, give a HTML format to the anthropic prompt
109 |
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/shared_dependencies.md:
--------------------------------------------------------------------------------
1 | the app is: a Chrome Manifest V3 extension that reads the current page, and offers a popup UI that sends the page content to the Anthropic Claude API along with a prompt to summarize it, and lets the user modify that prompt and re-send the prompt+content to get another summary view of the content.
2 |
3 | the files we have decided to generate are: content_script.js, background.js, popup.html, popup.js, popup.css
4 |
5 | Shared dependencies:
6 |
7 | 1. Exported variables:
8 | - pageTitle
9 | - pageContent
10 |
11 | 2. Data schemas:
12 | - storePageContent action data: { action: 'storePageContent', pageTitle, pageContent }
13 | - getPageContent action data: { action: 'getPageContent' }
14 |
15 | 3. ID names of DOM elements:
16 | - content
17 | - userPrompt
18 | - sendButton
19 | - loadingIndicator
20 |
21 | 4. Message names:
22 | - storePageContent
23 | - getPageContent
24 |
25 | 5. Function names:
26 | - requestAnthropicSummary
27 |
28 | 6. API endpoints:
29 | - https://api.anthropic.com/v1/complete
30 |
31 | 7. Model name:
32 | - claude-instant-v1-100k
33 |
34 | 8. Default prompt:
35 | - defaultPrompt
--------------------------------------------------------------------------------
/v0/exampleChromeExtension/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 16px;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
4 | min-width: 400px;
5 | min-height: 600px;
6 | }
7 |
8 | #content {
9 | margin-bottom: 16px;
10 | }
11 |
12 | #userPrompt {
13 | width: 100%;
14 | height: 80px;
15 | resize: none;
16 | margin-bottom: 8px;
17 | }
18 |
19 | #sendButton {
20 | display: inline-block;
21 | background-color: #007bff;
22 | color: white;
23 | padding: 8px 16px;
24 | border-radius: 4px;
25 | text-decoration: none;
26 | cursor: pointer;
27 | transition: background-color 0.2s;
28 | }
29 |
30 | #sendButton:hover {
31 | background-color: #0056b3;
32 | }
33 |
34 | #sendButton:disabled {
35 | background-color: #6c757d;
36 | cursor: not-allowed;
37 | }
38 |
39 | #loadingIndicator {
40 | display: none;
41 | width: 100%;
42 | height: 8rem;
43 | border-radius: 1rem;
44 | margin-bottom: 2rem;
45 | background-image: linear-gradient(-45deg, #007bff 25%, transparent 25%, transparent 50%, #007bff 50%, #007bff 75%, transparent 75%, transparent);
46 | background-size: 40px 40px;
47 | animation: loading 1s linear infinite;
48 | }
49 |
50 | @keyframes loading {
51 | 0% {
52 | background-position: 40px 0;
53 | }
54 | 100% {
55 | background-position: 0 0;
56 | }
57 | }
--------------------------------------------------------------------------------
/v0/main.py:
--------------------------------------------------------------------------------
1 | import os
2 | import modal
3 | import ast
4 | from utils import clean_dir
5 | from constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS
6 |
7 | stub = modal.Stub("smol-developer-v1") # yes we are recommending using Modal by default, as it helps with deployment. see readme for why.
8 | openai_image = modal.Image.debian_slim().pip_install("openai", "tiktoken")
9 |
10 | @stub.function(
11 | image=openai_image,
12 | secret=modal.Secret.from_dotenv(),
13 | retries=modal.Retries(
14 | max_retries=5,
15 | backoff_coefficient=2.0,
16 | initial_delay=1.0,
17 | ),
18 | concurrency_limit=5, # many users report rate limit issues (https://github.com/smol-ai/developer/issues/10) so we try to do this but it is still inexact. would like ideas on how to improve
19 | timeout=120,
20 | )
21 | def generate_response(model, system_prompt, user_prompt, *args):
22 | # IMPORTANT: Keep import statements here due to Modal container restrictions https://modal.com/docs/guide/custom-container#additional-python-packages
23 | import openai
24 | import tiktoken
25 |
26 | def reportTokens(prompt):
27 | encoding = tiktoken.encoding_for_model(model)
28 | # print number of tokens in light gray, with first 50 characters of prompt in green. if truncated, show that it is truncated
29 | print("\033[37m" + str(len(encoding.encode(prompt))) + " tokens\033[0m" + " in prompt: " + "\033[92m" + prompt[:50] + "\033[0m" + ("..." if len(prompt) > 50 else ""))
30 |
31 |
32 | # Set up your OpenAI API credentials
33 | openai.api_key = os.environ["OPENAI_API_KEY"]
34 |
35 | messages = []
36 | messages.append({"role": "system", "content": system_prompt})
37 | reportTokens(system_prompt)
38 | messages.append({"role": "user", "content": user_prompt})
39 | reportTokens(user_prompt)
40 | # Loop through each value in `args` and add it to messages alternating role between "assistant" and "user"
41 | role = "assistant"
42 | for value in args:
43 | messages.append({"role": role, "content": value})
44 | reportTokens(value)
45 | role = "user" if role == "assistant" else "assistant"
46 |
47 | params = {
48 | "model": model,
49 | "messages": messages,
50 | "max_tokens": DEFAULT_MAX_TOKENS,
51 | "temperature": 0,
52 | }
53 |
54 | # Send the API request
55 | response = openai.ChatCompletion.create(**params)
56 |
57 | # Get the reply from the API response
58 | reply = response.choices[0]["message"]["content"]
59 | return reply
60 |
61 |
62 | @stub.function()
63 | def generate_file(filename, model=DEFAULT_MODEL, filepaths_string=None, shared_dependencies=None, prompt=None):
64 | # call openai api with this prompt
65 | filecode = generate_response.call(model,
66 | f"""You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
67 |
68 | the app is: {prompt}
69 |
70 | the files we have decided to generate are: {filepaths_string}
71 |
72 | the shared dependencies (like filenames and variable names) we have decided on are: {shared_dependencies}
73 |
74 | only write valid code for the given filepath and file type, and return only the code.
75 | do not add any other explanation, only return valid code for that file type.
76 | """,
77 | f"""
78 | We have broken up the program into per-file generation.
79 | Now your job is to generate only the code for the file {filename}.
80 | Make sure to have consistent filenames if you reference other files we are also generating.
81 |
82 | Remember that you must obey 3 things:
83 | - you are generating code for the file {filename}
84 | - do not stray from the names of the files and the shared dependencies we have decided on
85 | - MOST IMPORTANT OF ALL - the purpose of our app is {prompt} - every line of code you generate must be valid code. Do not include code fences in your response, for example
86 |
87 | Bad response:
88 | ```javascript
89 | console.log("hello world")
90 | ```
91 |
92 | Good response:
93 | console.log("hello world")
94 |
95 | Begin generating the code now.
96 |
97 | """,
98 | )
99 |
100 | return filename, filecode
101 |
102 |
103 | @stub.local_entrypoint()
104 | def main(prompt, directory=DEFAULT_DIR, model=DEFAULT_MODEL, file=None):
105 | # read file from prompt if it ends in a .md filetype
106 | if prompt.endswith(".md"):
107 | with open(prompt, "r") as promptfile:
108 | prompt = promptfile.read()
109 |
110 | print("hi its me, 🐣the smol developer🐣! you said you wanted:")
111 | # print the prompt in green color
112 | print("\033[92m" + prompt + "\033[0m")
113 |
114 | # call openai api with this prompt
115 | filepaths_string = generate_response.call(model,
116 | """You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
117 |
118 | When given their intent, create a complete, exhaustive list of filepaths that the user would write to make the program.
119 |
120 | only list the filepaths you would write, and return them as a python list of strings.
121 | do not add any other explanation, only return a python list of strings.
122 |
123 | Example output:
124 | ["index.html", "style.css", "script.js"]
125 | """,
126 | prompt,
127 | )
128 | print(filepaths_string)
129 | # parse the result into a python list
130 | list_actual = []
131 | try:
132 | list_actual = ast.literal_eval(filepaths_string)
133 |
134 | # if shared_dependencies.md is there, read it in, else set it to None
135 | shared_dependencies = None
136 | if os.path.exists("shared_dependencies.md"):
137 | with open("shared_dependencies.md", "r") as shared_dependencies_file:
138 | shared_dependencies = shared_dependencies_file.read()
139 |
140 | if file is not None:
141 | # check file
142 | print("file", file)
143 | filename, filecode = generate_file(file, model=model, filepaths_string=filepaths_string, shared_dependencies=shared_dependencies, prompt=prompt)
144 | write_file(filename, filecode, directory)
145 | else:
146 | clean_dir(directory)
147 |
148 | # understand shared dependencies
149 | shared_dependencies = generate_response.call(model,
150 | """You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
151 |
152 | In response to the user's prompt:
153 |
154 | ---
155 | the app is: {prompt}
156 | ---
157 |
158 | the files we have decided to generate are: {filepaths_string}
159 |
160 | Now that we have a list of files, we need to understand what dependencies they share.
161 | Please name and briefly describe what is shared between the files we are generating, including exported variables, data schemas, id names of every DOM elements that javascript functions will use, message names, and function names.
162 | Exclusively focus on the names of the shared dependencies, and do not add any other explanation.
163 | """,
164 | prompt,
165 | )
166 | print(shared_dependencies)
167 | # write shared dependencies as a md file inside the generated directory
168 | write_file("shared_dependencies.md", shared_dependencies, directory)
169 |
170 | # Iterate over generated files and write them to the specified directory
171 | for filename, filecode in generate_file.map(
172 | list_actual, order_outputs=False, kwargs=dict(model=model, filepaths_string=filepaths_string, shared_dependencies=shared_dependencies, prompt=prompt)
173 | ):
174 | write_file(filename, filecode, directory)
175 |
176 |
177 | except ValueError:
178 | print("Failed to parse result")
179 |
180 |
181 | def write_file(filename, filecode, directory):
182 | # Output the filename in blue color
183 | print("\033[94m" + filename + "\033[0m")
184 | print(filecode)
185 |
186 | file_path = os.path.join(directory, filename)
187 | dir = os.path.dirname(file_path)
188 |
189 | # Check if the filename is actually a directory
190 | if os.path.isdir(file_path):
191 | print(f"Error: {filename} is a directory, not a file.")
192 | return
193 |
194 | os.makedirs(dir, exist_ok=True)
195 |
196 | # Open the file in write mode
197 | with open(file_path, "w") as file:
198 | # Write content to the file
199 | file.write(filecode)
200 |
201 |
--------------------------------------------------------------------------------
/v0/main_no_modal.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | import ast
4 | from time import sleep
5 | from utils import clean_dir
6 | from constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS
7 |
8 |
9 | def generate_response(system_prompt, user_prompt, *args):
10 | import openai
11 | import tiktoken
12 |
13 | def reportTokens(prompt):
14 | encoding = tiktoken.encoding_for_model(DEFAULT_MODEL)
15 | # print number of tokens in light gray, with first 10 characters of prompt in green
16 | print(
17 | "\033[37m"
18 | + str(len(encoding.encode(prompt)))
19 | + " tokens\033[0m"
20 | + " in prompt: "
21 | + "\033[92m"
22 | + prompt[:50]
23 | + "\033[0m"
24 | )
25 |
26 | # Set up your OpenAI API credentials
27 | openai.api_key = os.environ["OPENAI_API_KEY"]
28 |
29 | messages = []
30 | messages.append({"role": "system", "content": system_prompt})
31 | reportTokens(system_prompt)
32 | messages.append({"role": "user", "content": user_prompt})
33 | reportTokens(user_prompt)
34 | # loop thru each arg and add it to messages alternating role between "assistant" and "user"
35 | role = "assistant"
36 | for value in args:
37 | messages.append({"role": role, "content": value})
38 | reportTokens(value)
39 | role = "user" if role == "assistant" else "assistant"
40 |
41 | params = {
42 | "model": DEFAULT_MODEL,
43 | "messages": messages,
44 | "max_tokens": DEFAULT_MAX_TOKENS,
45 | "temperature": 0,
46 | }
47 |
48 | # Send the API request
49 | keep_trying = True
50 | while keep_trying:
51 | try:
52 | response = openai.ChatCompletion.create(**params)
53 | keep_trying = False
54 | except Exception as e:
55 | # e.g. when the API is too busy, we don't want to fail everything
56 | print("Failed to generate response. Error: ", e)
57 | sleep(30)
58 | print("Retrying...")
59 |
60 | # Get the reply from the API response
61 | reply = response.choices[0]["message"]["content"]
62 | return reply
63 |
64 |
65 | def generate_file(
66 | filename, filepaths_string=None, shared_dependencies=None, prompt=None
67 | ):
68 | # call openai api with this prompt
69 | filecode = generate_response(
70 | f"""You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
71 |
72 | the app is: {prompt}
73 |
74 | the files we have decided to generate are: {filepaths_string}
75 |
76 | the shared dependencies (like filenames and variable names) we have decided on are: {shared_dependencies}
77 |
78 | only write valid code for the given filepath and file type, and return only the code.
79 | do not add any other explanation, only return valid code for that file type.
80 | """,
81 | f"""
82 | We have broken up the program into per-file generation.
83 | Now your job is to generate only the code for the file {filename}.
84 | Make sure to have consistent filenames if you reference other files we are also generating.
85 |
86 | Remember that you must obey 3 things:
87 | - you are generating code for the file {filename}
88 | - do not stray from the names of the files and the shared dependencies we have decided on
89 | - MOST IMPORTANT OF ALL - the purpose of our app is {prompt} - every line of code you generate must be valid code. Do not include code fences in your response, for example
90 |
91 | Bad response:
92 | ```javascript
93 | console.log("hello world")
94 | ```
95 |
96 | Good response:
97 | console.log("hello world")
98 |
99 | Begin generating the code now.
100 |
101 | """,
102 | )
103 |
104 | return filename, filecode
105 |
106 |
107 | def main(prompt, directory=DEFAULT_DIR, file=None):
108 | # read file from prompt if it ends in a .md filetype
109 | if prompt.endswith(".md"):
110 | with open(prompt, "r") as promptfile:
111 | prompt = promptfile.read()
112 |
113 | print("hi its me, 🐣the smol developer🐣! you said you wanted:")
114 | # print the prompt in green color
115 | print("\033[92m" + prompt + "\033[0m")
116 |
117 | # example prompt:
118 | # a Chrome extension that, when clicked, opens a small window with a page where you can enter
119 | # a prompt for reading the currently open page and generating some response from openai
120 |
121 | # call openai api with this prompt
122 | filepaths_string = generate_response(
123 | """You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
124 |
125 | When given their intent, create a complete, exhaustive list of filepaths that the user would write to make the program.
126 |
127 | only list the filepaths you would write, and return them as a python list of strings.
128 | do not add any other explanation, only return a python list of strings.
129 | """,
130 | prompt,
131 | )
132 | print(filepaths_string)
133 | # parse the result into a python list
134 | list_actual = []
135 | try:
136 | list_actual = ast.literal_eval(filepaths_string)
137 |
138 | # if shared_dependencies.md is there, read it in, else set it to None
139 | shared_dependencies = None
140 | if os.path.exists("shared_dependencies.md"):
141 | with open("shared_dependencies.md", "r") as shared_dependencies_file:
142 | shared_dependencies = shared_dependencies_file.read()
143 |
144 | if file is not None:
145 | # check file
146 | print("file", file)
147 | filename, filecode = generate_file(
148 | file,
149 | filepaths_string=filepaths_string,
150 | shared_dependencies=shared_dependencies,
151 | prompt=prompt,
152 | )
153 | write_file(filename, filecode, directory)
154 | else:
155 | clean_dir(directory)
156 |
157 | # understand shared dependencies
158 | shared_dependencies = generate_response(
159 | """You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
160 |
161 | In response to the user's prompt:
162 |
163 | ---
164 | the app is: {prompt}
165 | ---
166 |
167 | the files we have decided to generate are: {filepaths_string}
168 |
169 | Now that we have a list of files, we need to understand what dependencies they share.
170 | Please name and briefly describe what is shared between the files we are generating, including exported variables, data schemas, id names of every DOM elements that javascript functions will use, message names, and function names.
171 | Exclusively focus on the names of the shared dependencies, and do not add any other explanation.
172 | """,
173 | prompt,
174 | )
175 | print(shared_dependencies)
176 | # write shared dependencies as a md file inside the generated directory
177 | write_file("shared_dependencies.md", shared_dependencies, directory)
178 |
179 | for name in list_actual:
180 | filename, filecode = generate_file(
181 | name,
182 | filepaths_string=filepaths_string,
183 | shared_dependencies=shared_dependencies,
184 | prompt=prompt,
185 | )
186 | write_file(filename, filecode, directory)
187 |
188 | except ValueError:
189 | print("Failed to parse result: " + result)
190 |
191 |
192 | def write_file(filename, filecode, directory):
193 | # Output the filename in blue color
194 | print("\033[94m" + filename + "\033[0m")
195 | print(filecode)
196 |
197 | file_path = directory + "/" + filename
198 | dir = os.path.dirname(file_path)
199 | os.makedirs(dir, exist_ok=True)
200 |
201 | # if file_path does not end with "/"
202 | if not file_path.endswith("/"):
203 | with open(file_path, "w") as file:
204 | # Write content to the file
205 | file.write(filecode)
206 |
207 |
208 | if __name__ == "__main__":
209 |
210 | # Check for arguments
211 | if len(sys.argv) < 2:
212 |
213 | # Looks like we don't have a prompt. Check if prompt.md exists
214 | if not os.path.exists("prompt.md"):
215 |
216 | # Still no? Then we can't continue
217 | print("Please provide a prompt")
218 | sys.exit(1)
219 |
220 | # Still here? Assign the prompt file name to prompt
221 | prompt = "prompt.md"
222 |
223 | else:
224 | # Set prompt to the first argument
225 | prompt = sys.argv[1]
226 |
227 | # Pull everything else as normal
228 | directory = sys.argv[2] if len(sys.argv) > 2 else DEFAULT_DIR
229 | file = sys.argv[3] if len(sys.argv) > 3 else None
230 |
231 | # Run the main function
232 | main(prompt, directory, file)
233 |
--------------------------------------------------------------------------------
/v0/readme.md:
--------------------------------------------------------------------------------
1 | this is the archive for v0 of smol developer, which was the original Modal.com based version that we found (well, knew) had a few issues with reliability and dependency management.
2 |
3 | it has been rewritten from scratch in the root folder, but is preserved here for others to easily reference.
--------------------------------------------------------------------------------
/v0/static/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/v0/static/icon128.png
--------------------------------------------------------------------------------
/v0/static/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/v0/static/icon16.png
--------------------------------------------------------------------------------
/v0/static/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smol-ai/developer/a6747d1a6ccd983c54a483c4a4fb1fa4bf740e82/v0/static/icon48.png
--------------------------------------------------------------------------------
/v0/static/readme.md:
--------------------------------------------------------------------------------
1 | by default, files in here will be copied into the generated folder, useful for adding non-code files like images and large files like csv's.
--------------------------------------------------------------------------------
/v0/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | from constants import EXTENSION_TO_SKIP
3 |
4 | def clean_dir(directory):
5 | # Check if the directory exists
6 | if os.path.exists(directory):
7 | # If it does, iterate over all files and directories
8 | for dirpath, _, filenames in os.walk(directory):
9 | for filename in filenames:
10 | _, extension = os.path.splitext(filename)
11 | if extension not in EXTENSION_TO_SKIP:
12 | os.remove(os.path.join(dirpath, filename))
13 | else:
14 | os.makedirs(directory, exist_ok=True)
--------------------------------------------------------------------------------
/v0/v0_readme.md:
--------------------------------------------------------------------------------
1 | # smol developer
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ***Human-centric & Coherent Whole Program Synthesis*** aka your own personal junior developer
11 |
12 | > [Build the thing that builds the thing!](https://twitter.com/swyx/status/1657578738345979905) a `smol dev` for every dev in every situation
13 |
14 | this is a prototype of a "junior developer" agent (aka `smol dev`) that scaffolds an entire codebase out for you once you give it a product spec, but does not end the world or overpromise AGI. instead of making and maintaining specific, rigid, one-shot starters, like `create-react-app`, or `create-nextjs-app`, this is basically [`create-anything-app`](https://news.ycombinator.com/item?id=35942352) where you develop your scaffolding prompt in a tight loop with your smol dev.
15 |
16 | AI that is helpful, harmless, and honest is complemented by a codebase that is simple, safe, and smol - <200 lines of Python and Prompts, so this is easy to understand and customize.
17 |
18 | *Not no code, not low code, but some third thing.*
19 |
20 | Perhaps a higher order evolution of programming where you still need to be technical, but no longer have to implement every detail at least to scaffold things out.
21 |
22 | ## examples/prompt gallery
23 |
24 | - [6 minute video demo](https://youtu.be/UCo7YeTy-aE) - (sorry for sped up audio, we were optimizing for twitter, bad call)
25 | - this was the original smol developer demo - going from prompt to full chrome extension that requests and stores and apikey, generates a popup window, reads and transmits page content, and usefully summarizes any website with Anthropic Claude, switching models up to the 100k one based on length of input
26 | - the prompt is located in [prompt.md](https://github.com/smol-ai/developer/blob/main/prompt.md) and it outputs [/exampleChromeExtension](https://github.com/smol-ai/developer/tree/main/examples/exampleChromeExtension)
27 | - `smol-plugin` - prompt to ChatGPT plugin ([tweet](https://twitter.com/ultrasoundchad/status/1659366507409985536?s=20), [fork](https://github.com/gmchad/smol-plugin))
28 |
29 |
30 |
31 | - [Prompt to Pokemon App](https://twitter.com/RobertCaracaus/status/1659312419485761536?s=20)
32 |
33 |
34 |
35 | - [Political Campaign CRM Program example](https://github.com/smol-ai/developer/pull/22/files)
36 | - [Lessons from Creating a VSCode Extension with GPT-4](https://bit.kevinslin.com/p/leveraging-gpt-4-to-automate-the) (also on [HN](https://news.ycombinator.com/item?id=36071342))
37 | - [7 min Video: Smol AI Developer - Build ENTIRE Codebases With A Single Prompt](https://www.youtube.com/watch?v=DzRoYc2UGKI) produces a full working OpenAI CLI python app from a prompt
38 |
39 |
40 |
41 | - [12 min Video: SMOL AI - Develop Large Scale Apps with AGI in one click](https://www.youtube.com/watch?v=zsxyqz6SYp8) scaffolds a surprisingly complex React/Node/MongoDB full stack app in 40 minutes and $9
42 |
43 |
44 |
45 | I'm actively seeking more examples, please PR yours!
46 |
47 | sorry for the lack of examples, I know that is frustrating but I wasnt ready for so many of you lol
48 |
49 | ## major forks/alternatives
50 |
51 | please send in alternative implementations, and deploy strategies on alternative stacks!
52 |
53 | - **JS/TS**: https://github.com/PicoCreator/smol-dev-js A pure JS variant of smol-dev, allowing even smoler incremental changes via prompting (if you dun want to do the whole spec2code thing), allowing you to plug it into any project live (for better or worse)
54 | - **C#/Dotnet**: https://github.com/colhountech/smol-ai-dotnet in C#!
55 | - **Golang**: https://github.com/tmc/smol-dev-go in Go
56 | - https://github.com/gmchad/smol-plugin automatically generate @openai plugins by specifying your API in markdown in smol-developer style
57 | - your fork here!
58 |
59 | ## arch diagram
60 |
61 | naturally generated with gpt4, like [we did for babyagi](https://twitter.com/swyx/status/1648724820316786688)
62 | 
63 |
64 |
65 | ### innovations and insights
66 |
67 | > Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections
68 |
69 | - **Markdown is all you need** - Markdown is the perfect way to prompt for whole program synthesis because it is easy to mix english and code (whether `variable_names` or entire \`\`\` code fenced code samples)
70 | - turns out you can specify prompts in code in prompts and gpt4 obeys that to the letter
71 | - **Copy and paste programming**
72 | - teaching the program to understand how to code around a new API (Anthropic's API is after GPT3's knowledge cutoff) by just pasting in the `curl` input and output
73 | - pasting error messages into the prompt and vaguely telling the program how you'd like it handled. it kind of feels like "logbook driven programming".
74 | - **Debugging by `cat`ing** the whole codebase with your error message and getting specific fix suggestions - particularly delightful!
75 | - **Tricks for whole program coherence** - our chosen example usecase, Chrome extensions, have a lot of indirect dependencies across files. Any hallucination of cross dependencies causes the whole program to error.
76 | - We solved this by adding an intermediate step asking GPT to think through `shared_dependencies.md`, and then insisting on using that in generating each file. This basically means GPT is able to talk to itself...
77 | - ... but it's not perfect, yet. `shared_dependencies.md` is sometimes not comperehensive in understanding what are hard dependencies between files. So we just solved it by specifying a specific `name` in the prompt. felt dirty at first but it works, and really it's just clear unambiguous communication at the end of the day.
78 | - see `prompt.md` for SOTA smol-dev prompting
79 | - **Low activation energy for unfamiliar APIs**
80 | - we have never really learned css animations, but now can just say we want a "juicy css animated red and white candy stripe loading indicator" and it does the thing.
81 | - ditto for Chrome Extension Manifest v3 - the docs are an abject mess, but fortunately we don't have to read them now to just get a basic thing done
82 | - the Anthropic docs (bad bad) were missing guidance on what return signature they have. so just curl it and dump it in the prompt lol.
83 | - **Modal is all you need** - we chose Modal to solve 4 things:
84 | - solve python dependency hell in dev and prod
85 | - parallelizable code generation
86 | - simple upgrade path from local dev to cloud hosted endpoints (in future)
87 | - fault tolerant openai api calls with retries/backoff, and attached storage (for future use)
88 |
89 | > Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections
90 |
91 | ### caveats
92 |
93 | We were working on a Chrome Extension, which requires images to be generated, so we added some usecase specific code in there to skip destroying/regenerating them, that we haven't decided how to generalize.
94 |
95 | We dont have access to GPT4-32k, but if we did, we'd explore dumping entire API/SDK documentation into context.
96 |
97 | The feedback loop is very slow right now (`time` says about 2-4 mins to generate a program with GPT4, even with parallelization due to Modal (occasionally spiking higher)), but it's a safe bet that it will go down over time (see also "future directions" below).
98 |
99 | ## install
100 |
101 | it's basically:
102 |
103 | - `git clone https://github.com/smol-ai/developer`.
104 | - copy over `.example.env` to `.env` filling in your API keys.
105 |
106 | There are no python dependencies to wrangle thanks to using Modal as a [self-provisioning runtime](https://www.google.com/search?q=self+provisioning+runtime).
107 |
108 | Unfortunately this project also uses 3 other things:
109 |
110 | - Modal.com - [sign up](https://modal.com/signup), then `pip install modal-client && modal token new`
111 | - You can run this project w/o Modal following these instructions:
112 | - `pip install -r requirements.txt`
113 | - `export OPENAI_API_KEY=sk-xxxxxx` (your openai api key here)
114 | - `python main_no_modal.py YOUR_PROMPT_HERE`
115 | - GPT-4 api (private beta) - this project now defaults to using `gpt-3.5-turbo` but it obviously wont be as good. we are working on a hosted version so you can try this out on our keys.
116 | - (for the demo project only) anthropic claude 100k context api (private beta) - not important unless you're exactly trying to repro my demo
117 |
118 | you'll have to adapt this code on a fork if you want to use it on other infra. please open issues/PRs and i'll happily highlight your fork here.
119 |
120 | ### trying the example chrome extension from the demo video
121 |
122 | the `/examples/exampleChromeExtension` folder contains `a Chrome Manifest V3 extension that reads the current page, and offers a popup UI that has the page title+content and a textarea for a prompt (with a default value we specify). When the user hits submit, it sends the page title+content to the Anthropic Claude API along with the up to date prompt to summarize it. The user can modify that prompt and re-send the prompt+content to get another summary view of the content.`
123 |
124 | - go to Manage Extensions in Chrome
125 | - load unpacked
126 | - find the relevant folder in your file system and load it
127 | - go to any content heavy site
128 | - click the cute bird
129 | - see it work and rejoice
130 |
131 | this entire extension was generated by the prompt in `prompt.md` (except for the images), and was built up over time by adding more words to the prompt in an iterative process.
132 |
133 | ## usage: smol dev
134 |
135 | basic usage (by default it runs with `gpt-3.5-turbo`, but we strongly encourage running with `gpt-4` if you have access)
136 |
137 | ```bash
138 | # inline prompt
139 | modal run main.py --prompt "a Chrome extension that, when clicked, opens a small window with a page where you can enter a prompt for reading the currently open page and generating some response from openai" --model=gpt-4
140 | ```
141 |
142 | after a while of adding to your prompt, you can extract your prompt to a file, as long as your "prompt" ends in a .md extension we'll go look for that file
143 |
144 | ```bash
145 | # prompt in markdown file
146 | modal run main.py --prompt prompt.md --model=gpt-4
147 | ```
148 |
149 | each time you run this, the generated directory is deleted (except for images) and all files are rewritten from scratch.
150 |
151 | In the `shared_dependencies.md` file is a helper file that ensures coherence between files. This is in the process of being expanded into an official `--plan` functionality (see https://github.com/smol-ai/developer/issues/12)
152 |
153 | ### smol dev in single file mode
154 |
155 | if you make a tweak to the prompt and only want it to affect one file, and keep the rest of the files, specify the file param:
156 |
157 | ```bash
158 | modal run main.py --prompt prompt.md --file popup.js
159 | ```
160 |
161 | ### smol dev without modal.com
162 |
163 | By default, `main.py` uses Modal, beacuse it provides a nice upgrade path to a hosted experience (coming soon, so you can try it out without needing GPT4 key access).
164 |
165 | However if you want to just run it on your own machine, you can run smol dev w/o Modal following these instructions:
166 |
167 | ```bash
168 | pip install -r requirements.txt
169 | export OPENAI_API_KEY=sk-xxxxxx # your openai api key here)
170 |
171 | python main_no_modal.py YOUR_PROMPT_HERE
172 | ```
173 |
174 | If no command line argument is given, **and** the file `prompt.md` exists, the main function will automatically use the `prompt.md` file. All other command line arguments are left as default. *this is handy for those using the "run" function on a `venv` setup in PyCharm for Windows, where no opportunity is given to enter command line arguments. Thanks [@danmenzies](https://github.com/smol-ai/developer/pull/55)*
175 |
176 | ## usage: smol debugger
177 |
178 | *this is a beta feature, very very MVP, just a proof of concept really*
179 |
180 | take the entire contents of the generated directory in context, feed in an error, get a response. this basically takes advantage of longer (32k-100k) context so we basically dont have to do any embedding of the source.
181 |
182 | ```bash
183 | modal run debugger.py --prompt "Uncaught (in promise) TypeError: Cannot destructure property 'pageTitle' of '(intermediate value)' as it is undefined. at init (popup.js:59:11)"
184 |
185 | # gpt4
186 | modal run debugger.py --prompt "your_error msg_here" --model=gpt-4
187 | ```
188 |
189 | ## usage: smol pm
190 |
191 | *this is even worse than beta, its kind of a "let's see what happens" experiment*
192 |
193 | take the entire contents of the generated directory in context, and get a prompt back that could synthesize the whole program. basically `smol dev`, in reverse.
194 |
195 | ```bash
196 | modal run code2prompt.py # ~0.5 second with gpt 3.5
197 |
198 | # use gpt4
199 | modal run code2prompt.py --model=gpt-4 # 2 mins, MUCH better results
200 | ```
201 |
202 | We have done indicative runs of both, stored in `examples/code2prompt/code2prompt-gpt3.md` vs `examples/code2prompt/code2prompt-gpt4.md`. Note how incredibly better gpt4 is at prompt engineering its future self.
203 |
204 | Naturally, we had to try `code2prompt2code`...
205 |
206 | ```bash
207 | # add prompt... this needed a few iterations to get right
208 | modal run code2prompt.py --prompt "make sure all the id's of the DOM elements, and the data structure of the page content (stored with {pageTitle, pageContent }) , referenced/shared by the js files match up exactly. take note to only use Chrome Manifest V3 apis. rename the extension to code2prompt2code" --model=gpt-4 # takes 4 mins. produces semi working chrome extension copy based purely on the model-generated description of a different codebase
209 |
210 | # must go deeper
211 | modal run main.py --prompt code2prompt-gpt4.md --directory code2prompt2code
212 | ```
213 |
214 | We leave the social and technical impacts of multilayer generative deep-frying of codebases as an exercise to the reader.
215 |
216 | ## Development using a Dev Container
217 |
218 | > this is a [new addition](https://github.com/smol-ai/developer/pull/30)! Please try it out and send in fixes if there are any issues.
219 |
220 | We have configured a development container for this project, which provides an isolated and consistent development environment. This approach is ideal for developers using Visual Studio Code's Remote - Containers extension or GitHub's Codespaces.
221 |
222 | If you have [VS Code](https://code.visualstudio.com/download) and [Docker](https://www.swyx.io/running-docker-without-docker-desktop) installed on your machine, you can make use of the devcontainer to create an isolated environment with all dependencies automatically installed and configured. This is a great way to ensure a consistent development experience across different machines.
223 |
224 | Here are the steps to use the devcontainer:
225 |
226 | 1. Open this project in VS Code.
227 | 2. When prompted to "Reopen in Container", choose "Reopen in Container". This will start the process of building the devcontainer defined by the `Dockerfile` and `.devcontainer.json` in the `.devcontainer` directory.
228 | 3. Wait for the build to finish. The first time will be a bit longer as it downloads and builds everything. Future loads will be much faster.
229 | 4. Once the build is finished, the VS Code window will reload and you are now working inside the devcontainer.
230 |
231 |
232 |
233 | Benefits of a Dev Container
234 |
235 | 1. **Consistent Environment**: Every developer works within the same development setup, eliminating "it works on my machine" issues and easing the onboarding of new contributors.
236 |
237 | 2. **Sandboxing**: Your development environment is isolated from your local machine, allowing you to work on multiple projects with differing dependencies without conflict.
238 |
239 | 3. **Version Control for Environments**: Just as you version control your source code, you can do the same with your development environment. If a dependency update introduces issues, it's easy to revert to a previous state.
240 |
241 | 4. **Easier CI/CD Integration**: If your CI/CD pipeline utilizes Docker, your testing environment will be identical to your local development environment, ensuring consistency across development, testing, and production setups.
242 |
243 | 5. **Portability**: This setup can be utilized on any computer with Docker and the appropriate IDE installed. Simply clone the repository and start the container.
244 |
245 |
246 |
247 | ## future directions
248 |
249 | things to try/would accept open issue discussions and PRs:
250 |
251 | - **specify .md files for each generated file**, with further prompts that could finetune the output in each of them
252 | - so basically like `popup.html.md` and `content_script.js.md` and so on
253 | - **bootstrap the `prompt.md`** for existing codebases - write a script to read in a codebase and write a descriptive, bullet pointed prompt that generates it
254 | - done by `smol pm`, but its not very good yet - would love for some focused polish/effort until we have quine smol developer that can generate itself lmao
255 | - **ability to install its own dependencies**
256 | - this leaks into depending on the execution environment, which we all know is the path to dependency madness. how to avoid? dockerize? nix? [web container](https://twitter.com/litbid/status/1658154530385670150)?
257 | - Modal has an interesting possibility: generate functions that speak modal which also solves the dependency thing https://twitter.com/akshat_b/status/1658146096902811657
258 | - **self-heal** by running the code itself and use errors as information for reprompting
259 | - however its a bit hard to get errors from the chrome extension environment so we did not try this
260 | - **using anthropic as the coding layer**
261 | - you can run `modal run anthropic.py --prompt prompt.md --outputdir=anthropic` to try it
262 | - but it doesnt work because anthropic doesnt follow instructions to generate file code very well.
263 | - **make agents that autonomously run this code in a loop/watch the prompt file** and regenerate code each time, on a new git branch
264 | - the code could be generated on 5 simultaneous git branches and checking their output would just involve switching git branches
265 |
--------------------------------------------------------------------------------