Skip to content

compare and merge #55

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

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
93ccb2b
Add pest control landing page with chat
moemeyer May 20, 2025
b267143
Rename .env.example to .env
moemeyer May 21, 2025
d27ec16
Merge pull request #1 from moemeyer/codex/create-high-conversion-pest…
moemeyer May 22, 2025
c22022c
Add Briostack function calling example
moemeyer May 23, 2025
8689b33
Add Briostack function check script
moemeyer May 23, 2025
dec16ed
Merge pull request #4 from moemeyer/4lnqn1-codex/integrate-briostack-…
moemeyer May 23, 2025
213e1ce
Merge pull request #3 from moemeyer/codex/integrate-briostack-functio…
moemeyer May 24, 2025
fc30ba7
Add Briostack helpers and UI tweaks
moemeyer May 24, 2025
2181da8
Merge pull request #5 from moemeyer/codex/review-api-functions-and-we…
moemeyer May 24, 2025
016a6b7
Add Briostack function call handler
moemeyer May 24, 2025
fcb15db
Merge pull request #6 from moemeyer/codex/implement-briostack-functio…
moemeyer May 24, 2025
b1bffd5
Add note about network access
moemeyer May 24, 2025
9495c6d
Merge pull request #7 from moemeyer/codex/ensure-openai-access-on-sta…
moemeyer May 24, 2025
e232d30
Add files via upload
moemeyer May 25, 2025
a8b9f87
Add Briostack API examples
moemeyer May 25, 2025
4b06fe7
Merge pull request #8 from moemeyer/codex/find-required-scripts-for-app
moemeyer May 25, 2025
d8612f6
Expand Briostack helpers
moemeyer May 26, 2025
555a29f
Merge pull request #9 from moemeyer/codex/use-functions-to-create-or-…
moemeyer May 26, 2025
0e299f7
Remove old commented JSX
moemeyer May 26, 2025
53e7bc1
chore: type messages state
moemeyer May 26, 2025
5f4a073
fix file upload condition and reduce polling
moemeyer May 26, 2025
ee86f32
Add messaging, payment, and GHL integration routes
moemeyer May 26, 2025
2f6edc2
Merge pull request #14 from moemeyer/codex/prepare-app-for-deployment…
moemeyer May 26, 2025
085a06b
docs: note about npm proxy warning
moemeyer May 26, 2025
496902b
Merge pull request #15 from moemeyer/codex/check-for-new-updates-and-…
moemeyer May 26, 2025
eae71c2
Improve chat layout responsiveness
moemeyer May 27, 2025
7627e65
Merge pull request #16 from moemeyer/codex/implement-realtime-api-int…
moemeyer May 27, 2025
bc10692
Add NextBillion route optimization integration
moemeyer May 27, 2025
bb44feb
Merge pull request #17 from moemeyer/codex/investigate-nextbillion-ai…
moemeyer May 27, 2025
1b57808
Merge pull request #10 from moemeyer/codex/remove-commented-out-jsx-b…
moemeyer May 27, 2025
c22f868
Merge pull request #11 from moemeyer/codex/define-message-interface-a…
moemeyer May 28, 2025
c31e671
Merge pull request #12 from moemeyer/codex/update-file-viewer.tsx-com…
moemeyer May 28, 2025
b1fd2f8
Add error handling for file viewer and realtime
moemeyer May 28, 2025
71820b4
Merge pull request #18 from moemeyer/codex/update-api-calls-and-refac…
moemeyer May 28, 2025
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
37 changes: 37 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# DO NOT COMMIT THIS FILE - IT WILL CONTAIN YOUR SENSITIVE KEYS.
# Instead, rename this file to ".env" instead of ".env.example". (.env will not be committed)

# Create an OpenAI API key at https://platform.openai.com/account/api-keys
OPENAI_API_KEY="sk-123..."

# (optional) Create an OpenAI Assistant at https://platform.openai.com/assistants.
# IMPORTANT! Be sure to enable file search and code interpreter tools.
OPENAI_ASSISTANT_ID="123..."

# Briostack API configuration
BRIOSTACK_INSTANCE_NAME="demo-instance"
BRIOSTACK_API_KEY="demo-api-key"
NEXT_PUBLIC_BRIOSTACK_INSTANCE_NAME="demo-instance"
NEXT_PUBLIC_BRIOSTACK_API_KEY="demo-api-key"

# Twilio configuration
TWILIO_ACCOUNT_SID="your-sid"
TWILIO_AUTH_TOKEN="your-token"
TWILIO_PHONE_NUMBER="+1234567890"

# SendGrid configuration
SENDGRID_API_KEY="sg-key"
SENDGRID_FROM_EMAIL="[email protected]"

# Stripe configuration
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_SUCCESS_URL="https://yourapp.com/success"
STRIPE_CANCEL_URL="https://yourapp.com/cancel"

# GoHighLevel / Lead Connector
GHL_API_DOMAIN="link.jom.services"
GHL_API_KEY="your-ghl-api-key"

# NextBillion Route Optimization
NEXTBILLION_API_KEY="nb-key"
NEXT_PUBLIC_NEXTBILLION_API_KEY="nb-key"
9 changes: 0 additions & 9 deletions .env.example

