Skip to content

Update evi-proxy to use Node and Vite #177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions evi/evi-proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ This app is useful as an example in its own right: it demonstrates
* how to connect to EVI from a Typescript backend,
* how to accept websocket connections, process messages, and send them upstream to EVI

See [upstream.ts](app/upstream.ts) and [downstream.ts](app/downstream.ts) for more details.

It is also useful as a debugging tool: it supports
* recording and replaying EVI conversations,
* simulating error conditions that you might want to handle to make your EVI application more robust.

## Prerequisites

- [Bun](https://bun.sh/) runtime
- Node.js (for web frontend build)
- Hume AI API credentials
- Node.js (for running the proxy and building the web frontend)
- Hume AI API credentials

## Installation

Expand All @@ -26,8 +27,8 @@ It is also useful as a debugging tool: it supports

2. Install dependencies for both app and web components:
```bash
cd app && bun install
cd ../web && bun install
cd app && npm install
cd ../web && npm install && npm run build
cd ..
```

Expand All @@ -50,7 +51,7 @@ To get your API key:
### Start the Proxy Server

```bash
cd app && bun run start
cd app && npm start
```

This starts the WebSocket proxy server on port 3000 with an interactive CLI interface. The CLI allows you to:
Expand Down Expand Up @@ -82,6 +83,9 @@ The proxy also includes a built-in web interface available at:
```
http://localhost:3000
```
The interface is built using [Vite](https://vitejs.dev). If you modify any
frontend code, run `npm run build` in the `web/` directory again to rebuild the
static assets.

### Recording and Playback

Expand All @@ -93,7 +97,7 @@ http://localhost:3000

```
eviproxy/
├── app/ # Main proxy server (Bun/Node.js)
├── app/ # Main proxy server (Node.js)
│ ├── main.ts # Entry point and state machine
│ ├── cli.ts # Interactive CLI interface
│ ├── upstream.ts # Hume API connections
Expand Down
231 changes: 0 additions & 231 deletions evi/evi-proxy/app/clack_prompts.ts

This file was deleted.

5 changes: 3 additions & 2 deletions evi/evi-proxy/app/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { State, AppEvent } from '../shared/types.ts';
import { ERROR_CODES, CLOSE_TYPES, ERROR_CODE_KEYS } from '../shared/types.ts';
import * as p from "./clack_prompts.js";
import * as sharedTypes from '../shared/types.ts';
const { ERROR_CODES, CLOSE_TYPES, ERROR_CODE_KEYS } = sharedTypes;
import * as p from "@clack/prompts";
import { exhaustive } from './util.ts';

const abortable = <T>(signal: AbortSignal, p: Promise<T>): Promise<T> => {
Expand Down
45 changes: 26 additions & 19 deletions evi/evi-proxy/app/main.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import 'dotenv/config';
import * as fs from "fs";
import * as p from "@clack/prompts";
import * as http from "http";
import * as path from "path";
import { fileURLToPath } from "url";
import { CLI } from "./cli.js";
import type { Message, State, AppEvent, Effect } from '../shared/types.ts';
import { ERROR_CODES } from '../shared/types.ts';
import * as sharedTypes from '../shared/types.ts';
const { ERROR_CODES } = sharedTypes;
import { Api } from "./api.js";
import { BaseUpstream, LiveUpstream, PlaybackUpstream, UninitializedUpstream } from "./upstream.js";
import { Downstream } from "./downstream.js";
import { exhaustive, truncateDataReplacer } from "./util.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DIST_DIR = path.join(__dirname, "../web/dist");

const PORT = 3000;
const DOWNSTREAM_WS_PATH = "/v0/evi/chat";
const UPSTREAM_WS_BASE_URL = "wss://api.hume.ai";
Expand Down Expand Up @@ -38,31 +45,31 @@ const serve = async (req: http.IncomingMessage, res: http.ServerResponse) => {
}
}

// This builds the frontend on each request
await Bun.build({
entrypoints: ["../web/index.html"],
outdir: "../out",
target: "browser",
});
if (url.pathname === "/" || url.pathname === "/index.html") {
res.writeHead(200, {
"Content-Type": "text/html",
"Cache-Control": "no-cache",
});
res.write(await Bun.file("../out/index.html").text());
res.end();
return;
const htmlPath = path.join(DIST_DIR, "index.html");
if (fs.existsSync(htmlPath)) {
res.writeHead(200, {
"Content-Type": "text/html",
"Cache-Control": "no-cache",
});
res.write(fs.readFileSync(htmlPath));
res.end();
return;
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.write("out/index.html not found. Run `cd web && npm install && npm run build` to build the frontend.");
res.end();
return;
}
}
// Serve any file from out/ (e.g. chunk-*.js, chunk-*.css)
const filePath = `../out${url.pathname}`;
const file = Bun.file(filePath);
if (await file.exists()) {
const filePath = path.join(DIST_DIR, url.pathname);
if (fs.existsSync(filePath)) {
let contentType = "application/octet-stream";
if (filePath.endsWith(".js")) contentType = "application/javascript";
else if (filePath.endsWith(".css")) contentType = "text/css";
else if (filePath.endsWith(".html")) contentType = "text/html";
res.writeHead(200, { "Content-Type": contentType });
res.write(await file.text());
res.write(fs.readFileSync(filePath));
res.end();
return;
}
Expand Down
Loading