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:
1 parent
ff0a09f0e5
commit
20fef2b9f3
1156 files changed
+7267
-7148
No files matched your search
+2
-2
@@ -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
@@ -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
@@ -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 | ✅ |
|
||||
|
||||
Generated
+2
-2
@@ -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
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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 ?? ''))
|
||||
}
|
||||
|
||||
@@ -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}`)
|
||||
|
||||
@@ -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!'))
|
||||
|
||||
@@ -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') })
|
||||
})
|
||||
})
|
||||
@@ -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,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
@@ -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)
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -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 */
|
||||
|
||||
@@ -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
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in new issue
Block a user