Skip to content

Commit d478508

Browse files
CoveMBericglau
andauthored
Add API development server (#504)
Co-authored-by: Eric Lau <[email protected]>
1 parent 2dbe765 commit d478508

31 files changed

+325
-67
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ node_modules
44

55
.env
66
.env.local
7-
.vscode/
7+
.vscode/settings.json
8+
9+
*deno.lock

.vscode/example.settings.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"editor.defaultFormatter": "esbenp.prettier-vscode",
3+
"editor.formatOnSave": true,
4+
"deno.enablePaths": [
5+
"packages/ui/api",
6+
"packages/ui/scripts/development-server.ts"
7+
],
8+
"deno.enable": true,
9+
"deno.path": "/opt/homebrew/bin/deno" // Update this to your Deno path
10+
}

.vscode/extensions.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"recommendations": [
3+
"esbenp.prettier-vscode",
4+
"dbaeumer.vscode-eslint",
5+
"denoland.vscode-deno",
6+
"ms-vscode.vscode-typescript-tslint-plugin",
7+
]
8+
}

CONTRIBUTING.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
Contributing to OpenZeppelin Contracts Wizard
2+
=======
3+
4+
Contributions to OpenZeppelin Contracts Wizard are welcome. Please review the information below to test out and contribute your changes.
5+
6+
## Project layout
7+
8+
- `packages/core` contains the code generation logic for each language under separately named subfolders.
9+
- `packages/ui` is the interface built in Svelte.
10+
11+
## Building and testing the project
12+
13+
### Prerequisites
14+
The following prerequisites are required to build the project locally:
15+
- [Node.js](https://nodejs.org/)
16+
- [Yarn](https://yarnpkg.com/getting-started/install)
17+
18+
If you want to run the local API server for the AI Assistant, you also need to install [Deno](https://github.com/denoland/deno?tab=readme-ov-file#installation).
19+
20+
### Installing dependencies
21+
From the root directory:
22+
- ```yarn install```
23+
24+
The dependencies must be installed at least once before proceeding to any of the below.
25+
26+
### Running tests
27+
From each language's subfolder under `packages/core`:
28+
- ```yarn test```
29+
30+
Ensure that all tests pass. If you are adding new functionality, include testcases as appropriate.
31+
If tests fail due to snapshots not matching and the new behavior is expected, run `yarn test:update-snapshots` to update AVA snapshots and commit the changes.
32+
33+
> [!TIP]
34+
> From the root directory, you can use `yarn run:core` to run specific commands within `core/{language}` folders.
35+
> For example, running `yarn run:core cairo test` from the root directory will run tests for Cairo.
36+
37+
### Running linter
38+
From the root directory:
39+
- ```yarn lint```
40+
41+
If linting errors or warnings occur, run `yarn lint --fix` to attempt to auto-fix issues. If there are remaining issues that cannot be auto-fixed, manually address them and re-run the command to ensure it passes.
42+
43+
### Running the UI
44+
From the `packages/ui` directory:
45+
- Run `yarn dev` to start a local server for the UI.
46+
- Default port is 8080. To use another port, set the environment variable `PORT`, for example: `PORT=800 yarn dev`
47+
48+
### Running the AI Assistant (Optional)
49+
Create a `.env` file at the root directory, set the environment variable `OPENAI_API_KEY` using your OpenAI API key, and configure your OpenAI project limits to allow the `gpt-4o-mini` model or set the environment variable `OPENAI_MODEL` to a specific model.
50+
51+
Then from the `packages/ui` directory:
52+
- In one terminal, start the UI according to the above section if the UI isn't already running.
53+
- In another terminal, run `yarn dev:api` to start a local API server which handles AI Assistant functions.
54+
- Default port is 3000. To use another port, set the environment variable `API_PORT`
55+
56+
> [!TIP]
57+
> You can also start both the UI and API servers simultaneously by running `yarn dev` from the root directory.
58+
59+
## Creating Pull Requests (PRs)
60+
61+
As a contributor, we ask that you fork this repository, work on your own fork and then submit pull requests. The pull requests will be reviewed and eventually merged into the main repo. See ["Fork-a-Repo"](https://help.github.com/articles/fork-a-repo/) for how this works.

README.md

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,9 @@ Contracts Wizard is a web application to interactively build a contract out of c
88

99
[![](./screenshot.png)](https://wizard.openzeppelin.com/)
1010

11-
## Development
11+
## Contributing
1212

13-
Install dependencies with `yarn install`.
14-
15-
`packages/core` contains the code generation logic for each language under separately named subfolders. From each language's subfolder:
16-
- Run `yarn test` to run the tests.
17-
- Run `yarn test:update-snapshots` to update AVA snapshots and run the tests.
18-
- Run `yarn lint` to run the linter across the codebase (optionally `yarn lint --fix` will automatically fix fixable issues, like formatting issues).
19-
20-
`packages/ui` is the interface built in Svelte. From the `packages/ui` directory, run `yarn dev` to spin up a local server to develop the UI.
21-
- By default, the local server runs on port 8080. To use another port, set the environment variable `PORT`, for example: `PORT=800 yarn dev`
22-
23-
You'll need to supply your own environment variables if you want to enable Wizard AI Assistant (OPENAI_API_KEY) and/or logging (REDIS_URL, REDIS_TOKEN).
24-
25-
You can run yarn commands directly into `core/{language}` folders with `yarn run:core`.
26-
For example, running `yarn run:core cairo test` from the root directory will run tests for Cairo.
13+
See our [contributing guidelines](CONTRIBUTING.md).
2714

2815
## Embedding
2916

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"prepare": "wsrun -m prepare",
66
"lint": "eslint",
77
"dev:ui": "yarn --cwd ./packages/ui dev",
8+
"dev:api": "yarn --cwd ./packages/ui dev:api",
9+
"dev": "concurrently --kill-others-on-fail --names \"ui,api\" --prefix-colors \"magenta.bold,green.bold\" \"yarn dev:ui\" \"yarn dev:api\"",
810
"run:core": "node ./scripts/run-command.mjs"
911
},
1012
"workspaces": {
@@ -19,6 +21,7 @@
1921
},
2022
"devDependencies": {
2123
"@eslint/js": "^9.21.0",
24+
"concurrently": "^9.1.2",
2225
"eslint": "^9.21.0",
2326
"eslint-config-prettier": "^10.0.1",
2427
"eslint-plugin-prettier": "^5.2.3",
@@ -28,4 +31,4 @@
2831
"typescript-eslint": "^8.24.1",
2932
"wsrun": "^5.2.4"
3033
}
31-
}
34+
}

packages/ui/api/ai.ts

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import OpenAI from 'https://esm.sh/[email protected]';
2-
import { OpenAIStream, StreamingTextResponse } from 'https://esm.sh/[email protected]';
1+
import { OpenAIStream } from 'https://esm.sh/[email protected]';
32
import {
43
erc20Function,
54
erc721Function,
@@ -9,28 +8,16 @@ import {
98
governorFunction,
109
customFunction,
1110
} from '../src/solidity/wiz-functions.ts';
12-
import { Redis } from 'https://esm.sh/@upstash/[email protected]';
11+
import { getRedisInstance } from './services/redis.ts';
12+
import { getOpenAiInstance } from './services/open-ai.ts';
13+
import { getEnvironmentVariableOr } from './utils/env.ts';
1314

14-
export default async (req: Request) => {
15+
export default async (req: Request): Promise<Response> => {
1516
try {
1617
const data = await req.json();
17-
const apiKey = Deno.env.get('OPENAI_API_KEY');
1818

19-
const redisUrl = Deno.env.get('REDIS_URL');
20-
const redisToken = Deno.env.get('REDIS_TOKEN');
21-
22-
if (!redisUrl || !redisToken) {
23-
throw new Error('missing redis credentials');
24-
}
25-
26-
const redis = new Redis({
27-
url: redisUrl,
28-
token: redisToken,
29-
});
30-
31-
const openai = new OpenAI({
32-
apiKey: apiKey,
33-
});
19+
const redis = getRedisInstance();
20+
const openai = getOpenAiInstance();
3421

3522
const validatedMessages = data.messages.filter((message: { role: string; content: string }) => {
3623
return message.content.length < 500;
@@ -49,7 +36,7 @@ export default async (req: Request) => {
4936
];
5037

5138
const response = await openai.chat.completions.create({
52-
model: 'gpt-4-1106-preview',
39+
model: getEnvironmentVariableOr('OPENAI_MODEL', 'gpt-4o-mini'),
5340
messages,
5441
functions: [
5542
erc20Function,
@@ -87,7 +74,14 @@ export default async (req: Request) => {
8774
await redis.hset(`chat:${id}`, payload);
8875
},
8976
});
90-
return new StreamingTextResponse(stream);
77+
78+
return new Response(stream, {
79+
headers: new Headers({
80+
'Access-Control-Allow-Origin': '*',
81+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
82+
'Content-Type': 'text/html; charset=utf-8',
83+
}),
84+
});
9185
} catch (e) {
9286
console.error('Could not retrieve results:', e);
9387
return Response.json({
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// deno-lint-ignore-file require-await
2+
import type { Redis } from 'https://esm.sh/@upstash/[email protected]';
3+
4+
export default class RedisMock implements Pick<Redis, 'exists' | 'hset'> {
5+
private store: Record<
6+
string,
7+
{
8+
[field: string]: unknown;
9+
}
10+
> = {};
11+
12+
async exists(key: string) {
13+
return this.store[key] ? 1 : 0;
14+
}
15+
16+
async hset(
17+
key: string,
18+
kv: {
19+
[field: string]: unknown;
20+
},
21+
) {
22+
this.store[key] = this.store[key] || {};
23+
24+
const newFields = Object.entries(kv).filter(([field]) => !(field in this.store[key]));
25+
26+
newFields.forEach(([field, value]) => {
27+
this.store[key][field] = value;
28+
});
29+
30+
return newFields.length;
31+
}
32+
}

packages/ui/api/services/open-ai.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import OpenAI from 'https://esm.sh/[email protected]';
2+
import { getEnvironmentVariablesOrFail } from '../utils/env.ts';
3+
4+
export const getOpenAiInstance = () => {
5+
const { OPENAI_API_KEY } = getEnvironmentVariablesOrFail('OPENAI_API_KEY');
6+
7+
return new OpenAI({
8+
apiKey: OPENAI_API_KEY,
9+
});
10+
};

packages/ui/api/services/redis.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Redis } from 'https://esm.sh/@upstash/[email protected]';
2+
import RedisMock from './dev-mocks/redis.ts';
3+
import { getEnvironmentVariableOr, getEnvironmentVariablesOrFail } from '../utils/env.ts';
4+
5+
export const getRedisInstance = () => {
6+
if (getEnvironmentVariableOr('ENV', 'production') === 'dev') return new RedisMock();
7+
8+
const { REDIS_URL, REDIS_TOKEN } = getEnvironmentVariablesOrFail(['REDIS_URL', 'REDIS_TOKEN']);
9+
10+
return new Redis({
11+
url: REDIS_URL,
12+
token: REDIS_TOKEN,
13+
});
14+
};

0 commit comments

Comments
 (0)