Skip to content

Commit 0114986

Browse files
authored
Form Copilot (#30)
## Background This PR introduces **Form Copilot** as a MIT-licensed forkable demo. The demo runs live at https://form-copilot.simplepdf.com. **Form Copilot** allows filling PDF forms using LLMs by leveraging client-side tool calling over an iframe bridge (introduced as part of this PR). ## Changes - Add the **Form Copilot** demo at `form-copilot/`, built with TanStack Start (React 19, Vite, Nitro), Tailwind CSS, and the Vercel AI SDK - Add the iframe bridge at `form-copilot/src/lib/embed-bridge/` together with two adapters: `embed-bridge-adapters/react` (host-side `useEmbedBridge` hook) and `embed-bridge-adapters/client-tools` (LLM-tool input/output Zod schemas) ## Notes We will most likely move the iframe bridge into the [web-embed package](https://github.com/SimplePDF/simplepdf-embed/tree/main/web) since we've so far shipped poor-man's bridges per consumer (less capable, less durable, less typed than what a shared package would offer).
1 parent 53f1df4 commit 0114986

113 files changed

Lines changed: 25997 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.do/deploy.template.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
spec:
2+
name: form-copilot
3+
services:
4+
- name: web
5+
github:
6+
repo: SimplePDF/simplepdf-embed
7+
branch: main
8+
deploy_on_push: false
9+
source_dir: /form-copilot
10+
environment_slug: node-js
11+
build_command: npm run build
12+
run_command: npm start
13+
http_port: 3000
14+
instance_count: 1
15+
instance_size_slug: apps-s-1vcpu-1gb
16+
envs:
17+
- key: VITE_SIMPLEPDF_COMPANY_IDENTIFIER
18+
scope: BUILD_TIME
19+
type: GENERAL
20+
- key: SHARED_API_KEYS
21+
scope: RUN_TIME
22+
type: SECRET
23+
- key: REDIS_URL
24+
scope: RUN_TIME
25+
type: SECRET
26+
- key: IP_HASH_SALT
27+
scope: RUN_TIME
28+
type: SECRET

form-copilot/.cta.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"projectName": "form-copilot",
3+
"mode": "file-router",
4+
"typescript": true,
5+
"packageManager": "npm",
6+
"includeExamples": false,
7+
"tailwind": true,
8+
"addOnOptions": {},
9+
"envVarValues": {},
10+
"git": false,
11+
"install": true,
12+
"routerOnly": false,
13+
"version": 1,
14+
"framework": "react",
15+
"chosenAddOns": [
16+
"biome",
17+
"nitro"
18+
]
19+
}

