@speak2web/create-chat 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 speak2web
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @speak2web/create-chat
2
+
3
+ Scaffold a **pre-wired Speak2Web AIN/AIC chat app** against your headless bridge — so day-one is
4
+ a working app, not hand-wiring `createClient` + provider + widget + env vars (SPEAK-234).
5
+
6
+ ```bash
7
+ npm create @speak2web/chat my-app
8
+ # or
9
+ npx @speak2web/create-chat my-app
10
+ ```
11
+
12
+ Interactive by default — it prompts for the template and your bridge values, then writes them to
13
+ the project's env file. Or pass flags for a non-interactive run:
14
+
15
+ ```bash
16
+ npx @speak2web/create-chat my-app \
17
+ --template next-app \
18
+ --api-base https://your-site.com/wp-json/s2w/v1 \
19
+ --site-id pub_abc123 \
20
+ --yes
21
+ ```
22
+
23
+ ## Options
24
+
25
+ | Flag | Description |
26
+ |---|---|
27
+ | `<project-name>` | Positional. Directory to create (must not exist). |
28
+ | `-t, --template` | `next-app` (default) or `vite-react`. |
29
+ | `--api-base` | Bridge REST base, e.g. `https://site.com/wp-json/s2w/v1`. |
30
+ | `--token-endpoint` | JWT issuer. Defaults to `<api-base>/auth/jwt`. |
31
+ | `--site-id` | Public site id from **AI-Gateway → Headless / JWT** (`pub_…`). |
32
+ | `-y, --yes` | Skip prompts; use flags/defaults (also auto-on when there's no TTY). |
33
+ | `-h, --help` | Show help. |
34
+
35
+ ## Templates
36
+
37
+ | Template | Stack | Source | Env file / prefix |
38
+ |---|---|---|---|
39
+ | `next-app` | Next.js (App Router) | `ecis2-next-pilot` | `.env.local` / `NEXT_PUBLIC_S2W_*` |
40
+ | `vite-react` | Vite + plain React | `bridge-harness` | `.env` / `VITE_S2W_*` |
41
+
42
+ Both mount `createClient()` → `<AICChatProvider>` → `<AICChat>` (the wiring in
43
+ `ecis2-next-pilot/app/chat-widget.tsx`).
44
+
45
+ ## After scaffolding
46
+
47
+ ```bash
48
+ cd my-app
49
+ npm install
50
+ npm run dev # http://localhost:5173
51
+ ```
52
+
53
+ Then make sure the app's origin is allowlisted in **AI-Gateway → Headless / JWT → Allowed
54
+ origins** (e.g. `http://localhost:5173`), or the token mint (`/auth/jwt`) returns `403`.
55
+
56
+ ## SDK dependency (SPEAK-230)
57
+
58
+ Generated apps depend on **`@speak2web/ain-aic-sdk`**. Until it's published to npm (SPEAK-230),
59
+ `npm install` can't resolve it from the registry — link a local build instead:
60
+
61
+ ```bash
62
+ # in the SDK repo
63
+ npm run build && npm link
64
+ # in the generated app
65
+ npm link @speak2web/ain-aic-sdk
66
+ ```
67
+
68
+ Zero runtime dependencies — Node 18+ built-ins only.
package/index.js ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @speak2web/create-chat — scaffold a pre-wired AIN/AIC chat app (SPEAK-234).
4
+ *
5
+ * Productizes the reference apps (ecis2-next-pilot → next-app, bridge-harness →
6
+ * vite-react) into starters so day-one is a working app, not hand-wiring
7
+ * createClient + provider + widget + env vars.
8
+ *
9
+ * npm create @speak2web/chat my-app
10
+ * npx @speak2web/create-chat my-app --template next-app \
11
+ * --api-base https://site.com/wp-json/s2w/v1 --site-id pub_abc123 --yes
12
+ *
13
+ * Zero runtime deps — Node 18+ built-ins only (matches the SDK's no-deps ethos).
14
+ */
15
+ import { parseArgs } from 'node:util';
16
+ import { createInterface } from 'node:readline/promises';
17
+ import { stdin, stdout, cwd, exit, argv } from 'node:process';
18
+ import { cp, rename, writeFile, readFile, readdir, stat } from 'node:fs/promises';
19
+ import { existsSync } from 'node:fs';
20
+ import { join, resolve, dirname } from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+
23
+ const HERE = dirname(fileURLToPath(import.meta.url));
24
+ const TEMPLATES_DIR = join(HERE, 'templates');
25
+
26
+ const TEMPLATES = {
27
+ 'next-app': {
28
+ label: 'Next.js (App Router)',
29
+ envFile: '.env.local',
30
+ envPrefix: 'NEXT_PUBLIC_S2W_',
31
+ run: 'npm run dev # http://localhost:5173',
32
+ },
33
+ 'vite-react': {
34
+ label: 'Vite + plain React',
35
+ envFile: '.env',
36
+ envPrefix: 'VITE_S2W_',
37
+ run: 'npm run dev',
38
+ },
39
+ };
40
+
41
+ const PLACEHOLDER_API_BASE = 'https://your-site.com/wp-json/s2w/v1';
42
+ const PLACEHOLDER_SITE_ID = 'pub_xxxxxxxx';
43
+
44
+ function printHelp() {
45
+ stdout.write(`
46
+ create-chat — scaffold a Speak2Web AIN/AIC chat app
47
+
48
+ Usage:
49
+ npm create @speak2web/chat <project-name> [options]
50
+ npx @speak2web/create-chat <project-name> [options]
51
+
52
+ Options:
53
+ -t, --template <name> next-app (default) | vite-react
54
+ --api-base <url> bridge REST base, e.g. https://site.com/wp-json/s2w/v1
55
+ --token-endpoint <url> JWT issuer (default: <api-base>/auth/jwt)
56
+ --site-id <pub_…> public site id from AI-Gateway → Headless / JWT
57
+ -y, --yes accept defaults / skip prompts (non-interactive)
58
+ -h, --help show this help
59
+
60
+ Templates are pre-wired to @speak2web/ain-aic-sdk (on npm); bridge values are
61
+ written to the project's env file.
62
+ `);
63
+ }
64
+
65
+ /** Recursively list every file under dir (absolute paths). */
66
+ async function walk(dir) {
67
+ const out = [];
68
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
69
+ const full = join(dir, entry.name);
70
+ if (entry.isDirectory()) out.push(...(await walk(full)));
71
+ else out.push(full);
72
+ }
73
+ return out;
74
+ }
75
+
76
+ /** Replace {{TOKENS}} in every text file under dir. */
77
+ async function replaceTokens(dir, tokens) {
78
+ for (const file of await walk(dir)) {
79
+ let text;
80
+ try {
81
+ text = await readFile(file, 'utf8');
82
+ } catch {
83
+ continue; // unreadable/binary — skip
84
+ }
85
+ let next = text;
86
+ for (const [k, v] of Object.entries(tokens)) next = next.split(k).join(v);
87
+ if (next !== text) await writeFile(file, next);
88
+ }
89
+ }
90
+
91
+ async function main() {
92
+ const { values, positionals } = parseArgs({
93
+ args: argv.slice(2),
94
+ allowPositionals: true,
95
+ options: {
96
+ template: { type: 'string', short: 't' },
97
+ 'api-base': { type: 'string' },
98
+ 'token-endpoint': { type: 'string' },
99
+ 'site-id': { type: 'string' },
100
+ yes: { type: 'boolean', short: 'y' },
101
+ help: { type: 'boolean', short: 'h' },
102
+ },
103
+ });
104
+
105
+ if (values.help) {
106
+ printHelp();
107
+ return;
108
+ }
109
+
110
+ // Non-interactive when --yes, or when there's no TTY (CI, pipes) — fall back to
111
+ // flag values / defaults instead of hanging on a prompt.
112
+ const interactive = !values.yes && stdin.isTTY;
113
+ const rl = interactive ? createInterface({ input: stdin, output: stdout }) : null;
114
+ const ask = async (question, def) => {
115
+ if (!rl) return def;
116
+ const answer = (await rl.question(`${question}${def ? ` (${def})` : ''}: `)).trim();
117
+ return answer || def;
118
+ };
119
+
120
+ try {
121
+ const projectName = positionals[0] ?? (await ask('Project name', 'my-s2w-chat'));
122
+ if (!projectName || /[/\\]/.test(projectName)) {
123
+ throw new Error(`Invalid project name: "${projectName}"`);
124
+ }
125
+
126
+ let template = values.template ?? (await ask('Template (next-app / vite-react)', 'next-app'));
127
+ if (!TEMPLATES[template]) {
128
+ throw new Error(`Unknown template "${template}". Choose: ${Object.keys(TEMPLATES).join(', ')}`);
129
+ }
130
+ const spec = TEMPLATES[template];
131
+
132
+ const apiBase = values['api-base'] ?? (await ask('Bridge apiBase', PLACEHOLDER_API_BASE));
133
+ const tokenDefault = values['token-endpoint'] ?? `${apiBase.replace(/\/+$/, '')}/auth/jwt`;
134
+ const tokenEndpoint = values['token-endpoint'] ?? (await ask('Token endpoint', tokenDefault));
135
+ const siteId = values['site-id'] ?? (await ask('Public siteId (pub_…)', PLACEHOLDER_SITE_ID));
136
+
137
+ rl?.close();
138
+
139
+ const targetDir = resolve(cwd(), projectName);
140
+ if (existsSync(targetDir)) {
141
+ throw new Error(`Refusing to overwrite existing path: ${targetDir}`);
142
+ }
143
+ const templateDir = join(TEMPLATES_DIR, template);
144
+ if (!existsSync(templateDir)) {
145
+ throw new Error(`Template files missing: ${templateDir}`);
146
+ }
147
+
148
+ // 1. Copy the template tree.
149
+ await cp(templateDir, targetDir, { recursive: true });
150
+
151
+ // 2. npm strips `.gitignore` from published tarballs, so templates ship it as
152
+ // `gitignore` — restore the dot in the generated project.
153
+ if (existsSync(join(targetDir, 'gitignore'))) {
154
+ await rename(join(targetDir, 'gitignore'), join(targetDir, '.gitignore'));
155
+ }
156
+
157
+ // 3. Fill placeholders (project name in package.json / README).
158
+ await replaceTokens(targetDir, { '{{PROJECT_NAME}}': projectName });
159
+
160
+ // 4. Write the bridge config to the template's env file.
161
+ const env =
162
+ `# Speak2Web bridge — written by create-chat. ${spec.envPrefix}* values are\n` +
163
+ `# read by the app at build/start.\n` +
164
+ `${spec.envPrefix}API_BASE=${apiBase}\n` +
165
+ `${spec.envPrefix}TOKEN_ENDPOINT=${tokenEndpoint}\n` +
166
+ `${spec.envPrefix}SITE_ID=${siteId}\n`;
167
+ await writeFile(join(targetDir, spec.envFile), env);
168
+
169
+ // 5. Report.
170
+ const warnUnset = apiBase === PLACEHOLDER_API_BASE || siteId === PLACEHOLDER_SITE_ID;
171
+ stdout.write(`\n✅ Created ${projectName} (${spec.label})\n`);
172
+ stdout.write(` bridge: ${apiBase} · siteId: ${siteId}\n`);
173
+ if (warnUnset) {
174
+ stdout.write(
175
+ `\n⚠️ Placeholder bridge values — edit ${projectName}/${spec.envFile} with your\n` +
176
+ ` real apiBase / siteId (AI-Gateway → Headless / JWT) before the chat will connect.\n`,
177
+ );
178
+ }
179
+ stdout.write(`\nNext:\n cd ${projectName}\n npm install\n ${spec.run}\n\n`);
180
+ } finally {
181
+ rl?.close();
182
+ }
183
+ }
184
+
185
+ main().catch((err) => {
186
+ stdout.write(`\n✖ ${err.message}\n`);
187
+ exit(1);
188
+ });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@speak2web/create-chat",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold a pre-wired Speak2Web AIN/AIC chat app (Next.js App Router or Vite/React) against your headless bridge. `npm create @speak2web/chat`.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "create-chat": "index.js",
9
+ "create-s2w-chat": "index.js"
10
+ },
11
+ "files": [
12
+ "index.js",
13
+ "templates",
14
+ "README.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "keywords": [
23
+ "speak2web",
24
+ "ain-aic-sdk",
25
+ "scaffold",
26
+ "create",
27
+ "chat",
28
+ "headless-wordpress"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+ssh://git@bitbucket.org/speak2web/s2w-ain-aic-sdk.git",
33
+ "directory": "create-chat"
34
+ }
35
+ }
@@ -0,0 +1,42 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A Speak2Web **AI Navigator / AI Concierge** chat app on **Next.js (App Router)**, scaffolded by
4
+ [`@speak2web/create-chat`](https://bitbucket.org/speak2web/s2w-ain-aic-sdk).
5
+
6
+ ## Run
7
+
8
+ ```bash
9
+ npm install
10
+ npm run dev # http://localhost:5173
11
+ ```
12
+
13
+ Open the page and use the chat bubble (bottom-right).
14
+
15
+ ## How it's wired
16
+
17
+ - **`app/chat-widget.tsx`** — the client island: `createClient()` → `<AICChatProvider>` → `<AICChat>`.
18
+ - **`lib/config.ts`** — reads `NEXT_PUBLIC_S2W_*` env vars.
19
+ - **`.env.local`** — your bridge values (`apiBase`, `tokenEndpoint`, `siteId`), written by the scaffolder. Edit it to retarget.
20
+
21
+ Want a styled widget? `import '@speak2web/ain-aic-sdk/react/aic-chat.css'` and pass `theme="dark"`.
22
+ Want lead capture? Add `enableEscalation` to `<AICChat>`.
23
+
24
+ ## CORS
25
+
26
+ The bridge only accepts requests from **allowlisted origins**. Add this app's origin
27
+ (e.g. `http://localhost:5173`) in **AI-Gateway → Headless / JWT → Allowed origins**, or the
28
+ token mint (`/auth/jwt`) returns `403`.
29
+
30
+ ## SDK dependency
31
+
32
+ This app depends on `@speak2web/ain-aic-sdk`. Until it's published to npm (SPEAK-230), point the
33
+ dependency at a local build:
34
+
35
+ ```bash
36
+ # in the SDK repo
37
+ npm run build && npm link
38
+ # here
39
+ npm link @speak2web/ain-aic-sdk
40
+ ```
41
+
42
+ or set `"@speak2web/ain-aic-sdk": "file:../path/to/s2w-ain-aic-sdk"` in `package.json`.
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+
3
+ import { useMemo } from 'react';
4
+ import { createClient } from '@speak2web/ain-aic-sdk';
5
+ import { AICChatProvider, AICChat } from '@speak2web/ain-aic-sdk/react';
6
+ import { config } from '../lib/config';
7
+
8
+ /**
9
+ * The headless chat island: createClient() against your bridge, then the
10
+ * opinionated React provider + drop-in widget. The JWT is minted once on first
11
+ * call and reused (watch the Network tab for one /auth/jwt then Bearer on /chat).
12
+ *
13
+ * Auto-bridge-on-mount (adr-0004 §D4): pass a `sessionId` to AICChatProvider to
14
+ * resume a prior conversation across rendering layers. `enableEscalation` adds an
15
+ * opt-in "Talk to a human" lead-capture flow. Import the stylesheet for a styled
16
+ * widget: `import '@speak2web/ain-aic-sdk/react/aic-chat.css'`.
17
+ */
18
+ export default function ChatWidget() {
19
+ const client = useMemo(
20
+ () =>
21
+ createClient({
22
+ apiBase: config.apiBase,
23
+ tokenEndpoint: config.tokenEndpoint,
24
+ siteId: config.siteId,
25
+ visitorFingerprint: config.visitorFingerprint,
26
+ }),
27
+ [],
28
+ );
29
+
30
+ return (
31
+ <div
32
+ style={{
33
+ position: 'fixed',
34
+ right: 16,
35
+ bottom: 16,
36
+ width: 360,
37
+ maxWidth: 'calc(100vw - 32px)',
38
+ background: '#fff',
39
+ border: '1px solid #e5e7eb',
40
+ borderRadius: 12,
41
+ boxShadow: '0 8px 30px rgba(0,0,0,0.12)',
42
+ overflow: 'hidden',
43
+ }}
44
+ >
45
+ <div
46
+ style={{
47
+ padding: '10px 14px',
48
+ borderBottom: '1px solid #f1f1f4',
49
+ fontWeight: 600,
50
+ fontSize: 14,
51
+ }}
52
+ >
53
+ Chat
54
+ </div>
55
+ <div style={{ padding: 12 }}>
56
+ <AICChatProvider client={client} onError={(e) => console.error('[s2w bridge]', e)}>
57
+ <AICChat greeting="Hi! How can I help?" placeholder="Type a message…" />
58
+ </AICChatProvider>
59
+ </div>
60
+ </div>
61
+ );
62
+ }
@@ -0,0 +1,23 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export const metadata = {
4
+ title: 'Speak2Web chat',
5
+ description: 'Next.js front end with AIN/AIC chat via @speak2web/ain-aic-sdk.',
6
+ };
7
+
8
+ export default function RootLayout({ children }: { children: ReactNode }) {
9
+ return (
10
+ <html lang="en">
11
+ <body
12
+ style={{
13
+ margin: 0,
14
+ fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, sans-serif',
15
+ color: '#1a1a1a',
16
+ background: '#f6f7f9',
17
+ }}
18
+ >
19
+ {children}
20
+ </body>
21
+ </html>
22
+ );
23
+ }
@@ -0,0 +1,43 @@
1
+ import ChatWidget from './chat-widget';
2
+
3
+ // Server component (App Router). The chat widget is a client island mounted below;
4
+ // everything it needs talks to your bridge over /wp-json/s2w/v1 — no WordPress theme.
5
+ export default function Home() {
6
+ return (
7
+ <main style={{ maxWidth: 820, margin: '0 auto', padding: '32px 16px 96px' }}>
8
+ <header style={{ marginBottom: 24 }}>
9
+ <p
10
+ style={{
11
+ fontSize: 12,
12
+ letterSpacing: 0.5,
13
+ textTransform: 'uppercase',
14
+ color: '#6b7280',
15
+ margin: 0,
16
+ }}
17
+ >
18
+ Speak2Web · headless chat
19
+ </p>
20
+ <h1 style={{ fontSize: 28, margin: '4px 0 8px' }}>Your Next.js front end</h1>
21
+ <p style={{ color: '#4b5563', margin: 0 }}>
22
+ This page is rendered by Next.js. The chat bubble (bottom-right) talks to AI
23
+ Navigator / AI Concierge through the <code>/wp-json/s2w/v1</code> bridge via{' '}
24
+ <code>@speak2web/ain-aic-sdk</code>. Edit the wiring in{' '}
25
+ <code>app/chat-widget.tsx</code> and the bridge config in <code>.env.local</code>.
26
+ </p>
27
+ </header>
28
+
29
+ <section
30
+ style={{ background: '#fff', border: '1px solid #e5e7eb', borderRadius: 12, padding: 20 }}
31
+ >
32
+ <h2 style={{ fontSize: 18, marginTop: 0 }}>It works</h2>
33
+ <p style={{ color: '#4b5563', margin: 0 }}>
34
+ Open the chat in the bottom-right corner and say hello. If it can&apos;t connect,
35
+ confirm this origin is allowlisted in <strong>AI-Gateway → Headless / JWT → Allowed
36
+ origins</strong> and that your <code>.env.local</code> values are correct.
37
+ </p>
38
+ </section>
39
+
40
+ <ChatWidget />
41
+ </main>
42
+ );
43
+ }
@@ -0,0 +1,12 @@
1
+ node_modules
2
+ /.next
3
+ /out
4
+ /build
5
+ *.tsbuildinfo
6
+ next-env.d.ts
7
+
8
+ # env (the bridge config create-chat wrote)
9
+ .env*.local
10
+
11
+ .DS_Store
12
+ *.log
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Bridge config. Values come from NEXT_PUBLIC_S2W_* env vars (inlined at build,
3
+ * so they're available in the browser). `create-chat` wrote your real values into
4
+ * `.env.local`; the defaults below are placeholders for reference.
5
+ */
6
+ export const config = {
7
+ /** AIN/AIC public REST surface (adr-0002): /wp-json/s2w/v1 */
8
+ apiBase: process.env.NEXT_PUBLIC_S2W_API_BASE ?? 'https://your-site.com/wp-json/s2w/v1',
9
+ /** AIG JWT issuer (adr-0003) */
10
+ tokenEndpoint:
11
+ process.env.NEXT_PUBLIC_S2W_TOKEN_ENDPOINT ?? 'https://your-site.com/wp-json/s2w/v1/auth/jwt',
12
+ /** Public site id (AIG → Headless / JWT, `pub_…`). Tokens are bound to it. */
13
+ siteId: process.env.NEXT_PUBLIC_S2W_SITE_ID ?? 'pub_xxxxxxxx',
14
+ /**
15
+ * Optional fixed visitor fingerprint. Set it ONLY for local dev (localhost has no
16
+ * apex cookie) so cross-renderer continuity still works while testing; leave unset
17
+ * in production, where the apex `s2w_visitor` cookie drives a stable identity.
18
+ */
19
+ visitorFingerprint: process.env.NEXT_PUBLIC_S2W_VISITOR_FP || undefined,
20
+ };
@@ -0,0 +1,5 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,8 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ // Transpile the SDK so a single React instance is used and ESM/CJS interop is clean.
4
+ transpilePackages: ['@speak2web/ain-aic-sdk'],
5
+ reactStrictMode: true,
6
+ };
7
+
8
+ export default nextConfig;
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Speak2Web AIN/AIC chat app (Next.js App Router) — scaffolded by @speak2web/create-chat.",
6
+ "scripts": {
7
+ "dev": "next dev -p 5173",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@speak2web/ain-aic-sdk": "^1.0.0",
14
+ "next": "^14.2.5",
15
+ "react": "^18.3.1",
16
+ "react-dom": "^18.3.1"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^20.14.0",
20
+ "@types/react": "^18.3.0",
21
+ "@types/react-dom": "^18.3.0",
22
+ "typescript": "^5.6.0"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ }
27
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": false,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": {
18
+ "@/*": ["./*"]
19
+ }
20
+ },
21
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22
+ "exclude": ["node_modules"]
23
+ }
@@ -0,0 +1,28 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A Speak2Web **AI Navigator / AI Concierge** chat app on **Vite + React**, scaffolded by
4
+ [`@speak2web/create-chat`](https://bitbucket.org/speak2web/s2w-ain-aic-sdk).
5
+
6
+ ## Run
7
+
8
+ ```bash
9
+ npm install
10
+ npm run dev # http://localhost:5173
11
+ ```
12
+
13
+ ## How it's wired
14
+
15
+ - **`src/main.jsx`** — `createClient()` → `<AICChatProvider>` → `<AICChat>` (with the default stylesheet imported).
16
+ - **`.env`** — your bridge values (`VITE_S2W_API_BASE`, `VITE_S2W_TOKEN_ENDPOINT`, `VITE_S2W_SITE_ID`), written by the scaffolder. Edit it to retarget.
17
+
18
+ ## CORS
19
+
20
+ The bridge only accepts requests from **allowlisted origins**. Add this app's origin
21
+ (e.g. `http://localhost:5173`) in **AI-Gateway → Headless / JWT → Allowed origins**, or the
22
+ token mint (`/auth/jwt`) returns `403`.
23
+
24
+ ## SDK dependency
25
+
26
+ This app depends on `@speak2web/ain-aic-sdk`. Until it's published to npm (SPEAK-230), link a
27
+ local build: `npm link @speak2web/ain-aic-sdk` (after `npm run build && npm link` in the SDK
28
+ repo), or set a `file:` path in `package.json`.
@@ -0,0 +1,10 @@
1
+ node_modules
2
+ dist
3
+ dist-ssr
4
+
5
+ # env (the bridge config create-chat wrote)
6
+ .env
7
+ .env.local
8
+
9
+ .DS_Store
10
+ *.log
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Speak2Web chat</title>
7
+ </head>
8
+ <body style="margin: 0; background: #f6f7f9">
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.jsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "Speak2Web AIN/AIC chat app (Vite + React) — scaffolded by @speak2web/create-chat.",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@speak2web/ain-aic-sdk": "^1.0.0",
14
+ "react": "^18.3.1",
15
+ "react-dom": "^18.3.1"
16
+ },
17
+ "devDependencies": {
18
+ "@vitejs/plugin-react": "^4.3.4",
19
+ "vite": "^5.4.11"
20
+ },
21
+ "engines": {
22
+ "node": ">=18"
23
+ }
24
+ }
@@ -0,0 +1,36 @@
1
+ import React, { useMemo } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { createClient } from '@speak2web/ain-aic-sdk';
4
+ import { AICChatProvider, AICChat } from '@speak2web/ain-aic-sdk/react';
5
+ import '@speak2web/ain-aic-sdk/react/aic-chat.css';
6
+
7
+ // Bridge config from VITE_S2W_* env vars (create-chat wrote your values into .env).
8
+ const config = {
9
+ apiBase: import.meta.env.VITE_S2W_API_BASE ?? 'https://your-site.com/wp-json/s2w/v1',
10
+ tokenEndpoint:
11
+ import.meta.env.VITE_S2W_TOKEN_ENDPOINT ?? 'https://your-site.com/wp-json/s2w/v1/auth/jwt',
12
+ siteId: import.meta.env.VITE_S2W_SITE_ID ?? 'pub_xxxxxxxx',
13
+ // Set VITE_S2W_VISITOR_FP only for local dev (localhost has no apex cookie).
14
+ visitorFingerprint: import.meta.env.VITE_S2W_VISITOR_FP || undefined,
15
+ };
16
+
17
+ function App() {
18
+ // createClient() mints the JWT once and reuses it (watch the Network tab for one
19
+ // /auth/jwt then Bearer on /chat). Then the opinionated provider + drop-in widget.
20
+ const client = useMemo(() => createClient(config), []);
21
+
22
+ return (
23
+ <div style={{ maxWidth: 480, margin: '40px auto', fontFamily: 'system-ui, sans-serif', padding: '0 16px' }}>
24
+ <h1 style={{ fontSize: 20 }}>Speak2Web chat</h1>
25
+ <p style={{ color: '#4b5563', fontSize: 14 }}>
26
+ Wired via <code>@speak2web/ain-aic-sdk</code>. Edit <code>src/main.jsx</code>; bridge
27
+ config lives in <code>.env</code>. Add <code>enableEscalation</code> for lead capture.
28
+ </p>
29
+ <AICChatProvider client={client} onError={(e) => console.error('[s2w bridge]', e)}>
30
+ <AICChat greeting="Hi! How can I help?" placeholder="Type a message…" theme="light" />
31
+ </AICChatProvider>
32
+ </div>
33
+ );
34
+ }
35
+
36
+ createRoot(document.getElementById('root')).render(<App />);
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ // Port 5173 is a common default to allowlist in AI-Gateway → Headless / JWT.
7
+ server: { port: 5173, strictPort: true },
8
+ // The SDK externalizes react/react-dom; dedupe so the app and the SDK share one
9
+ // React copy (avoids the "two Reacts" invalid-hook-call error).
10
+ resolve: { dedupe: ['react', 'react-dom'] },
11
+ });