chore: settle product naming across app, widget and deployments

One word everywhere: ReplyPier in UI copy, package and image names, the
replypier.site domains (app/api/docs/help), REPLYPIER_* settings and the
--rp-* design tokens.

- public widget API is window.ReplyPier with replypierId / replypierBaseUrl /
  replypierConfig / replypierToken, loader at webclient/replypier.min.js;
  both bundles rebuilt from source
- NOTICE, llms.txt, CONTRIBUTING, HELP_CENTER_INFRA and the demo runtime
  config rewritten; the demo config ships no third-party keys
- migration adds the ReplyPier managed model value to the AI model enum
- local compose project, database, role, volume and Docker network carry the
  same name
This commit is contained in:
WangShen007 committed 2026-10-26 00:57:22 +08:00
1 parent ff0a09f0e5
commit 20fef2b9f3
1156 files changed
+7267 -7148

No files matched your search

+2 -2
View File
@@ -4,7 +4,7 @@
# NO_ENTERPRISE and the real DATABASE_URL are exported by stack.sh (they depend on
# the edition and the local Postgres). This placeholder points nowhere, so a backend
# started without stack.sh fails fast instead of falling back to the dev database.
DATABASE_URL=postgresql+psycopg://set-by-stack-sh@127.0.0.1:1/chattermate_e2e
DATABASE_URL=postgresql+psycopg://set-by-stack-sh@127.0.0.1:1/replypier_e2e
ENVIRONMENT=test
JWT_SECRET_KEY=e2e-jwt-secret-not-for-production
@@ -35,7 +35,7 @@ FIREBASE_CREDENTIALS=/nonexistent/firebase-config.json
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
# Providers that ignore the org's base_url (e.g. the CHATTERMATE default model)
# Providers that ignore the org's base_url (e.g. the REPLYPIER default model)
# still land on the fake LLM through the openai SDK's env fallback.
OPENAI_BASE_URL=http://127.0.0.1:14010/v1
OPENAI_API_KEY=e2e-fake-key
+3 -3
View File
@@ -1,4 +1,4 @@
# ChatterMate end-to-end tests
# ReplyPier end-to-end tests
Playwright tests that drive the real dashboard, the real widget and the real backend.
@@ -43,7 +43,7 @@ npx playwright test # reuses the running stack
```
**Safety:**
- `stack.sh` only ever manages `chattermate_e2e` and its template `chattermate_e2e_tpl`. It refuses any database name that does not end in `_e2e`.
- `stack.sh` only ever manages `replypier_e2e` and its template `replypier_e2e_tpl`. It refuses any database name that does not end in `_e2e`.
- The backend reads only `e2e/.env.e2e` (through `ENV_FILE`), never `backend/.env`.
- The OSS edition runs with `NO_ENTERPRISE=1`, and the dashboard is built without `src/modules/enterprise`, so it behaves like a community install even in a checkout that has the submodules.
@@ -168,4 +168,4 @@ A journey that should hold on every channel loops over `channels` (or `channelsW
- **Port already in use:** something else is on 18000, 15173, 14010, 14020, 14030, 13000, 11025 or 18025. Run `./scripts/stack.sh down`, or stop the other process.
- **The launcher never becomes clickable:** the suite runs with `reducedMotion: 'reduce'` because the launcher floats forever otherwise. Keep that option on any new browser context.
- **The customer site must be a real server:** Chrome blocks a page faked with `page.route` from calling `127.0.0.1` (Local Network Access).
- **The widget and loader are built from source on every run.** `stack.sh` builds the widget bundle and `chattermate.min.js` from the current `frontend/src/webclient` into `e2e/.cache`, and points the backend at that build with `WIDGET_ASSETS_DIR`. The committed production bundles in `backend/assets` and `frontend/public/webclient` are never rebuilt or modified.
- **The widget and loader are built from source on every run.** `stack.sh` builds the widget bundle and `replypier.min.js` from the current `frontend/src/webclient` into `e2e/.cache`, and points the backend at that build with `WIDGET_ASSETS_DIR`. The committed production bundles in `backend/assets` and `frontend/public/webclient` are never rebuilt or modified.
+1 -1
View File
@@ -21,7 +21,7 @@ One row per user journey. Every feature PR adds or updates its row and the spec
| Widget chat | Email-first agent (gate → chat, person recorded), Ask anything panel, lead capture with no email asked — each also with the first authorization lost | `specs/widget/chat-styles.spec.ts` | P1 | ✅ |
| Widget chat | Agent wraps up → customer rates the chat → rating stored against the agent | `specs/widget/rating.spec.ts` | P1 | ✅ |
| Widget chat | A second browser that types a customer's email gets its own empty conversation (the owner keeps theirs), can't rename them or make them a lead, and an address claimed before its owner signs in yields none of their chats | `specs/widget/conversation-ownership.spec.ts` | P1 | ✅ |
| Widget chat | Scoping keeps a visitor's own conversation: a second tab joins it; a visitor the site signed in finds it in another browser, and after `ChatterMate.identify()` | `specs/widget/conversation-ownership.spec.ts` | P1 | ✅ |
| Widget chat | Scoping keeps a visitor's own conversation: a second tab joins it; a visitor the site signed in finds it in another browser, and after `ReplyPier.identify()` | `specs/widget/conversation-ownership.spec.ts` | P1 | ✅ |
| Widget chat | Provider error, provider timeout and malformed reply each end in an apology; the next message works | `specs/widget/ai-failures.spec.ts` | P1 | ✅ |
| Human handover | AI hands over → contact form → agent takes over → two-way messages → the AI stays silent | `specs/handover/handover.spec.ts` | P1 | ✅ |
| Human handover | After hours, or nobody online: the chat waits in the team's queue with a reply that says so | `specs/handover/after-hours.org.spec.ts`, `nobody-online.spec.ts` | P1 | ✅ |
+2 -2
View File
@@ -1,10 +1,10 @@
{
"name": "chattermate-e2e",
"name": "replypier-e2e",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "chattermate-e2e",
"name": "replypier-e2e",
"devDependencies": {
"@axe-core/playwright": "4.13.0",
"@modelcontextprotocol/sdk": "1.30.1",
+2 -2
View File
@@ -1,8 +1,8 @@
{
"name": "chattermate-e2e",
"name": "replypier-e2e",
"private": true,
"type": "module",
"description": "End-to-end tests for ChatterMate (dashboard, widget, backend) — see AUTHORING.md",
"description": "End-to-end tests for ReplyPier (dashboard, widget, backend) — see AUTHORING.md",
"engines": {
"node": ">=22.18"
},
+8 -8
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Runs the ChatterMate e2e stack natively (no Docker): a throwaway Postgres DB,
# Runs the ReplyPier e2e stack natively (no Docker): a throwaway Postgres DB,
# the backend, a production build of the dashboard and the fake LLM — all on
# 127.0.0.1 ports that never collide with a dev stack on localhost.
#
@@ -39,7 +39,7 @@ CACHE_DIR="$E2E_DIR/.cache"
export FASTEMBED_CACHE_PATH="${FASTEMBED_CACHE_PATH:-$CACHE_DIR/fastembed}"
EDITION="${E2E_EDITION:-oss}"
DB_NAME="chattermate_e2e"
DB_NAME="replypier_e2e"
TPL_NAME="${DB_NAME}_tpl"
PG_URL="${E2E_PG_URL:-postgresql://${USER}@127.0.0.1:5432}"
PG_URL="${PG_URL%/}"
@@ -172,7 +172,7 @@ sync_frontend_copy() {
mkdir -p "$dest"
# The loader bundle is generated inside the copy (build_loader), so keep rsync off it.
local excludes=(--exclude node_modules --exclude 'dist*' --exclude '.env*' --exclude e2e --exclude coverage
--exclude public/webclient/chattermate.min.js)
--exclude public/webclient/replypier.min.js)
if [[ "$EDITION" == oss ]]; then excludes+=(--exclude src/modules/enterprise); fi
rsync -a --delete -i "${excludes[@]}" "$REPO_DIR/frontend/" "$dest/"
ln -sfn "$NODE_MODULES" "$dest/node_modules"
@@ -192,15 +192,15 @@ build_widget() {
find "$out" -mindepth 1 -maxdepth 1 ! -name widget.js ! -name widget.css -exec rm -rf {} +
}
# The embed loader (chattermate.min.js), built from source into the dashboard copy's
# The embed loader (replypier.min.js), built from source into the dashboard copy's
# public/ so vite ships it in the build — what build-webclient-*.ts does.
build_loader() {
local src="$1"
(cd "$src" && node_modules/.bin/esbuild src/webclient/chattermate.js --bundle --minify --format=iife \
(cd "$src" && node_modules/.bin/esbuild src/webclient/replypier.js --bundle --minify --format=iife \
--target=es2015 --loader:.svg=text --log-level=warning \
--define:process.env.NODE_ENV='"production"' \
--define:__CHATTERMATE_API_URL__="\"http://127.0.0.1:$BACKEND_PORT/api/v1\"" \
--outfile=public/webclient/chattermate.min.js) >&2
--define:__REPLYPIER_API_URL__="\"http://127.0.0.1:$BACKEND_PORT/api/v1\"" \
--outfile=public/webclient/replypier.min.js) >&2
}
# One copy of frontend/ feeds all three builds: loader, widget, dashboard.
@@ -250,7 +250,7 @@ run_customer_site() {
cd "$E2E_DIR"
exec node services/customer-site/server.ts --port "$CUSTOMER_SITE_PORT" \
--api "http://127.0.0.1:$BACKEND_PORT/api/v1" \
--loader "http://127.0.0.1:$FRONTEND_PORT/webclient/chattermate.min.js"
--loader "http://127.0.0.1:$FRONTEND_PORT/webclient/replypier.min.js"
}
# SMTP capture (Mailpit): everything the backend emails — email-channel replies,
+2 -2
View File
@@ -13,8 +13,8 @@ set -euo pipefail
E2E_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REPO_DIR="$(cd "$E2E_DIR/.." && pwd)"
PG_URL="${E2E_PG_URL:-postgresql://${USER}@127.0.0.1:5432}"; PG_URL="${PG_URL%/}"
DB_NAME="chattermate_upgrade_e2e"
FRESH_DB="chattermate_fresh_e2e"
DB_NAME="replypier_upgrade_e2e"
FRESH_DB="replypier_fresh_e2e"
BASE_REF="${1:-$(git -C "$REPO_DIR" describe --tags --abbrev=0 --match 'v*')}"
if [[ -z "${E2E_PYTHON:-}" ]]; then
if [[ -x "$REPO_DIR/backend/venv/bin/python" ]]; then E2E_PYTHON="$REPO_DIR/backend/venv/bin/python"; else E2E_PYTHON="python3"; fi
+11 -11
View File
@@ -1,11 +1,11 @@
// A pretend customer website with the ChatterMate widget embedded the way the
// A pretend customer website with the ReplyPier widget embedded the way the
// dashboard's install snippet does it. Everything about the embed comes from the
// query string:
//
// http://127.0.0.1:13000/?widget=<id>
// &config=<json> → window.chattermateConfig (launcher, position, trigger, …)
// &token=<jwt> → window.chattermateToken (token-authenticated widgets)
// &trigger=1 → adds a page button with data-chattermate-open
// &config=<json> → window.replypierConfig (launcher, position, trigger, …)
// &token=<jwt> → window.replypierToken (token-authenticated widgets)
// &trigger=1 → adds a page button with data-replypier-open
// &snippet=iframe → the dashboard's iframe snippet instead of the script
//
// It has to be a real server on 127.0.0.1: Chrome's Local Network Access check
@@ -23,7 +23,7 @@ function arg(name: string, fallback: string): string {
const port = Number(arg('port', '13000'))
const apiUrl = arg('api', 'http://127.0.0.1:18000/api/v1')
const loaderUrl = arg('loader', 'http://127.0.0.1:15173/webclient/chattermate.min.js')
const loaderUrl = arg('loader', 'http://127.0.0.1:15173/webclient/replypier.min.js')
interface Embed {
widgetId: string
@@ -36,7 +36,7 @@ interface Embed {
/** The dashboard's iframe snippet (AgentWidgetTab), classed so the page object finds it. */
function iframeSnippet(widgetId: string): string {
const src = `${apiUrl}/widgets/${encodeURIComponent(widgetId)}/data`
return `<iframe class="chattermate-iframe" src="${src}" width="100%" height="600" frameborder="0" title="AI Assistant" allow="clipboard-write"></iframe>`
return `<iframe class="replypier-iframe" src="${src}" width="100%" height="600" frameborder="0" title="AI Assistant" allow="clipboard-write"></iframe>`
}
function page(embed: Embed): string {
@@ -51,18 +51,18 @@ function page(embed: Embed): string {
</html>`
}
const globals = [
`window.chattermateId = ${JSON.stringify(embed.widgetId)};`,
`window.chattermateBaseUrl = ${JSON.stringify(apiUrl)};`,
`window.replypierId = ${JSON.stringify(embed.widgetId)};`,
`window.replypierBaseUrl = ${JSON.stringify(apiUrl)};`,
// config arrives as JSON text; parse it here so a malformed value fails the request, not the page.
embed.config ? `window.chattermateConfig = ${JSON.stringify(JSON.parse(embed.config))};` : '',
embed.token ? `window.chattermateToken = ${JSON.stringify(embed.token)};` : '',
embed.config ? `window.replypierConfig = ${JSON.stringify(JSON.parse(embed.config))};` : '',
embed.token ? `window.replypierToken = ${JSON.stringify(embed.token)};` : '',
].filter(Boolean).join('\n ')
return `<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Acme Store</title></head>
<body>
<h1>Acme Store</h1>
${embed.trigger ? '<button type="button" data-chattermate-open>Chat with us</button>' : ''}
${embed.trigger ? '<button type="button" data-replypier-open>Chat with us</button>' : ''}
<script>
${globals}
</script>
+4 -4
View File
@@ -23,7 +23,7 @@
// Meta tokens `…:app=<id>.<secret>:…` were issued by the customer's own Meta
// app: GET /app answers that id and checks
// appsecret_proof against that secret (any
// other token is ChatterMate's app)
// other token is ReplyPier's app)
// Meta POST /<number> {webhook_configuration} verifies the override callback the way
// Meta does — a GET with hub.verify_token
// that must echo hub.challenge — before
@@ -188,13 +188,13 @@ function graphError(message: string, code = 100): Answer {
return { status: 400, body: { error: { message, type: 'OAuthException', code, fbtrace_id: 'e2e' } } }
}
/** ChatterMate's own Meta app; its secret mirrors META_APP_SECRET in .env.e2e. */
const CHATTERMATE_APP = { id: 'e2e-chattermate-app', secret: 'e2e-meta-app-secret' }
/** ReplyPier's own Meta app; its secret mirrors META_APP_SECRET in .env.e2e. */
const REPLYPIER_APP = { id: 'e2e-replypier-app', secret: 'e2e-meta-app-secret' }
/** The Meta app that issued a token. */
function appOf(token: string): { id: string; secret: string } {
const match = /:app=([^.:]+)\.([^:]+)/.exec(token)
return match ? { id: match[1], secret: match[2] } : CHATTERMATE_APP
return match ? { id: match[1], secret: match[2] } : REPLYPIER_APP
}
/** Meta checks an override callback before taking it: the GET must echo the challenge. */
+2 -2
View File
@@ -5,7 +5,7 @@
// replies keyed by a `[[e2e:<id>]]` marker in its agent's instructions or by the
// agent's unique name — so parallel tests in one org never see each other's script.
//
// It speaks only what ChatterMate uses: non-streaming /v1/chat/completions, with or
// It speaks only what ReplyPier uses: non-streaming /v1/chat/completions, with or
// without a strict json_schema response_format (ChatAgent's ChatResponse), and tool
// calls. Anything else fails loudly instead of returning something plausible.
//
@@ -245,7 +245,7 @@ async function route(req: IncomingMessage, res: ServerResponse) {
throw new HttpError(401, 'Incorrect API key provided.')
}
const body = (await readJson(req)) as ChatRequest
if (body.stream) throw new HttpError(400, 'the e2e fake LLM does not stream — ChatterMate never asks it to; if it does now, extend services/fake-llm')
if (body.stream) throw new HttpError(400, 'the e2e fake LLM does not stream — ReplyPier never asks it to; if it does now, extend services/fake-llm')
if (!Array.isArray(body.messages)) throw new HttpError(400, 'messages[] is required')
return send(res, 200, await answer(body, req.headers.authorization ?? ''))
}
+2 -2
View File
@@ -11,14 +11,14 @@ test.describe('accessibility', () => {
await expectNoNewA11yViolations(anonymousPage, 'login')
})
// The dashboard is dark unless the user picks light (stored as cm-theme); both are checked.
// The dashboard is dark unless the user picks light (stored as rp-theme); both are checked.
for (const scheme of ['dark', 'light'] as const) {
const suffix = scheme === 'dark' ? '' : '-light'
test(`dashboard screens, ${scheme} theme @a11y`, async ({ page, agent, agentsPage, aiConfigPage, ticketsPage, peoplePage }) => {
// An agent of its own, so the agents screen is the list (with a card to check), not first-run setup.
expect(agent.id).toBeTruthy()
await page.addInitScript((theme) => localStorage.setItem('cm-theme', theme), scheme)
await page.addInitScript((theme) => localStorage.setItem('rp-theme', theme), scheme)
await new InboxPage(page).goto()
expect(await page.evaluate(() => document.documentElement.dataset.theme)).toBe(scheme)
await expectNoNewA11yViolations(page, `inbox${suffix}`)
+1 -1
View File
@@ -13,7 +13,7 @@ import {
// by the dashboard's unit tests.
test.describe('a number or Page from the customer\'s own Meta app', () => {
test('a WhatsApp number receives messages: its webhooks are pointed at ChatterMate on connect @channels', async ({ agent, llm, adminApi, request }) => {
test('a WhatsApp number receives messages: its webhooks are pointed at ReplyPier on connect @channels', async ({ agent, llm, adminApi, request }) => {
await llm.script(agent, (s) => s
.when(/opening hours/i).reply('We are open 9am to 5pm, Monday to Friday.')
.otherwise().reply('Happy to help!'))
+2 -2
View File
@@ -49,9 +49,9 @@ test.describe('Pipedrive', () => {
const personId = (created.response as { data: { id: number } }).data.id
const [lead] = (await externals.calls('pipedrive', 'leads', { contains: `"person_id":${personId}` })).filter((c) => c.method === 'POST')
expect(lead.body).toMatchObject({ title: `${name} — ChatterMate lead`, person_id: personId })
expect(lead.body).toMatchObject({ title: `${name} — ReplyPier lead`, person_id: personId })
const leadId = (lead.response as { data: { id: string } }).data.id
const [note] = await externals.calls('pipedrive', 'notes', { contains: leadId })
expect(note.body).toMatchObject({ lead_id: leadId, content: expect.stringContaining('Lead captured by ChatterMate') })
expect(note.body).toMatchObject({ lead_id: leadId, content: expect.stringContaining('Lead captured by ReplyPier') })
})
})
+1 -1
View File
@@ -6,7 +6,7 @@ import { test, expect, env, AI_REPLY } from '@e2e'
const KB = 1024
const BUDGETS = [
{ file: 'embed loader (chattermate.min.js)', url: `${env.frontendUrl}/webclient/chattermate.min.js`, maxBytes: 60 * KB },
{ file: 'embed loader (replypier.min.js)', url: `${env.frontendUrl}/webclient/replypier.min.js`, maxBytes: 60 * KB },
{ file: 'widget script (widget.js)', url: `${env.backendUrl}/assets/widget.js`, maxBytes: 500 * KB },
{ file: 'widget styles (widget.css)', url: `${env.backendUrl}/assets/widget.css`, maxBytes: 135 * KB },
]
+1 -1
View File
@@ -1,7 +1,7 @@
import { test, expect } from '@e2e'
test.describe('widget JS API', () => {
test('the host page opens and closes the chat with window.ChatterMate @widget', async ({ widget }) => {
test('the host page opens and closes the chat with window.ReplyPier @widget', async ({ widget }) => {
await widget.embed()
await widget.api('open')
+3 -3
View File
@@ -27,12 +27,12 @@ export async function currentAiConfig(api: ApiClient): Promise<AiConfig> {
/**
* Puts the org back on the AI it had before a test changed it: the fake LLM, or
* (enterprise) the ChatterMate AI its signup created, which reaches the fake too.
* (enterprise) the ReplyPier AI its signup created, which reaches the fake too.
*/
export async function restoreAi(api: ApiClient, before: AiConfig): Promise<void> {
if (before.model_type.toUpperCase() === 'CHATTERMATE') {
if (before.model_type.toUpperCase() === 'REPLYPIER') {
// As the dashboard's onboarding sends it: the backend injects the key.
await api.post('/ai/setup', { data: { model_type: 'CHATTERMATE', model_name: 'chattermate', api_key: '' } })
await api.post('/ai/setup', { data: { model_type: 'REPLYPIER', model_name: 'replypier', api_key: '' } })
} else {
await useFakeLlm(api)
}
+1 -1
View File
@@ -4914,7 +4914,7 @@ export interface components {
* AIModelType
* @enum {string}
*/
AIModelType: "OPENAI" | "ANTHROPIC" | "DEEPSEEK" | "GOOGLE" | "GOOGLEVERTEX" | "GROQ" | "MISTRAL" | "HUGGINGFACE" | "OLLAMA" | "XAI" | "OPENAI_COMPATIBLE" | "CHATTERMATE";
AIModelType: "OPENAI" | "ANTHROPIC" | "DEEPSEEK" | "GOOGLE" | "GOOGLEVERTEX" | "GROQ" | "MISTRAL" | "HUGGINGFACE" | "OLLAMA" | "XAI" | "OPENAI_COMPATIBLE" | "REPLYPIER";
/** AISetupResponse */
AISetupResponse: {
/** Message */
+11 -11
View File
@@ -4,11 +4,11 @@ import { unique } from '../support/ids'
import { step } from '../support/step'
export interface EmbedOptions {
/** window.chattermateConfig — launcher, displayMode, position, trigger, … */
/** window.replypierConfig — launcher, displayMode, position, trigger, … */
config?: Record<string, unknown>
/** window.chattermateToken, for agents that require token auth. */
/** window.replypierToken, for agents that require token auth. */
token?: string
/** Put a `[data-chattermate-open]` button on the page. */
/** Put a `[data-replypier-open]` button on the page. */
withTriggerButton?: boolean
/** The dashboard's iframe snippet instead of the script: the chat is already open. */
snippet?: 'script' | 'iframe'
@@ -20,7 +20,7 @@ type JsApiMethod = 'open' | 'close' | 'toggle' | 'isOpen' | 'showLauncher' | 'hi
* The customer's view: a pretend website with the widget embedded exactly the way the
* dashboard's install snippet does it, and the chat inside the widget's iframe.
*
* Why CSS here: the loader's launcher (#chattermate-button) and the widget's message
* Why CSS here: the loader's launcher (#replypier-button) and the widget's message
* bubbles have no accessible role or name yet. Keeping those selectors in this one
* file means adding aria-labels later is a one-file change.
*/
@@ -37,7 +37,7 @@ export class ChatWidget {
this.widgetId = widgetId
const id = unique('customer')
this.customer = customer ?? { name: `Customer ${id}`, email: `${id}@example.com` }
this.frame = page.frameLocator('iframe.chattermate-iframe')
this.frame = page.frameLocator('iframe.replypier-iframe')
}
/**
@@ -161,11 +161,11 @@ export class ChatWidget {
return new ChatWidget(await this.page.context().newPage(), this.widgetId, this.customer)
}
/** Calls the public `window.ChatterMate` API the host page uses. */
/** Calls the public `window.ReplyPier` API the host page uses. */
async api(method: JsApiMethod, ...args: unknown[]): Promise<unknown> {
await this.page.waitForFunction(() => 'ChatterMate' in window)
await this.page.waitForFunction(() => 'ReplyPier' in window)
return this.page.evaluate(
({ method, args }) => (window as unknown as { ChatterMate: Record<string, (...a: unknown[]) => unknown> }).ChatterMate[method](...args),
({ method, args }) => (window as unknown as { ReplyPier: Record<string, (...a: unknown[]) => unknown> }).ReplyPier[method](...args),
{ method, args },
)
}
@@ -176,12 +176,12 @@ export class ChatWidget {
}
get launcher(): Locator {
return this.page.locator('#chattermate-button')
return this.page.locator('#replypier-button')
}
/** The chat panel; it carries `.active` while open. */
get panel(): Locator {
return this.page.locator('#chattermate-container')
return this.page.locator('#replypier-container')
}
/** Shown by the loader instead of a chat when the widget refuses to load (e.g. no token). */
@@ -233,7 +233,7 @@ export class ChatWidget {
/** The agent's welcome message, shown when the chat opens. */
get welcomeMessage(): Locator {
return this.frame.locator('.cm-welcome-block .message-bubble')
return this.frame.locator('.rp-welcome-block .message-bubble')
}
get contactForm(): Locator {
+1 -1
View File
@@ -25,7 +25,7 @@ export const env = {
/** The pretend customer website the widget is embedded on (services/customer-site). */
customerSiteUrl: 'http://127.0.0.1:13000',
/** The throwaway database stack.sh manages (same E2E_PG_URL convention). */
databaseUrl: `${(process.env.E2E_PG_URL ?? `postgresql://${process.env.USER}@127.0.0.1:5432`).replace(/\/$/, '')}/chattermate_e2e`,
databaseUrl: `${(process.env.E2E_PG_URL ?? `postgresql://${process.env.USER}@127.0.0.1:5432`).replace(/\/$/, '')}/replypier_e2e`,
/** The private enterprise suite, present only where the enterprise_frontend submodule is checked out. */
enterpriseSpecDir: fileURLToPath(new URL('../../frontend/src/modules/enterprise/e2e/', import.meta.url)),
/** stack.sh, for `stack.sh python …` one-off jobs against the running stack. */
+1 -1
View File
@@ -16,7 +16,7 @@ export interface PreparedOrg {
adminStorageState: string
/**
* OSS points the org at the fake LLM itself. Enterprise orgs keep the
* ChatterMate AI their signup created, which reaches the fake through
* ReplyPier AI their signup created, which reaches the fake through
* OPENAI_BASE_URL.
*/
configureAi: boolean
+1 -1
View File
@@ -22,7 +22,7 @@ export class AiConfigPage {
async useOpenAiCompatible(settings: ModelSettings): Promise<void> {
await step(`Save an OpenAI-compatible model (${settings.model})`, async () => {
// With the enterprise module the page can open on the "ChatterMate AI" tab.
// With the enterprise module the page can open on the "ReplyPier AI" tab.
await this.page.getByRole('tab', { name: /Bring Your Own Model/ }).click()
await this.page.getByRole('radio', { name: /OpenAI-compatible/ }).click()
await this.page.getByLabel('Base URL').fill(settings.baseUrl)
+1 -1
View File
@@ -19,7 +19,7 @@ export class SetupPage {
async goto(): Promise<void> {
await this.page.goto('/setup')
await expect(this.page.getByRole('heading', { name: 'Welcome to ChatterMate' })).toBeVisible()
await expect(this.page.getByRole('heading', { name: 'Welcome to ReplyPier' })).toBeVisible()
}
async createOrganization(details: OrgDetails): Promise<void> {