form-copilot/.env.example

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# =============================================================================
2+
# Access keys (OPTIONAL: drives the invite-link `?share=<id>` flow)
3+
# =============================================================================
4+
#
5+
# Leave SHARED_API_KEYS unset and the demo runs in BYOK-only mode: every
6+
# visitor brings their own provider key via the in-app Model Picker, and
7+
# the stream goes browser-direct (never through this server). This is
8+
# the easiest setup if you only need it for yourself.
9+
#
10+
# Set SHARED_API_KEYS to skip BYOK and let non-technical viewers chat
11+
# without pasting a key. Each entry maps a share id to an api key, a
12+
# model handle, and a per-IP lifetime cap on chat turns. Visitors land
13+
# on `?share=<id>` and the server pays for the LLM under your account.
14+
# Requests without a recognized `?share=<id>` still return 401 and the
15+
# client falls back to BYOK.
16+
#
17+
# Shape (per share id):
18+
#
19+
# {
20+
# "<share_id>": {
21+
# "api_key": "<provider api key>",
22+
# "model": "<anthropic_haiku_4_5 | deepseek_v4_flash>",
23+
# "rate_limit_turns_lifetime": 20
24+
# }
25+
# }
26+
#
27+
# Each share id has its own per-IP lifetime counter. With REDIS_URL set
28+
# (see below), counters survive restarts and are shared across containers.
29+
# Without REDIS_URL, counters live in memory per container (fine for
30+
# single-instance / BYOK-only deployments). The reserved id "__default__"
31+
# is rejected at parse time. Example with two invites on two providers:
32+
#
33+
# SHARED_API_KEYS='{"preview-alice":{"api_key":"sk-ant-...","model":"anthropic_haiku_4_5","rate_limit_turns_lifetime":5},"customer-acme":{"api_key":"sk-...","model":"deepseek_v4_flash","rate_limit_turns_lifetime":100}}'
34+
#
35+
# Visitors then open `http://localhost:3001/?share=preview-alice` (or
36+
# the equivalent on your hosted domain).
37+
#
38+
# -----------------------------------------------------------------------------
39+
# Base64 fallback (recommended for hosted deploys)
40+
# -----------------------------------------------------------------------------
41+
# Some hosts (DigitalOcean App Platform, Render, fly.io app specs) mangle
42+
# embedded or surrounding quotes when storing JSON-shaped env vars. The
43+
# parser accepts a base64-encoded payload as an alternative: plain JSON
44+
# is tried first, base64-then-JSON second. Either works.
45+
#
46+
# Portable one-liner (macOS + Linux):
47+
#
48+
# printf '%s' '{"preview-alice":{"api_key":"sk-ant-...","model":"anthropic_haiku_4_5","rate_limit_turns_lifetime":5}}' | base64 | tr -d '\n'
49+
#
50+
# Paste the resulting ASCII-only string as the value, with no surrounding
51+
# quotes. The parser also auto-strips one wrapping pair of single or
52+
# double quotes if your host shell-escapes the value into the env.
53+
SHARED_API_KEYS=
54+
55+
# =============================================================================
56+
# Client configuration
57+
# =============================================================================
58+
59+
# SimplePDF company identifier. The subdomain piece of
60+
# <companyIdentifier>.simplepdf.com that serves the embedded editor.
61+
# Exposed to the browser (VITE_ prefix) because the iframe src is built
62+
# client-side. REQUIRED: the app throws at startup if unset.
63+
#
64+
# `form-copilot` is the public demo workspace and whitelists
65+
# `http://localhost:3001`, so the iframe loads as-is for local dev. To
66+
# host this on your own domain, set this to your own SimplePDF
67+
# Premium company identifier and whitelist your serving origin in the
68+
# SimplePDF dashboard.
69+
VITE_SIMPLEPDF_COMPANY_IDENTIFIER=form-copilot
70+
71+
72+
# Optional. Gates the TanStack Router devtools panel + chatty monitoring
73+
# logs. Leave unset in production / CI; set to "true" for local dev.
74+
# VITE_ENABLE_DEVTOOLS=true
75+
76+
# =============================================================================
77+
# Rate-limit storage (optional; required for multi-container deployments)
78+
# =============================================================================
79+
80+
# Connection URL to a Redis-protocol-compatible instance (Valkey, Redis,
81+
# KeyDB, etc.). Required for hosted deployments where you want
82+
# per-(share, IP) lifetime counters to survive restarts AND be shared
83+
# across multiple containers. Without it, counters live in memory per
84+
# container (fine for local dev, single-instance hosts, or BYOK-only
85+
# deployments).
86+
#
87+
# DO Managed Caching for Valkey: $15/mo single-node, drop-in protocol-
88+
# compatible. The dashboard returns a `rediss://default:<password>@<host>:25061/0`
89+
# URL. The password is INSIDE the URL, so this value is a secret and must
90+
# be stored as such (the deploy.template.yaml at the repo root marks it
91+
# `type: SECRET`).
92+
# REDIS_URL=rediss://default:<password>@<host>:25061/0
93+
94+
# REQUIRED when REDIS_URL is set (the server refuses to boot otherwise).
95+
# Salts the SHA-256 IP hash used in Redis keys (rl:<share>:<hash>).
96+
# Without a salt, a leak of the Redis snapshot lets anyone brute-force the
97+
# ~4B IPv4 space in minutes. With a salt, the brute force needs the
98+
# server-side secret too. Optional in BYOK-only mode (no REDIS_URL set),
99+
# since hashes never persist beyond the process.
100+
#
101+
# Generate with: openssl rand -hex 32
102+
# IP_HASH_SALT=

form-copilot/.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
node_modules
2+
.DS_Store
3+
dist
4+
dist-ssr
5+
*.local
6+
.env
7+
.nitro
8+
.tanstack
9+
.wrangler
10+
.output
11+
.vinxi
12+
__unconfig*
13+
todos.json

form-copilot/.vscode/settings.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"files.watcherExclude": {
3+
"**/routeTree.gen.ts": true
4+
},
5+
"search.exclude": {
6+
"**/routeTree.gen.ts": true
7+
},
8+
"files.readonlyInclude": {
9+
"**/routeTree.gen.ts": true
10+
},
11+
"[javascript]": {
12+
"editor.defaultFormatter": "biomejs.biome"
13+
},
14+
"[javascriptreact]": {
15+
"editor.defaultFormatter": "biomejs.biome"
16+
},
17+
"[typescript]": {
18+
"editor.defaultFormatter": "biomejs.biome"
19+
},
20+
"[typescriptreact]": {
21+
"editor.defaultFormatter": "biomejs.biome"
22+
},
23+
"[json]": {
24+
"editor.defaultFormatter": "biomejs.biome"
25+
},
26+
"[jsonc]": {
27+
"editor.defaultFormatter": "biomejs.biome"
28+
},
29+
"[css]": {
30+
"editor.defaultFormatter": "biomejs.biome"
31+
},
32+
"editor.codeActionsOnSave": {
33+
"source.organizeImports.biome": "explicit"
34+
}
35+
}

form-copilot/LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) SimplePDF Engineering <engineering@simplepdf.com>
4+
(https://simplepdf.com)
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.

0 commit comments

Comments
 (0)