This file was deleted.

3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next"
}
114 changes: 112 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,21 @@ git clone https://github.com/openai/openai-assistants-quickstart.git
cd openai-assistants-quickstart
```

### 2. Set your [OpenAI API key](https://platform.openai.com/api-keys)
### 2. Set your [OpenAI API key](https://platform.openai.com/api-keys) and other credentials

```shell
export OPENAI_API_KEY="sk_..."
# Optional integrations
export BRIOSTACK_INSTANCE_NAME="demo-instance"
export BRIOSTACK_API_KEY="demo-key"
export TWILIO_ACCOUNT_SID="your-sid"
export TWILIO_AUTH_TOKEN="your-token"
export TWILIO_PHONE_NUMBER="+1234567890"
export SENDGRID_API_KEY="sg-key"
export SENDGRID_FROM_EMAIL="[email protected]"
export STRIPE_SECRET_KEY="sk_test_..."
export GHL_API_DOMAIN="link.jom.services"
export GHL_API_KEY="your-ghl-api-key"
```

(or in `.env.example` and rename it to `.env`).
Expand All @@ -27,14 +38,36 @@ export OPENAI_API_KEY="sk_..."
```shell
npm install
```
You can also use `pnpm install` or `yarn` if you prefer those package managers.

Ensure this step completes while your environment still has network
access so all required packages are downloaded.

> **Note**: If `npm install` prints `npm warn Unknown env config "http-proxy"`,
> your shell is exporting `npm_config_http_proxy` or `npm_config_https_proxy`.
> Those variables are deprecated in newer versions of npm. Unset them or rename
> them to `HTTP_PROXY`/`HTTPS_PROXY` (or `npm_config_proxy`) to remove the
> warning.

### 4. Run

```shell
npm run dev
```

### 5. Navigate to [http://localhost:3000](http://localhost:3000).
### 5. Run tests

```shell
npm test
```

This uses Node's built-in test runner to verify the included Briostack OpenAPI specification.

### 6. Navigate to [http://localhost:3000](http://localhost:3000).

The Briostack API specification used by the examples lives at
`public/briostack-openapi.json`. Feel free to explore it for a full list of
available endpoints. A basic test in `test/briostack-openapi.test.js` ensures this file can be loaded and contains the expected title.

## Deployment

Expand Down Expand Up @@ -68,6 +101,83 @@ The main logic for chat will be found in the `Chat` component in `app/components
- `api/assistants/threads/[threadId]/actions` - `POST`: inform assistant of the result of a function it decided to call
- `api/assistants/files` - `GET`/`POST`/`DELETE`: fetch, upload, and delete assistant files for file search

### Briostack Function Example

To check that your assistant has the `call_briostack` function registered, run:

```ts
npm install
node scripts/check-functions.ts
```

This prints the assistant's registered functions. You can also run

```ts
node scripts/briostack-example.ts
```

to try a simple request if you have set `BRIOSTACK_INSTANCE_NAME` and
`BRIOSTACK_API_KEY` in your environment. You can also store these
values in the browser by visiting [`/briostack-connect`](./briostack-connect).
The page saves your instance name and API key to `localStorage` so the
helper functions can use them on subsequent visits.

The `app/utils/briostack.ts` file also exposes helper methods like
`listCustomers`, `getCustomer`, and `listProperties` for common API calls.
Additional helpers such as `listAppointments`, `createAppointment`, and
`listServices` demonstrate how to access other paid endpoints defined in the
`public/briostack-openapi.json` specification. Recent additions
`listWebhooks`, `createWebhook`, `getWebhook`, and `updateWebhook` make it easy
to manage webhook resources as well. New helpers `createService` and
`updateService` let you modify a service's `serviceSchedule` if needed. You can
try them with the new
script:

```ts
node scripts/briostack-appointments.ts
```

You can also inspect a service's schedule with:

```ts
node scripts/briostack-services.ts
```


### NextBillion Route Optimization

This project also includes a helper for [NextBillion's Route Optimization API](https://nextbillion.ai/route-optimization-api).
Use the `optimize_routes` function to post a JSON payload describing jobs and vehicles.
The helper lives in `app/utils/nextbillion.ts` and reads `NEXTBILLION_API_KEY` from the environment.

```ts
import { callNextBillionOptimizer } from "./app/utils/nextbillion";
const result = await callNextBillionOptimizer({ payload: { /* ... */ } });
```

You can experiment with this by calling `optimize_routes` in the example chats.


### Realtime API Demo

This repo now includes a minimal example page using the new Realtime API.
Run `npm run dev` and navigate to `/realtime` to try a WebRTC session in your
browser. The `/api/realtime/session` endpoint mints an ephemeral token using
your `OPENAI_API_KEY` for authentication. The client page then connects to the
Realtime model and streams events back and forth.

### Messaging and Payments

This template now includes optional API routes for sending SMS via Twilio,
sending email via SendGrid, creating Stripe Checkout sessions, and proxying
requests to GoHighLevel's Lead Connector API. Configure the related environment
variables in `.env` and POST to the following endpoints:

- `POST /api/messages/send-sms` – send an SMS message
- `POST /api/messages/send-email` – send an email
- `POST /api/payments/create-checkout-session` – create a Stripe checkout session
- `POST /api/ghl/proxy` – proxy a call to your GHL domain

## Feedback

Let us know if you have any thoughts, questions, or feedback in [this form](https://docs.google.com/forms/d/e/1FAIpQLScn_RSBryMXCZjCyWV4_ebctksVvQYWkrq90iN21l1HLv3kPg/viewform?usp=sf_link)!
52 changes: 52 additions & 0 deletions app/api/assistants/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,58 @@ export async function POST() {
},
},
},
{
type: "function",
function: {
name: "call_briostack",
description: "Make a request to the Briostack API",
parameters: {
type: "object",
properties: {
endpoint: {
type: "string",
description: "The Briostack endpoint path, e.g. '/v1/properties'",
},
method: {
type: "string",
enum: ["GET", "POST", "PUT", "DELETE"],
},
payload: {
type: "object",
description: "JSON body for POST/PUT requests",
},
query: {
type: "object",
description: "Query params as key/value pairs",
},
},
required: ["endpoint", "method"],
},
},
},
{
type: "function",
function: {
name: "optimize_routes",
description:
"Optimize technician routes using the NextBillion API",
parameters: {
type: "object",
properties: {
payload: {
type: "object",
description:
"JSON body to send to NextBillion's route optimization API",
},
apiKey: {
type: "string",
description: "Optional NextBillion API key override",
},
},
required: ["payload"],
},
},
},
{ type: "file_search" },
],
});
Expand Down
24 changes: 24 additions & 0 deletions app/api/ghl/proxy/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NextRequest } from "next/server";

export const runtime = "nodejs";

export async function POST(req: NextRequest) {
const { endpoint, method = "GET", payload, query } = await req.json();
const domain = process.env.GHL_API_DOMAIN;
const key = process.env.GHL_API_KEY;
if (!domain || !key) {
return new Response("GHL not configured", { status: 500 });
}
const url = new URL(`https://${domain}${endpoint}`);
if (query) Object.entries(query).forEach(([k, v]) => url.searchParams.append(k, String(v)));
const res = await fetch(url.toString(), {
method,
headers: {
"Authorization": `Bearer ${key}`,
"Content-Type": "application/json",
},
body: payload ? JSON.stringify(payload) : undefined,
});
const bodyText = await res.text();
return new Response(bodyText, { status: res.status });
}
16 changes: 16 additions & 0 deletions app/api/messages/send-email/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { NextRequest } from "next/server";
import sgMail from "@sendgrid/mail";

export const runtime = "nodejs";

export async function POST(req: NextRequest) {
const { to, subject, text, html } = await req.json();
const apiKey = process.env.SENDGRID_API_KEY;
const from = process.env.SENDGRID_FROM_EMAIL;
if (!apiKey || !from) {
return new Response("SendGrid not configured", { status: 500 });
}
sgMail.setApiKey(apiKey);
await sgMail.send({ to, from, subject, text, html });
return Response.json({ success: true });
}
17 changes: 17 additions & 0 deletions app/api/messages/send-sms/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NextRequest } from "next/server";
import twilio from "twilio";

export const runtime = "nodejs";

export async function POST(req: NextRequest) {
const { to, body } = await req.json();
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const from = process.env.TWILIO_PHONE_NUMBER;
if (!accountSid || !authToken || !from) {
return new Response("Twilio not configured", { status: 500 });
}
const client = twilio(accountSid, authToken);
const message = await client.messages.create({ from, to, body });
return Response.json({ sid: message.sid });
}
23 changes: 23 additions & 0 deletions app/api/payments/create-checkout-session/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NextRequest } from "next/server";
import Stripe from "stripe";

export const runtime = "nodejs";

export async function POST(req: NextRequest) {
const secret = process.env.STRIPE_SECRET_KEY;
if (!secret) {
return new Response("Stripe not configured", { status: 500 });
}
const stripe = new Stripe(secret, { apiVersion: "2023-10-16" });
const { priceId } = await req.json();
const session = await stripe.checkout.sessions.create({
mode: "subscription",
payment_method_types: ["card"],
line_items: [{ price: priceId, quantity: 1 }],
success_url:
process.env.STRIPE_SUCCESS_URL || "https://example.com/success",
cancel_url:
process.env.STRIPE_CANCEL_URL || "https://example.com/cancel",
});
return Response.json({ url: session.url });
}
28 changes: 28 additions & 0 deletions app/api/realtime/session/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { NextRequest } from "next/server";

export const runtime = "nodejs";

export async function POST(req: NextRequest) {
const { model = "gpt-4o-realtime-preview-2024-12-17", voice = "nova" } = await req.json();
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
return new Response("OpenAI API key not configured", { status: 500 });
}

const res = await fetch("https://api.openai.com/v1/realtime/sessions", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ model, voice }),
});

if (!res.ok) {
const text = await res.text();
return new Response(text, { status: res.status });
}

const data = await res.json();
return Response.json(data);
}
Loading