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 01:02:17 +08:00
1 parent 6a4127ed24
commit 3017753b88
15 files changed
+4073 -3954

No files matched your search

+1 -1
View File
@@ -31,7 +31,7 @@ uploads/
!/wordpress/replypier-chat.zip !/wordpress/replypier-chat.zip
# Firebase service account key (contains sensitive credentials) # Firebase service account key (contains sensitive credentials)
replypier-13e0a-firebase-adminsdk-7dqo6-57f3de88cb.json *-firebase-adminsdk-*.json
test.db test.db
*.db *.db
+4 -5
View File
@@ -6,11 +6,10 @@ deploy-time pieces that live **outside** the repo: DNS, TLS and the host
nginx on the production VPS. `frontend/nginx.conf` and the docker-compose nginx on the production VPS. `frontend/nginx.conf` and the docker-compose
files need **no changes**. files need **no changes**.
> **As deployed on the ReplyPier cloud VPS (2026-07-14):** the base domain is > **As deployed on the ReplyPier VPS (2026-07-14):** the base domain is
> **`help.replypier.site`** (orgs serve at `{slug}.help.replypier.site`), not > **`help.replypier.site`** (orgs serve at `{slug}.help.replypier.site`). DNS is
> the `help.replypier.site` this doc originally planned — that domain was never > **Route53**, so the wildcard cert is issued and auto-renewed with the
> registered, and the org only owns `replypier.site`. DNS is **Route53**, so the > `dns-route53` plugin.
> wildcard cert is issued and auto-renewed with the `dns-route53` plugin.
> Placeholders (`<VPS_IPV4>`, `<ZONE_ID>`, …) stand in for the live values; the > Placeholders (`<VPS_IPV4>`, `<ZONE_ID>`, …) stand in for the live values; the
> real ones live on the VPS and in ops notes, not in this public repo. > real ones live on the VPS and in ops notes, not in this public repo.
+1 -1
View File
@@ -70,7 +70,7 @@ Python 后端、知识库 worker、前端预览服务在宿主机运行;Postgr
- Widget 与演示配置:[test-environment.json](deploy/local/.state/test-environment.json)。 - Widget 与演示配置:[test-environment.json](deploy/local/.state/test-environment.json)。
- 模型配置:DeepSeek / `deepseek-flash`;API Key 由应用加密保存在数据库中。 - 模型配置:DeepSeek / `deepseek-flash`;API Key 由应用加密保存在数据库中。
启动脚本使用上述 context、项目名和卷。不要随意改名、删除数据卷或关闭承载数据库的 Colima 虚拟机。 启动脚本使用上述 context、项目名和卷。不要随意改动这些名称、删除数据卷或关闭承载数据库的 Colima 虚拟机。
恢复数据时须同时保留数据库和 `backend/.env` 中的加密配置,否则加密保存的模型配置及消息可能无法读取。不要覆盖密钥、重置管理员,或将私有环境文件提交和对外分享。 恢复数据时须同时保留数据库和 `backend/.env` 中的加密配置,否则加密保存的模型配置及消息可能无法读取。不要覆盖密钥、重置管理员,或将私有环境文件提交和对外分享。
+5 -4
View File
@@ -1,8 +1,9 @@
ReplyPier ReplyPier
Copyright 2024-2026 ReplyPier Copyright 2024-2026 ReplyPier
This product includes software developed by ReplyPier This product is the ReplyPier AI customer-support platform: an AI agent answers
(https://replypier.site). first from your own knowledge base, a human takes over in the same conversation,
and the visitor can rate the service at the end.
Licensed under the Apache License, Version 2.0. See the LICENSE file for Licensed under the Apache License, Version 2.0. See the LICENSE file for the
the full license text. full license text. Contribution terms are in CONTRIBUTING.md.
@@ -0,0 +1,52 @@
"""
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
"""Add the ReplyPier managed model to the AI model enum
Revision ID: replypier_ai_model_001
Revises: widget_key_lookup_001
Create Date: 2026-10-26 00:00:00.000000
``AIModelType`` carries a managed-provider member for installs that bill model
usage through ReplyPier instead of the customer's own key. The enum type on an
existing database does not have that value yet, so add it; a database created
from the current head already contains it and this is then a no-op.
"""
from typing import Sequence, Union
from alembic import op
# revision identifiers, used by Alembic.
revision: str = 'replypier_ai_model_001'
down_revision: Union[str, None] = 'widget_key_lookup_001'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
MANAGED_VALUE = 'REPLYPIER'
def upgrade() -> None:
# ADD VALUE cannot be used in the same transaction that references the new
# label, so it runs in its own autocommit block.
with op.get_context().autocommit_block():
op.execute(
f"ALTER TYPE aimodeltype ADD VALUE IF NOT EXISTS '{MANAGED_VALUE}'"
)
def downgrade() -> None:
# PostgreSQL cannot drop an enum value; nothing to undo.
pass
+1 -1
View File
@@ -1 +1 @@
.new-chat-confirm[data-v-6c78f353]{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:10px 14px;border-bottom:1px solid var(--rp-hairline, rgba(0, 0, 0, .07));background:var(--rp-agent-bg, rgba(0, 0, 0, .03));font-family:var(--rp-body-font, inherit)}.new-chat-confirm__question[data-v-6c78f353]{margin:0;font-size:12.5px;line-height:1.35;color:var(--rp-text, inherit)}.new-chat-confirm__actions[data-v-6c78f353]{display:flex;gap:8px;margin-left:auto}.new-chat-confirm__button[data-v-6c78f353]{padding:5px 11px;border-radius:var(--rp-field-radius, 8px);border:1px solid var(--rp-border, rgba(0, 0, 0, .12));background:transparent;color:var(--rp-text, inherit);font-family:inherit;font-size:12px;font-weight:500;cursor:pointer;transition:opacity .15s ease}.new-chat-confirm__button--primary[data-v-6c78f353]{border-color:transparent;background:var(--rp-accent, #f34611);color:var(--rp-on-accent, #ffffff)}.new-chat-confirm__button[data-v-6c78f353]:disabled{opacity:.5;cursor:default}.new-chat-confirm__button[data-v-6c78f353]:not(:disabled):hover{opacity:.85}@media (pointer: coarse){.new-chat-confirm[data-v-6c78f353]{padding:10px 12px}.new-chat-confirm__button[data-v-6c78f353]{min-height:40px;padding:8px 14px}}@media (prefers-reduced-motion: reduce){.new-chat-confirm__button[data-v-6c78f353]{transition:none}}.askai[data-v-edea9534]{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--rp-card);color:var(--rp-text);font-family:var(--rp-body-font, inherit)}.askai__bar[data-v-edea9534]{display:flex;align-items:center;gap:10px;padding:14px 14px 14px 16px;border-bottom:1px solid var(--rp-border);flex-shrink:0}.askai__bar-icon[data-v-edea9534]{color:var(--rp-accent);flex-shrink:0}.askai__input[data-v-edea9534]{flex:1;min-width:0;border:none;outline:none;background:transparent;color:var(--rp-text);font-size:16px;font-family:inherit;padding:2px 0}.askai__input[data-v-edea9534]::placeholder{color:var(--rp-muted)}.askai__input[data-v-edea9534]:disabled{opacity:.6}.askai__new[data-v-edea9534]{position:relative;display:inline-flex;align-items:center;justify-content:center;width:28px;height:26px;border:1px solid var(--rp-border);background:transparent;border-radius:6px;cursor:pointer;color:var(--rp-muted);font-family:inherit;flex-shrink:0;transition:color .15s ease,border-color .15s ease}.askai__new[data-v-edea9534]:hover:not(:disabled){color:var(--rp-text);border-color:var(--rp-accent)}.askai__new[data-v-edea9534]:disabled{opacity:.5;cursor:default}.askai__new--armed[data-v-edea9534]{color:var(--rp-text);border-color:var(--rp-accent)}.askai__close[data-v-edea9534]{border:1px solid var(--rp-border);background:transparent;border-radius:6px;padding:3px 7px;cursor:pointer;color:var(--rp-muted);flex-shrink:0;transition:color .15s ease,border-color .15s ease}.askai__close[data-v-edea9534]:hover{color:var(--rp-text);border-color:var(--rp-accent)}.askai__kbd[data-v-edea9534]{font-size:11px;font-weight:600;letter-spacing:.02em;font-family:inherit}.askai__body[data-v-edea9534]{flex:1;min-height:0;overflow-y:auto;scrollbar-gutter:stable;padding:18px 20px 22px}.askai__content[data-v-edea9534]{display:flow-root}.askai__intro[data-v-edea9534]{margin-bottom:18px}.askai__title[data-v-edea9534]{margin:0 0 4px;font-size:19px;font-weight:600;letter-spacing:-.01em}.askai__subtitle[data-v-edea9534]{margin:0;font-size:14px;line-height:1.5;color:var(--rp-muted)}.askai__label[data-v-edea9534]{display:block;margin:0 0 8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:var(--rp-muted)}.askai__suggestions[data-v-edea9534]{display:flex;flex-direction:column;gap:6px}.askai__suggestion[data-v-edea9534]{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;text-align:left;padding:11px 13px;border:1px solid var(--rp-border);border-radius:10px;background:var(--rp-agent-bg);color:var(--rp-text);font-size:14px;font-family:inherit;cursor:pointer;transition:border-color .15s ease,transform .15s ease}.askai__suggestion[data-v-edea9534]:hover:not(:disabled){border-color:var(--rp-accent);transform:translate(2px)}.askai__suggestion[data-v-edea9534]:disabled{opacity:.55;cursor:default}.askai__suggestion svg[data-v-edea9534]{color:var(--rp-muted);flex-shrink:0}.askai__turn+.askai__turn[data-v-edea9534]{margin-top:20px}.askai__question[data-v-edea9534]{margin:0 0 10px;font-size:16px;font-weight:600;line-height:1.4;letter-spacing:-.01em}.askai__answer[data-v-edea9534]{font-size:14.5px;line-height:1.65;color:var(--rp-text);overflow-wrap:break-word}.askai__answer--streaming[data-v-edea9534]>:last-child:after{content:"";display:inline-block;width:6px;height:1em;margin-left:2px;vertical-align:-.15em;background:var(--rp-accent, currentColor);animation:askai-blink-edea9534 1s steps(1) infinite}@keyframes askai-blink-edea9534{0%,50%{opacity:1}51%,to{opacity:0}}.askai__answer[data-v-edea9534] p{margin:0 0 10px}.askai__answer[data-v-edea9534] p:last-child{margiLine truncated img[data-v-a09f9aae]{display:inline-block;vertical-align:middle;flex-shrink:0;object-fit:contain}.new-chat-confirm[data-v-080bebfe]{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:10px 14px;border-bottom:1px solid var(--rp-hairline, rgba(0, 0, 0, .07));background:var(--rp-agent-bg, rgba(0, 0, 0, .03));font-family:var(--rp-body-font, inherit)}.new-chat-confirm__question[data-v-080bebfe]{margin:0;font-size:12.5px;line-height:1.35;color:var(--rp-text, inherit)}.new-chat-confirm__actions[data-v-080bebfe]{display:flex;gap:8px;margin-left:auto}.new-chat-confirm__button[data-v-080bebfe]{padding:5px 11px;border-radius:var(--rp-field-radius, 8px);border:1px solid var(--rp-border, rgba(0, 0, 0, .12));background:transparent;color:var(--rp-text, inherit);font-family:inherit;font-size:12px;font-weight:500;cursor:pointer;transition:opacity .15s ease}.new-chat-confirm__button--primary[data-v-080bebfe]{border-color:transparent;background:var(--rp-accent, #f34611);color:var(--rp-on-accent, #ffffff)}.new-chat-confirm__button[data-v-080bebfe]:disabled{opacity:.5;cursor:default}.new-chat-confirm__button[data-v-080bebfe]:not(:disabled):hover{opacity:.85}@media (pointer: coarse){.new-chat-confirm[data-v-080bebfe]{padding:10px 12px}.new-chat-confirm__button[data-v-080bebfe]{min-height:40px;padding:8px 14px}}@media (prefers-reduced-motion: reduce){.new-chat-confirm__button[data-v-080bebfe]{transition:none}}.askai[data-v-9c300a6d]{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--rp-card);color:var(--rp-text);font-family:var(--rp-body-font, inherit)}.askai__bar[data-v-9c300a6d]{display:flex;align-items:center;gap:10px;padding:14px 14px 14px 16px;border-bottom:1px solid var(--rp-border);flex-shrink:0}.askai__bar-icon[data-v-9c300a6d]{color:var(--rp-accent);flex-shrink:0}.askai__input[data-v-9c300a6d]{flex:1;min-width:0;border:none;outline:none;background:transparent;color:var(--rp-text);font-size:16px;font-family:inherit;padding:2px 0}.askai__input[data-v-9c300a6d]::placeholder{color:var(--rp-muted)}.askai__input[data-v-9c300a6d]:disabled{opacity:.6}.askai__new[data-v-9c300a6d]{position:relative;display:inline-flex;align-items:center;justify-content:center;width:28px;height:26px;border:1px solid var(--rp-border);background:transparent;border-radius:6px;cursor:pointer;color:var(--rp-muted);font-family:inherit;flex-shrink:0;transition:color .15s ease,border-color .15s ease}.askai__new[data-v-9c300a6d]:hover:not(:disabled){color:var(--rp-text);border-color:var(--rp-accent)}.askai__new[data-v-9c300a6d]:disabled{opacity:.5;cursor:default}.askai__new--armed[data-v-9c300a6d]{color:var(--rp-text);border-color:var(--rp-accent)}.askai__close[data-v-9c300a6d]{border:1px solid var(--rp-border);background:transparent;border-radius:6px;padding:3px 7px;cursor:pointer;color:var(--rp-muted);flex-shrink:0;transition:color .15s ease,border-color .15s ease}.askai__close[data-v-9c300a6d]:hover{color:var(--rp-text);border-color:var(--rp-accent)}.askai__kbd[data-v-9c300a6d]{font-size:11px;font-weight:600;letter-spacing:.02em;font-family:inherit}.askai__body[data-v-9c300a6d]{flex:1;min-height:0;overflow-y:auto;scrollbar-gutter:stable;padding:18px 20px 22px}.askai__content[data-v-9c300a6d]{display:flow-root}.askai__intro[data-v-9c300a6d]{margin-bottom:18px}.askai__title[data-v-9c300a6d]{margin:0 0 4px;font-size:19px;font-weight:600;letter-spacing:-.01em}.askai__subtitle[data-v-9c300a6d]{margin:0;font-size:14px;line-height:1.5;color:var(--rp-muted)}.askai__label[data-v-9c300a6d]{display:block;margin:0 0 8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:var(--rp-muted)}.askai__suggestions[data-v-9c300a6d]{display:flex;flex-direction:column;gap:6px}.askai__suggestion[data-v-9c300a6d]{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;text-align:left;padding:11px 13px;border:1px solid var(--rp-border);border-radius:10px;background:var(--rp-agent-bg);color:var(--rp-text);font-size:14px;font-family:inherit;cursor:pointer;transition:border-color .15s ease,transform .15s ease}.askai__suggestion[data-v-9c300a6d]:hover:not(:disabled){border-color:var(--rp-accent);transform:translate(2px)}.askai__suggestion[data-v-9c300a6d]:disabled{opacity:.55;cursor:default}.askai__suggestion svg[data-v-9c300a6d]{color:var(--rp-muted);flex-shrink:0}.askai__turn+.askai__turn[data-v-9c300a6d]{margin-top:20px}.askai__question[data-v-9c300a6d]{margin:0 0 10px;font-size:16px;font-weight:600;line-height:1.4;letter-spacing:-.01em}.askai__answer[data-v-9c300a6d]{font-size:14.5px;line-height:1.65;color:var(--rp-text);overflow-wrap:break-word}.askai__answer--streaming[data-v-9c300a6d]>:last-child:after{content:"";display:inline-block;width:6px;height:1em;margin-left:2px;vertical-align:-.15em;background:var(--rp-accent, currentColor);animation:askai-blink-9c300a6d 1s steps(1) infinite}@keyframes askai-blink-9c300a6d{0%,50%{opacity:1}51%,to{opacity:0}}.asLine truncated
+3828 -3763
View File
File diff suppressed because it is too large. Load diff
@@ -151,7 +151,7 @@ def test_domain_endpoints_flow(client, db, test_organization):
assert body["custom_domain"] == "help.customer.com" assert body["custom_domain"] == "help.customer.com"
assert body["domain_status"] == "pending" assert body["domain_status"] == "pending"
assert [r["type"] for r in body["records"]] == ["CNAME", "TXT"] assert [r["type"] for r in body["records"]] == ["CNAME", "TXT"]
assert body["records"][1]["host"] == "_replypier.customer.com" assert body["records"][1]["host"] == "_replypier.help.customer.com"
with patch.object(dv, "check_txt_record", return_value=True), \ with patch.object(dv, "check_txt_record", return_value=True), \
patch.object(dv, "check_cname_record", return_value=True), \ patch.object(dv, "check_cname_record", return_value=True), \
@@ -359,7 +359,7 @@ class TestRowScopeCaseInsensitivity:
) )
def test_predicate_folds_both_sides(self): def test_predicate_folds_both_sides(self):
result = self._ci("Arun@replypier.site") result = self._ci("Arun@ReplyPier.Site")
assert result.ok assert result.ok
assert "LOWER(customer_email) = '%s'" % OWNER in result.sql assert "LOWER(customer_email) = '%s'" % OWNER in result.sql
@@ -369,10 +369,10 @@ class TestRowScopeCaseInsensitivity:
assert "'x'' or ''1''=''1'" in result.sql.lower() assert "'x'' or ''1''=''1'" in result.sql.lower()
def test_exact_match_when_disabled(self): def test_exact_match_when_disabled(self):
result = scoped("SELECT id FROM orders", value="Arun@replypier.site") result = scoped("SELECT id FROM orders", value="Arun@ReplyPier.Site")
assert result.ok assert result.ok
assert "LOWER(" not in result.sql assert "LOWER(" not in result.sql
assert "customer_email = 'Arun@replypier.site'" in result.sql assert "customer_email = 'Arun@ReplyPier.Site'" in result.sql
def test_still_fails_closed(self): def test_still_fails_closed(self):
result = self._ci(value=None) result = self._ci(value=None)
@@ -41,7 +41,7 @@ class TestScrubOutbound:
assert scrub_outbound(text, (OWNER_EMAIL,)) == text assert scrub_outbound(text, (OWNER_EMAIL,)) == text
def test_own_email_matched_case_insensitively(self): def test_own_email_matched_case_insensitively(self):
text = "Your account Arun@replypier.site is fixed." text = "Your account Arun@ReplyPier.Site is fixed."
assert scrub_outbound(text, (OWNER_EMAIL,)) == text assert scrub_outbound(text, (OWNER_EMAIL,)) == text
def test_own_phone_survives_reformatting(self): def test_own_phone_survives_reformatting(self):
+1 -1
View File
@@ -34,7 +34,7 @@ from app.tools.ticket_toolkit import TicketTools, _clean_email, _clean_name
class TestCleanEmail: class TestCleanEmail:
def test_normalizes_and_lowercases(self): def test_normalizes_and_lowercases(self):
assert _clean_email(" Admin@replypier.site ") == "admin@replypier.site" assert _clean_email(" Admin@ReplyPier.Site ") == "admin@replypier.site"
def test_rejects_junk(self): def test_rejects_junk(self):
for bad in (None, "", "not-an-email", "no@domain", "@no-local.com", "a b@c.com"): for bad in (None, "", "not-an-email", "no@domain", "@no-local.com", "a b@c.com"):
+1 -1
View File
@@ -25,7 +25,7 @@
## Design constraints ## Design constraints
Use existing ink/porcelain foundation: paper #F4F1EB, ink #16171A, vermilion #B23A22, celadon #2E6A5C, brass #8A6326. Keep 16px brand marks simple and readable. No change to customer-uploaded images or unrelated UI. Visible product naming currently mixes ReplyPier and ReplyPier; do not infer a full rename from the asset request. Use existing ink/porcelain foundation: paper #F4F1EB, ink #16171A, vermilion #B23A22, celadon #2E6A5C, brass #8A6326. Keep 16px brand marks simple and readable. No change to customer-uploaded images or unrelated UI. Product naming is ReplyPier throughout: UI copy, code identifiers, package and image names, domains and docs all use the same word, so a mismatch anywhere is a bug worth fixing.
## Concepts ## Concepts
+17 -20
View File
@@ -3,30 +3,27 @@
// in a page self === window, so window.APP_CONFIG still works. // in a page self === window, so window.APP_CONFIG still works.
self.APP_CONFIG = { self.APP_CONFIG = {
// API URLs // API URLs
API_URL: "https://devapi.replypier.site/api/v1", API_URL: "http://localhost:8000/api/v1",
WS_URL: "wss://devapi.replypier.site", WS_URL: "ws://localhost:8000",
WIDGET_URL: "https://devapp.replypier.site", WIDGET_URL: "http://localhost:8000",
// Firebase Configuration // Firebase Configuration (fill in for your own project; empty disables it)
FIREBASE_API_KEY: "AIzaSyDTwDgANnBItYx39qm-6shq4cUws6QY4wI", FIREBASE_API_KEY: "",
FIREBASE_AUTH_DOMAIN: "replypier-13e0a.firebaseapp.com", FIREBASE_AUTH_DOMAIN: "",
FIREBASE_PROJECT_ID: "replypier-13e0a", FIREBASE_PROJECT_ID: "",
FIREBASE_MESSAGING_SENDER_ID: "680125690355", FIREBASE_MESSAGING_SENDER_ID: "",
FIREBASE_APP_ID: "1:680125690355:web:466f50999a6c91935ae286", FIREBASE_APP_ID: "",
FIREBASE_STORAGE_BUCKET: "replypier-13e0a.firebasestorage.app", FIREBASE_STORAGE_BUCKET: "",
FIREBASE_MEASUREMENT_ID: "G-7LGB58R2Q7", FIREBASE_MEASUREMENT_ID: "",
FIREBASE_VAPID_KEY: "BJKnggErez2qK3AxBhHyEV-EoS9lfcWO1GDXhRwUKIHbT_PH23vhY_8HRIz_KyuC-0gbuViSrtSMiFp7zgyYY4g", FIREBASE_VAPID_KEY: "",
// Razorpay Checkout // Razorpay Checkout
RAZORPAY_KEY_ID: "rzp_test_T92OR9gqPBht0N", RAZORPAY_KEY_ID: "",
GTM_ID: "",
// Google Fonts API // Google Fonts API
GOOGLE_FONTS_API_KEY: "AIzaSyCTQCSK_ybLEglabeusbyekQGEyvFr2C5o", GOOGLE_FONTS_API_KEY: "",
DEMO_WIDGET_ID: "a6ee36e9-b70e-4b12-8f94-cc5a5b147f26",
// Node Environment // Node Environment
NODE_ENV: "development", NODE_ENV: "development",
HOST: "0.0.0.0" HOST: "0.0.0.0"
}; };
+117 -117
View File
@@ -1,49 +1,49 @@
(()=>{var wt=(nt,it,e)=>new Promise((M,d)=>{var Y=x=>{try{z(e.next(x))}catch(B){d(B)}},G=x=>{try{z(e.throw(x))}catch(B){d(B)}},z=x=>x.done?M(x.value):Promise.resolve(x.value).then(Y,G);z((e=e.apply(nt,it)).next())});var _t=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 48" fill="none"><path d="M29 20 39 6 42 21Z" fill="#568B73"/><path d="M4 16 32 26 23 43H15Z" fill="#2E6A5C"/><path d="M35 26 60 16 49 43H27Z" fill="#8BB49C"/></svg> (()=>{var we=(ie,ne,t)=>new Promise((E,p)=>{var D=y=>{try{z(t.next(y))}catch(B){p(B)}},G=y=>{try{z(t.throw(y))}catch(B){p(B)}},z=y=>y.done?E(y.value):Promise.resolve(y.value).then(D,G);z((t=t.apply(ie,ne)).next())});var _e=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 48" fill="none"><path d="M29 20 39 6 42 21Z" fill="#568B73"/><path d="M4 16 32 26 23 43H15Z" fill="#2E6A5C"/><path d="M35 26 60 16 49 43H27Z" fill="#8BB49C"/></svg>
`;window.replypierId;window.replypierBaseUrl;window.ReplyPier;window.replypierConfig;(function(){function nt(t){return/^#[0-9A-F]{6}$/i.test(t)}function it(){return typeof window!="undefined"&&window.replypierBaseUrl?window.replypierBaseUrl:typeof window!="undefined"&&window.APP_CONFIG&&window.APP_CONFIG.API_URL?window.APP_CONFIG.API_URL:"https://api.replypier.site/api/v1"}let e={baseUrl:it(),containerId:"replypier-container",backdropId:"replypier-backdrop",buttonId:"replypier-button",chatBubbleColor:"#f34611",loadingContainerId:"replypier-loading",tokenKey:"rpid",tokenProvider:null,containerBottom:100,containerRight:20,containerWidth:400,containerHeight:560,launcherBottom:20,launcherRight:20,side:"right",displayMode:"floating",launcher:!0,sidebarWidth:420,searchBarWidth:320,searchPlaceholder:"\u6709\u95EE\u9898\u968F\u65F6\u95EE\u6211\u2026",zIndex:999999,trigger:null,hotkey:!0,surfaceIsPalette:null,chatStyle:null,chatInitiationMessages:[],initiationMessageId:"replypier-initiation",initiationShownKey:"rpim_shown",unreadCount:0,hasOpened:!1},M={};function d(t,n){e[t]=n,M[t]=!0}let Y=["floating","sidebar-left","sidebar-right","search-bar"],G=["ASK_ANYTHING","AURORA"],z=680,x=620,B=140,A={width:!1,height:!1},N=0;function j(){return e.surfaceIsPalette===!1?!1:(G.indexOf(e.chatStyle)!==-1||e.displayMode==="search-bar")&&e.displayMode!=="sidebar-left"&&e.displayMode!=="sidebar-right"}function q(){let t=document.getElementById(e.containerId);if(!t)return;if(!j()||!N){t.style.height="";return}let n=Math.min(at().height,Math.round(window.innerHeight*.76));t.style.height=Math.max(B,Math.min(n,N))+"px"}function at(){return{width:A.width?e.containerWidth:z,height:A.height?e.containerHeight:x}}function Lt(t){if(!t||typeof t!="object")return;let n=function(i,o){M[i]||(e[i]=o)};Y.indexOf(t.mode)!==-1&&n("displayMode",t.mode),(t.side==="left"||t.side==="right")&&n("side",t.side),typeof t.launcher=="boolean"&&n("launcher",t.launcher),m(t.width)&&(n("containerWidth",Math.max(280,t.width)),M.containerWidth||(A.width=!0)),m(t.height)&&(n("containerHeight",Math.max(400,t.height)),M.containerHeight||(A.height=!0)),m(t.sidebar_width)&&n("sidebarWidth",Math.max(280,t.sidebar_width)),typeof t.search_placeholder=="string"&&t.search_placeholder&&n("searchPlaceholder",t.search_placeholder.slice(0,80)),m(t.offset_bottom)&&n("launcherBottom",Math.max(0,t.offset_bottom)),m(t.offset_side)&&n("launcherRight",Math.max(0,t.offset_side)),m(t.z_index)&&n("zIndex",Math.max(1,Math.floor(t.z_index)))}function It(){let t=j(),n=t?at():{width:e.containerWidth,height:e.containerHeight};return{type:"WIDGET_DISPLAY",mode:t?"ask-ai":e.displayMode,width:n.width,height:n.height,hotkey:e.hotkey!==!1}}let c=null,R=[],V=!1,g=null,T=null,y=!1;function Z(t){c?t():y||R.push(t)}let X={},ot=!1;function _(t,n){let i=X[t];!i||!i.length||i.slice().forEach(function(o){try{o(n)}catch(h){}})}function zt(t){let n=/^#?([0-9a-f]{6})$/i.exec((t||"").trim());if(!n)return"#0B0C10";let i=parseInt(n[1],16);return(.299*(i>>16&255)+.587*(i>>8&255)+.114*(i&255))/255>.6?"#0B0C10":"#FFFFFF"}let rt=!1,E=null,st=[];function P(){if(rt)return;rt=!0,E&&(clearTimeout(E),E=null);let t=document.getElementById(e.buttonId);t&&t.classList.remove("replypier-pending"),st.splice(0).forEach(n=>{try{n()}catch(i){}})}function F(){let t=document.getElementById(e.buttonId);if(!t)return;let n=t.querySelector(".rp-badge");if(!n)return;let i=e.hasOpened?e.unreadCount:(e.chatInitiationMessages||[]).length;n.textContent=i>99?"99+":String(i),n.style.display=i>0&&!t.classList.contains("active")?"flex":"none"}function O(){let t=e.displayMode,n=t==="sidebar-left"||t==="sidebar-right",i=t==="sidebar-left"?"left":t==="sidebar-right"?"right":e.side,o=t==="search-bar"?48:64,h=G.indexOf(e.chatStyle)===-1,r=j(),l=at(),w=document.createElement("style");w.id="replypier-styles",w.textContent=` `;window.replypierId;window.replypierBaseUrl;window.ReplyPier;window.replypierConfig;(function(){function ie(e){return/^#[0-9A-F]{6}$/i.test(e)}function ne(){return typeof window!="undefined"&&window.replypierBaseUrl?window.replypierBaseUrl:typeof window!="undefined"&&window.APP_CONFIG&&window.APP_CONFIG.API_URL?window.APP_CONFIG.API_URL:"https://api.replypier.site/api/v1"}let t={baseUrl:ne(),containerId:"replypier-container",backdropId:"replypier-backdrop",buttonId:"replypier-button",chatBubbleColor:"#f34611",loadingContainerId:"replypier-loading",tokenKey:"rpid",tokenProvider:null,containerBottom:100,containerRight:20,containerWidth:400,containerHeight:560,launcherBottom:20,launcherRight:20,side:"right",displayMode:"floating",launcher:!0,sidebarWidth:420,searchBarWidth:320,searchPlaceholder:"\u6709\u95EE\u9898\u968F\u65F6\u95EE\u6211\u2026",zIndex:999999,trigger:null,hotkey:!0,surfaceIsPalette:null,chatStyle:null,chatInitiationMessages:[],initiationMessageId:"replypier-initiation",initiationShownKey:"rpim_shown",unreadCount:0,hasOpened:!1},E={};function p(e,i){t[e]=i,E[e]=!0}let D=["floating","sidebar-left","sidebar-right","search-bar"],G=["ASK_ANYTHING","AURORA"],z=680,y=620,B=140,A={width:!1,height:!1},P=0;function j(){return t.surfaceIsPalette===!1?!1:(G.indexOf(t.chatStyle)!==-1||t.displayMode==="search-bar")&&t.displayMode!=="sidebar-left"&&t.displayMode!=="sidebar-right"}function q(){let e=document.getElementById(t.containerId);if(!e)return;if(!j()||!P){e.style.height="";return}let i=Math.min(oe().height,Math.round(window.innerHeight*.76));e.style.height=Math.max(B,Math.min(i,P))+"px"}function oe(){return{width:A.width?t.containerWidth:z,height:A.height?t.containerHeight:y}}function Te(e){if(!e||typeof e!="object")return;let i=function(n,r){E[n]||(t[n]=r)};D.indexOf(e.mode)!==-1&&i("displayMode",e.mode),(e.side==="left"||e.side==="right")&&i("side",e.side),typeof e.launcher=="boolean"&&i("launcher",e.launcher),m(e.width)&&(i("containerWidth",Math.max(280,e.width)),E.containerWidth||(A.width=!0)),m(e.height)&&(i("containerHeight",Math.max(400,e.height)),E.containerHeight||(A.height=!0)),m(e.sidebar_width)&&i("sidebarWidth",Math.max(280,e.sidebar_width)),typeof e.search_placeholder=="string"&&e.search_placeholder&&i("searchPlaceholder",e.search_placeholder.slice(0,80)),m(e.offset_bottom)&&i("launcherBottom",Math.max(0,e.offset_bottom)),m(e.offset_side)&&i("launcherRight",Math.max(0,e.offset_side)),m(e.z_index)&&i("zIndex",Math.max(1,Math.floor(e.z_index)))}function Ie(){let e=j(),i=e?oe():{width:t.containerWidth,height:t.containerHeight};return{type:"WIDGET_DISPLAY",mode:e?"ask-ai":t.displayMode,width:i.width,height:i.height,hotkey:t.hotkey!==!1}}let l=null,R=[],V=!1,g=null,L=null,x=!1;function Z(e){l?e():x||R.push(e)}let X={},re=!1;function _(e,i){let n=X[e];!n||!n.length||n.slice().forEach(function(r){try{r(i)}catch(c){}})}function ze(e){let i=/^#?([0-9a-f]{6})$/i.exec((e||"").trim());if(!i)return"#0B0C10";let n=parseInt(i[1],16);return(.299*(n>>16&255)+.587*(n>>8&255)+.114*(n&255))/255>.6?"#0B0C10":"#FFFFFF"}let ae=!1,M=null,se=[];function N(){if(ae)return;ae=!0,M&&(clearTimeout(M),M=null);let e=document.getElementById(t.buttonId);e&&e.classList.remove("replypier-pending"),se.splice(0).forEach(i=>{try{i()}catch(n){}})}function F(){let e=document.getElementById(t.buttonId);if(!e)return;let i=e.querySelector(".rp-badge");if(!i)return;let n=t.hasOpened?t.unreadCount:(t.chatInitiationMessages||[]).length;i.textContent=n>99?"99+":String(n),i.style.display=n>0&&!e.classList.contains("active")?"flex":"none"}function O(){let e=t.displayMode,i=e==="sidebar-left"||e==="sidebar-right",n=e==="sidebar-left"?"left":e==="sidebar-right"?"right":t.side,r=e==="search-bar"?48:64,c=G.indexOf(t.chatStyle)===-1,a=j(),d=oe(),w=document.createElement("style");w.id="replypier-styles",w.textContent=`
/* ===== Rounded-square launcher (design comp) ===== */ /* ===== Rounded-square launcher (design comp) ===== */
#${e.buttonId} { #${t.buttonId} {
position: fixed; position: fixed;
bottom: ${e.launcherBottom}px; bottom: ${t.launcherBottom}px;
${i}: ${e.launcherRight}px; ${n}: ${t.launcherRight}px;
width: 64px; width: 64px;
height: 64px; height: 64px;
border-radius: 20px 20px 20px 6px; border-radius: 20px 20px 20px 6px;
background: ${e.chatBubbleColor}; background: ${t.chatBubbleColor};
color: ${zt(e.chatBubbleColor)}; color: ${ze(t.chatBubbleColor)};
box-shadow: 0 16px 40px -8px ${e.chatBubbleColor}cc, inset 0 0 0 1px rgba(255,255,255,0.08); box-shadow: 0 16px 40px -8px ${t.chatBubbleColor}cc, inset 0 0 0 1px rgba(255,255,255,0.08);
cursor: pointer; cursor: pointer;
z-index: ${e.zIndex}; z-index: ${t.zIndex};
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: transform 0.3s cubic-bezier(.34,1.3,.5,1), opacity 320ms ease; transition: transform 0.3s cubic-bezier(.34,1.3,.5,1), opacity 320ms ease;
animation: replypier-float 4s ease-in-out infinite; animation: replypier-float 4s ease-in-out infinite;
} }
#${e.buttonId}.active { animation: none; } #${t.buttonId}.active { animation: none; }
/* Stay fully invisible until the org's real brand color is known (or the /* Stay fully invisible until the org's real brand color is known (or the
widget errors out / times out) \u2014 avoids ever flashing the wrong color. */ widget errors out / times out) \u2014 avoids ever flashing the wrong color. */
#${e.buttonId}.replypier-pending { #${t.buttonId}.replypier-pending {
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
} }
#${e.buttonId}:hover { transform: scale(1.06); } #${t.buttonId}:hover { transform: scale(1.06); }
/* Expanding rings (closed state only) */ /* Expanding rings (closed state only) */
#${e.buttonId} .rp-ring { #${t.buttonId} .rp-ring {
position: absolute; position: absolute;
inset: 0; inset: 0;
border-radius: 20px 20px 20px 6px; border-radius: 20px 20px 20px 6px;
border: 1.5px solid ${e.chatBubbleColor}; border: 1.5px solid ${t.chatBubbleColor};
animation: replypier-ring 2.4s ease-out infinite; animation: replypier-ring 2.4s ease-out infinite;
pointer-events: none; pointer-events: none;
} }
#${e.buttonId} .rp-ring.r2 { animation-delay: 1.2s; } #${t.buttonId} .rp-ring.r2 { animation-delay: 1.2s; }
#${e.buttonId}.active .rp-ring { display: none; } #${t.buttonId}.active .rp-ring { display: none; }
/* Pulsing 3-dot mark (closed). Absolutely centered so it stays pinned to the /* Pulsing 3-dot mark (closed). Absolutely centered so it stays pinned to the
button's center regardless of the button's display mode (flex on desktop, button's center regardless of the button's display mode (flex on desktop,
block when .mobile-closed) or viewport. */ block when .mobile-closed) or viewport. */
#${e.buttonId} .rp-dots { #${t.buttonId} .rp-dots {
display: flex; display: flex;
gap: 5px; gap: 5px;
position: absolute; position: absolute;
@@ -51,19 +51,19 @@
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
#${e.buttonId} .rp-dot { #${t.buttonId} .rp-dot {
width: 7px; width: 7px;
height: 7px; height: 7px;
border-radius: 50%; border-radius: 50%;
background: currentColor; background: currentColor;
animation: replypier-pulse 1.5s ease-in-out infinite; animation: replypier-pulse 1.5s ease-in-out infinite;
} }
#${e.buttonId} .rp-dot:nth-child(2) { animation-delay: .18s; } #${t.buttonId} .rp-dot:nth-child(2) { animation-delay: .18s; }
#${e.buttonId} .rp-dot:nth-child(3) { animation-delay: .36s; } #${t.buttonId} .rp-dot:nth-child(3) { animation-delay: .36s; }
#${e.buttonId}.active .rp-dots { display: none; } #${t.buttonId}.active .rp-dots { display: none; }
/* Chevron (open) \u2014 absolutely centered, pinned to the button center. */ /* Chevron (open) \u2014 absolutely centered, pinned to the button center. */
#${e.buttonId} .rp-chevron { #${t.buttonId} .rp-chevron {
display: none; display: none;
position: absolute; position: absolute;
top: 50%; top: 50%;
@@ -74,10 +74,10 @@
line-height: 1; line-height: 1;
color: currentColor; color: currentColor;
} }
#${e.buttonId}.active .rp-chevron { display: block; } #${t.buttonId}.active .rp-chevron { display: block; }
/* Unread badge = number of nudges */ /* Unread badge = number of nudges */
#${e.buttonId} .rp-badge { #${t.buttonId} .rp-badge {
position: absolute; position: absolute;
top: -5px; top: -5px;
right: -5px; right: -5px;
@@ -94,11 +94,11 @@
box-shadow: 0 4px 12px rgba(0,0,0,0.25); box-shadow: 0 4px 12px rgba(0,0,0,0.25);
display: none; display: none;
} }
#${e.buttonId}.active .rp-badge { display: none; } #${t.buttonId}.active .rp-badge { display: none; }
#${e.buttonId}.loading .rp-dots, #${t.buttonId}.loading .rp-dots,
#${e.buttonId}.loading .rp-chevron { visibility: hidden; } #${t.buttonId}.loading .rp-chevron { visibility: hidden; }
#${e.buttonId}.loading:after { #${t.buttonId}.loading:after {
content: ''; content: '';
position: absolute; position: absolute;
width: 16px; width: 16px;
@@ -113,47 +113,47 @@
} }
/* Search-bar trigger content: hidden unless search-bar mode (below) shows it. */ /* Search-bar trigger content: hidden unless search-bar mode (below) shows it. */
#${e.buttonId} .rp-search { display: none; } #${t.buttonId} .rp-search { display: none; }
${t==="search-bar"?` ${e==="search-bar"?`
@media (min-width: 769px) { @media (min-width: 769px) {
/* ===== Search-bar mode: the launcher becomes a search-input-like bar. /* ===== Search-bar mode: the launcher becomes a search-input-like bar.
Same element, same click/toggle/badge logic. Mobile keeps the bubble. ===== */ Same element, same click/toggle/badge logic. Mobile keeps the bubble. ===== */
#${e.buttonId} { #${t.buttonId} {
width: ${e.searchBarWidth}px; width: ${t.searchBarWidth}px;
height: 48px; height: 48px;
border-radius: 24px; border-radius: 24px;
padding: 0 16px; padding: 0 16px;
background: #ffffff; background: #ffffff;
color: #374151; color: #374151;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14), inset 0 0 0 1.5px ${e.chatBubbleColor}55; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14), inset 0 0 0 1.5px ${t.chatBubbleColor}55;
animation: none; animation: none;
justify-content: flex-start; justify-content: flex-start;
box-sizing: border-box; box-sizing: border-box;
} }
#${e.buttonId}:hover { transform: scale(1.02); } #${t.buttonId}:hover { transform: scale(1.02); }
#${e.buttonId} .rp-ring, #${t.buttonId} .rp-ring,
#${e.buttonId} .rp-dots, #${t.buttonId} .rp-dots,
#${e.buttonId} .rp-chevron, #${t.buttonId} .rp-chevron,
#${e.buttonId}.active .rp-chevron { display: none; } #${t.buttonId}.active .rp-chevron { display: none; }
/* Dark spinner \u2014 the stock white one vanishes on the white bar. */ /* Dark spinner \u2014 the stock white one vanishes on the white bar. */
#${e.buttonId}.loading .rp-search { visibility: hidden; } #${t.buttonId}.loading .rp-search { visibility: hidden; }
#${e.buttonId}.loading:after { #${t.buttonId}.loading:after {
border-color: rgba(0, 0, 0, 0.15); border-color: rgba(0, 0, 0, 0.15);
border-top-color: ${e.chatBubbleColor}; border-top-color: ${t.chatBubbleColor};
} }
#${e.buttonId} .rp-search { #${t.buttonId} .rp-search {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
min-width: 0; min-width: 0;
width: 100%; width: 100%;
} }
#${e.buttonId} .rp-search svg { #${t.buttonId} .rp-search svg {
flex-shrink: 0; flex-shrink: 0;
color: ${e.chatBubbleColor}; color: ${t.chatBubbleColor};
} }
#${e.buttonId} .rp-search-placeholder { #${t.buttonId} .rp-search-placeholder {
font: 400 14px/1.2 -apple-system, BlinkMacSystemFont, system-ui, sans-serif; font: 400 14px/1.2 -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
color: #6b7280; color: #6b7280;
white-space: nowrap; white-space: nowrap;
@@ -169,30 +169,30 @@
@keyframes replypier-pulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } } @keyframes replypier-pulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
#${e.buttonId}, #${t.buttonId},
#${e.buttonId} .rp-ring, #${t.buttonId} .rp-ring,
#${e.buttonId} .rp-dot, #${t.buttonId} .rp-dot,
.initiation-orb { animation: none !important; } .initiation-orb { animation: none !important; }
/* Open and close in place: no slide, scale or fade for people who asked for less motion. */ /* Open and close in place: no slide, scale or fade for people who asked for less motion. */
#${e.buttonId}, #${t.buttonId},
#${e.containerId}, #${t.containerId},
#${e.backdropId} { transition: none !important; } #${t.backdropId} { transition: none !important; }
} }
#${e.containerId} { #${t.containerId} {
position: fixed; position: fixed;
/* The window sits above the launcher; when the launcher is moved (via /* The window sits above the launcher; when the launcher is moved (via
ReplyPier.init position / setPosition) the window shifts by the same ReplyPier.init position / setPosition) the window shifts by the same
delta so the two stay together. Mobile is full-screen (media query below) delta so the two stay together. Mobile is full-screen (media query below)
and is unaffected. */ and is unaffected. */
bottom: ${(e.containerBottom||100)+((e.launcherBottom||20)-20)}px; bottom: ${(t.containerBottom||100)+((t.launcherBottom||20)-20)}px;
${i}: ${(e.containerRight||20)+((e.launcherRight||20)-20)}px; ${n}: ${(t.containerRight||20)+((t.launcherRight||20)-20)}px;
width: ${e.containerWidth||384}px; width: ${t.containerWidth||384}px;
max-width: calc(100vw - 48px); max-width: calc(100vw - 48px);
height: ${e.containerHeight}px; height: ${t.containerHeight}px;
max-height: calc(100vh - 132px); max-height: calc(100vh - 132px);
background: transparent; background: transparent;
z-index: ${e.zIndex}; z-index: ${t.zIndex};
overflow: hidden; overflow: hidden;
/* Animated open/close with slide + fade */ /* Animated open/close with slide + fade */
opacity: 0; opacity: 0;
@@ -205,7 +205,7 @@
pointer-events: none; pointer-events: none;
} }
#${e.containerId}.active { #${t.containerId}.active {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
transform: translateY(0) scale(1); transform: translateY(0) scale(1);
@@ -216,111 +216,111 @@
/* Backdrop element: always in the DOM, so keep it out of the host page's flow /* Backdrop element: always in the DOM, so keep it out of the host page's flow
even when it is not in use \u2014 an unstyled div in <body> adds a phantom row to even when it is not in use \u2014 an unstyled div in <body> adds a phantom row to
a flex/grid body layout. */ a flex/grid body layout. */
#${e.backdropId} { #${t.backdropId} {
position: fixed; position: fixed;
inset: 0; inset: 0;
display: none; display: none;
} }
${r?` ${a?`
/* ===== Ask AI: centered command palette (\u2318K), the pattern people expect from /* ===== Ask AI: centered command palette (\u2318K), the pattern people expect from
an "Ask anything" surface \u2014 dimmed page, top-anchored so the eye lands on an "Ask anything" surface \u2014 dimmed page, top-anchored so the eye lands on
the input rather than the middle of the screen. ===== */ the input rather than the middle of the screen. ===== */
#${e.containerId} { #${t.containerId} {
top: 12vh; top: 12vh;
bottom: auto; bottom: auto;
left: 50%; left: 50%;
right: auto; right: auto;
width: min(${l.width}px, calc(100vw - 32px)); width: min(${d.width}px, calc(100vw - 32px));
max-width: none; max-width: none;
/* A cross-document iframe can't size its embedder, so this is a starting /* A cross-document iframe can't size its embedder, so this is a starting
height only: the palette measures its content and posts WIDGET_RESIZE, height only: the palette measures its content and posts WIDGET_RESIZE,
which sets an inline height (clamped to this ceiling). height:100% against which sets an inline height (clamped to this ceiling). height:100% against
an auto-height parent would collapse to the 150px replaced default. */ an auto-height parent would collapse to the 150px replaced default. */
height: min(${l.height}px, 76vh); height: min(${d.height}px, 76vh);
max-height: 76vh; max-height: 76vh;
transform: translate(-50%, -8px) scale(0.98); transform: translate(-50%, -8px) scale(0.98);
z-index: ${e.zIndex+1}; z-index: ${t.zIndex+1};
transition: opacity 360ms cubic-bezier(0.22, 1, 0.36, 1), transition: opacity 360ms cubic-bezier(0.22, 1, 0.36, 1),
transform 360ms cubic-bezier(0.22, 1, 0.36, 1), transform 360ms cubic-bezier(0.22, 1, 0.36, 1),
height 200ms cubic-bezier(0.22, 1, 0.36, 1), height 200ms cubic-bezier(0.22, 1, 0.36, 1),
visibility 0s linear 360ms; visibility 0s linear 360ms;
} }
#${e.containerId}.active { #${t.containerId}.active {
transform: translate(-50%, 0) scale(1); transform: translate(-50%, 0) scale(1);
transition: opacity 420ms cubic-bezier(0.22, 1, 0.36, 1), transition: opacity 420ms cubic-bezier(0.22, 1, 0.36, 1),
transform 420ms cubic-bezier(0.22, 1, 0.36, 1), transform 420ms cubic-bezier(0.22, 1, 0.36, 1),
height 200ms cubic-bezier(0.22, 1, 0.36, 1); height 200ms cubic-bezier(0.22, 1, 0.36, 1);
} }
#${e.containerId} .replypier-iframe { #${t.containerId} .replypier-iframe {
border-radius: 16px; border-radius: 16px;
} }
#${e.backdropId} { #${t.backdropId} {
display: block; display: block;
background: rgba(9, 9, 14, 0.45); background: rgba(9, 9, 14, 0.45);
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
z-index: ${e.zIndex}; z-index: ${t.zIndex};
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
transition: opacity 220ms ease, visibility 0s linear 220ms; transition: opacity 220ms ease, visibility 0s linear 220ms;
} }
#${e.backdropId}.active { #${t.backdropId}.active {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
transition: opacity 220ms ease; transition: opacity 220ms ease;
} }
/* The palette dims the page and closes on backdrop click / Esc, so the launcher /* The palette dims the page and closes on backdrop click / Esc, so the launcher
would just be a stray button floating over the dim. */ would just be a stray button floating over the dim. */
#${e.buttonId}.active { #${t.buttonId}.active {
display: none; display: none;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
/* Full-screen on phones like every other mode. The main mobile block below /* Full-screen on phones like every other mode. The main mobile block below
pins the edges but never resets transform \u2014 without this the centering pins the edges but never resets transform \u2014 without this the centering
translate would shove the panel half a screen to the left. */ translate would shove the panel half a screen to the left. */
#${e.containerId}, #${t.containerId},
#${e.containerId}.active { transform: none !important; } #${t.containerId}.active { transform: none !important; }
#${e.backdropId} { display: none; } #${t.backdropId} { display: none; }
} }
`:""} `:""}
${n?` ${i?`
/* ===== Sidebar mode: full-height drawer hugging the ${i} edge ===== */ /* ===== Sidebar mode: full-height drawer hugging the ${n} edge ===== */
#${e.containerId} { #${t.containerId} {
top: 0; top: 0;
bottom: auto; bottom: auto;
${i}: 0; ${n}: 0;
${i==="left"?"right: auto;":"left: auto;"} ${n==="left"?"right: auto;":"left: auto;"}
width: ${e.sidebarWidth}px; width: ${t.sidebarWidth}px;
max-width: 100vw; max-width: 100vw;
height: 100dvh; height: 100dvh;
max-height: 100dvh; max-height: 100dvh;
transform: translateX(${i==="left"?"-100%":"100%"}); transform: translateX(${n==="left"?"-100%":"100%"});
box-shadow: ${i==="left"?"":"-"}12px 0 40px rgba(0, 0, 0, 0.12); box-shadow: ${n==="left"?"":"-"}12px 0 40px rgba(0, 0, 0, 0.12);
} }
#${e.containerId}.active { #${t.containerId}.active {
transform: translateX(0); transform: translateX(0);
} }
#${e.containerId} .replypier-iframe { #${t.containerId} .replypier-iframe {
border-radius: 0; border-radius: 0;
} }
${h?` ${c?`
/* The drawer's own header chevron closes it, so keeping the launcher on screen /* The drawer's own header chevron closes it, so keeping the launcher on screen
would just park a duplicate button beside the panel. Hide it while open. */ would just park a duplicate button beside the panel. Hide it while open. */
#${e.buttonId}.active { #${t.buttonId}.active {
display: none; display: none;
} }
`:` `:`
/* ASK_ANYTHING / AURORA draw no in-panel chevron, and the drawer would paint over /* ASK_ANYTHING / AURORA draw no in-panel chevron, and the drawer would paint over
the launcher (later sibling, equal z-index) \u2014 leaving nothing to click. Keep it the launcher (later sibling, equal z-index) \u2014 leaving nothing to click. Keep it
reachable: slide it out beside the drawer edge, one layer up. */ reachable: slide it out beside the drawer edge, one layer up. */
#${e.buttonId} { #${t.buttonId} {
z-index: ${e.zIndex+1}; z-index: ${t.zIndex+1};
transition: transform 0.3s cubic-bezier(.34,1.3,.5,1), opacity 320ms ease, transition: transform 0.3s cubic-bezier(.34,1.3,.5,1), opacity 320ms ease,
left 360ms cubic-bezier(0.22, 1, 0.36, 1), right 360ms cubic-bezier(0.22, 1, 0.36, 1); left 360ms cubic-bezier(0.22, 1, 0.36, 1), right 360ms cubic-bezier(0.22, 1, 0.36, 1);
} }
#${e.buttonId}.active { #${t.buttonId}.active {
${i}: ${e.sidebarWidth+20}px; ${n}: ${t.sidebarWidth+20}px;
} }
`} `}
`:""} `:""}
@@ -353,7 +353,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
z-index: ${e.zIndex+1}; z-index: ${t.zIndex+1};
transition: all 0.3s ease; transition: all 0.3s ease;
} }
@@ -367,7 +367,7 @@
background: rgba(255, 255, 255, 0.95); background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(0, 0, 0, 0.1); border-bottom: 1px solid rgba(0, 0, 0, 0.1);
z-index: ${e.zIndex}; z-index: ${t.zIndex};
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0 20px; padding: 0 20px;
@@ -392,7 +392,7 @@
} }
@media (max-width: 768px) { @media (max-width: 768px) {
#${e.containerId} { #${t.containerId} {
width: 100vw !important; width: 100vw !important;
height: 100vh !important; height: 100vh !important;
height: 100dvh !important; /* Dynamic viewport height for mobile browsers */ height: 100dvh !important; /* Dynamic viewport height for mobile browsers */
@@ -412,7 +412,7 @@
padding: 0 !important; padding: 0 !important;
} }
#${e.containerId}.active { #${t.containerId}.active {
bottom: 0 !important; bottom: 0 !important;
} }
@@ -428,11 +428,11 @@
padding: 0 !important; padding: 0 !important;
} }
#${e.buttonId} { #${t.buttonId} {
display: none; display: none;
} }
#${e.buttonId}.mobile-closed { #${t.buttonId}.mobile-closed {
/* flex (not block) keeps the launcher's centering context; the dots/chevron /* flex (not block) keeps the launcher's centering context; the dots/chevron
are absolutely centered regardless, but this keeps it consistent. */ are absolutely centered regardless, but this keeps it consistent. */
display: flex !important; display: flex !important;
@@ -440,8 +440,8 @@
height: 56px !important; height: 56px !important;
/* Respect the configured position on mobile too, so the closed launcher can /* Respect the configured position on mobile too, so the closed launcher can
clear a fixed bottom nav bar (the main reason to move it up). */ clear a fixed bottom nav bar (the main reason to move it up). */
bottom: ${e.launcherBottom}px !important; bottom: ${t.launcherBottom}px !important;
${i}: ${e.launcherRight}px !important; ${n}: ${t.launcherRight}px !important;
} }
.ask-anything-mobile #replypier-mobile-close.active { .ask-anything-mobile #replypier-mobile-close.active {
@@ -460,7 +460,7 @@
.ask-anything-mobile #replypier-mobile-close.active { .ask-anything-mobile #replypier-mobile-close.active {
top: 15px !important; top: 15px !important;
right: 15px !important; right: 15px !important;
z-index: ${e.zIndex+2} !important; z-index: ${t.zIndex+2} !important;
} }
/* When topbar is visible, push iframe down to avoid overlap */ /* When topbar is visible, push iframe down to avoid overlap */
@@ -478,18 +478,18 @@
} }
/* Chat Initiation Message Styles */ /* Chat Initiation Message Styles */
#${e.initiationMessageId} { #${t.initiationMessageId} {
position: fixed !important; position: fixed !important;
/* Sit above the launcher (+12px gap) and align to its edge on the active side, /* Sit above the launcher (+12px gap) and align to its edge on the active side,
so it never overlaps the icon regardless of the configured position/mode. */ so it never overlaps the icon regardless of the configured position/mode. */
bottom: ${(e.launcherBottom||20)+o+12}px !important; bottom: ${(t.launcherBottom||20)+r+12}px !important;
${i}: ${e.launcherRight||20}px !important; ${n}: ${t.launcherRight||20}px !important;
max-width: 260px !important; max-width: 260px !important;
background: white !important; background: white !important;
padding: 12px 36px 12px 14px !important; padding: 12px 36px 12px 14px !important;
border-radius: 14px !important; border-radius: 14px !important;
box-shadow: 0 3px 16px rgba(0, 0, 0, 0.1) !important; box-shadow: 0 3px 16px rgba(0, 0, 0, 0.1) !important;
z-index: ${e.zIndex-1} !important; z-index: ${t.zIndex-1} !important;
cursor: pointer !important; cursor: pointer !important;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
@@ -514,7 +514,7 @@
/* Clamp the nudge text to 4 lines with an ellipsis so a long welcome/initiation /* Clamp the nudge text to 4 lines with an ellipsis so a long welcome/initiation
message can never balloon the bubble over the page or into the launcher. message can never balloon the bubble over the page or into the launcher.
Applied to the text node only, so the orb (left:-34px) is never clipped. */ Applied to the text node only, so the orb (left:-34px) is never clipped. */
#${e.initiationMessageId} .initiation-message-text { #${t.initiationMessageId} .initiation-message-text {
margin: 0 !important; margin: 0 !important;
display: -webkit-box !important; display: -webkit-box !important;
-webkit-line-clamp: 4 !important; -webkit-line-clamp: 4 !important;
@@ -536,7 +536,7 @@
z-index: 1 !important; z-index: 1 !important;
} }
@keyframes replypier-orb-spin { to { transform: rotate(360deg); } } @keyframes replypier-orb-spin { to { transform: rotate(360deg); } }
${i==="left"?` ${n==="left"?`
/* On the left edge the orb (which protrudes 34px past the bubble) would clip /* On the left edge the orb (which protrudes 34px past the bubble) would clip
off-screen \u2014 mirror it to the bubble's page-facing side. */ off-screen \u2014 mirror it to the bubble's page-facing side. */
.initiation-orb { .initiation-orb {
@@ -545,7 +545,7 @@
} }
`:""} `:""}
#${e.initiationMessageId}.show { #${t.initiationMessageId}.show {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
transform: translateY(0) scale(1); transform: translateY(0) scale(1);
@@ -566,16 +566,16 @@
} }
} }
#${e.initiationMessageId}:hover { #${t.initiationMessageId}:hover {
transform: translateY(-2px) scale(1.02); transform: translateY(-2px) scale(1.02);
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.14); box-shadow: 0 5px 20px rgba(0, 0, 0, 0.14);
} }
#${e.initiationMessageId}::after { #${t.initiationMessageId}::after {
content: '' !important; content: '' !important;
position: absolute !important; position: absolute !important;
bottom: -7px !important; bottom: -7px !important;
${i}: 30px !important; ${n}: 30px !important;
width: 14px !important; width: 14px !important;
height: 14px !important; height: 14px !important;
min-width: 14px !important; min-width: 14px !important;
@@ -627,7 +627,7 @@
content: '|'; content: '|';
animation: blink 1s step-end infinite; animation: blink 1s step-end infinite;
margin-left: 2px; margin-left: 2px;
color: ${e.chatBubbleColor}; color: ${t.chatBubbleColor};
font-weight: 500; font-weight: 500;
} }
@@ -707,7 +707,7 @@
} }
@media (max-width: 768px) { @media (max-width: 768px) {
#${e.initiationMessageId} { #${t.initiationMessageId} {
display: none !important; display: none !important;
} }
} }
@@ -859,17 +859,17 @@
} }
} }
${e.launcher?"":` ${t.launcher?"":`
/* Launcher hidden by config \u2014 the site opens the chat with its own trigger or /* Launcher hidden by config \u2014 the site opens the chat with its own trigger or
the JS API. Last in the sheet so it also beats the mobile .mobile-closed the JS API. Last in the sheet so it also beats the mobile .mobile-closed
re-show (display: flex !important at equal specificity). */ re-show (display: flex !important at equal specificity). */
#${e.buttonId}, #${e.buttonId}.mobile-closed { display: none !important; } #${t.buttonId}, #${t.buttonId}.mobile-closed { display: none !important; }
`} `}
`;let f=document.getElementById("replypier-styles");f&&f.remove(),document.head.appendChild(w),vt()}function vt(){let t=document.getElementById(e.buttonId);if(!t)return;let n=t.querySelector(".rp-search-placeholder");n&&n.textContent!==e.searchPlaceholder&&(n.textContent=e.searchPlaceholder)}function C(t){if(typeof t!="string")return null;let n=t.trim();return!n||n==="undefined"||n==="null"?null:n}function ct(){return C(window.replypierToken)||C(localStorage.getItem(e.tokenKey))}function lt(t){let n=C(t);if(!n)return null;let i=n.split(".")[1];if(!i)return null;try{let o=JSON.parse(atob(i.replace(/-/g,"+").replace(/_/g,"/")));return o.sub||o.customer_id||null}catch(o){return null}}function J(t){t&&localStorage.setItem(e.tokenKey,t)}function W(){localStorage.removeItem(e.tokenKey)}function H(){return window.innerWidth<=768}function Bt(t){return t?t.replace(/\uFFFD/g,"").replace(/[��]/g,"").replace(/[\x00-\x1F\x7F-\x9F]/g,"").trim():""}function Nt(){if(!e.chatInitiationMessages||e.chatInitiationMessages.length===0)return null;let t=Math.floor(Math.random()*e.chatInitiationMessages.length),n=e.chatInitiationMessages[t];return n?Bt(n):null}function Rt(){return H()||!e.launcher||!e.chatInitiationMessages||e.chatInitiationMessages.length===0?!1:!sessionStorage.getItem(e.initiationShownKey)}function dt(){sessionStorage.setItem(e.initiationShownKey,"true")}function Pt(){let t=Nt();if(!t)return null;let n=document.createElement("div");return n.id=e.initiationMessageId,n.innerHTML=` `;let f=document.getElementById("replypier-styles");f&&f.remove(),document.head.appendChild(w),ve()}function ve(){let e=document.getElementById(t.buttonId);if(!e)return;let i=e.querySelector(".rp-search-placeholder");i&&i.textContent!==t.searchPlaceholder&&(i.textContent=t.searchPlaceholder)}function C(e){if(typeof e!="string")return null;let i=e.trim();return!i||i==="undefined"||i==="null"?null:i}function le(){return C(window.replypierToken)||C(localStorage.getItem(t.tokenKey))}function de(e){let i=C(e);if(!i)return null;let n=i.split(".")[1];if(!n)return null;try{let r=JSON.parse(atob(n.replace(/-/g,"+").replace(/_/g,"/")));return r.sub||r.customer_id||null}catch(r){return null}}function J(e){e&&localStorage.setItem(t.tokenKey,e)}function W(){localStorage.removeItem(t.tokenKey)}function H(){return window.innerWidth<=768}function Be(e){return e?e.replace(/\uFFFD/g,"").replace(/[��]/g,"").replace(/[\x00-\x1F\x7F-\x9F]/g,"").trim():""}function Pe(){if(!t.chatInitiationMessages||t.chatInitiationMessages.length===0)return null;let e=Math.floor(Math.random()*t.chatInitiationMessages.length),i=t.chatInitiationMessages[e];return i?Be(i):null}function Re(){return H()||!t.launcher||!t.chatInitiationMessages||t.chatInitiationMessages.length===0?!1:!sessionStorage.getItem(t.initiationShownKey)}function pe(){sessionStorage.setItem(t.initiationShownKey,"true")}function Ne(){let e=Pe();if(!e)return null;let i=document.createElement("div");return i.id=t.initiationMessageId,i.innerHTML=`
<span class="initiation-orb"></span> <span class="initiation-orb"></span>
<button class="initiation-close" aria-label="\u5173\u95ED"></button> <button class="initiation-close" aria-label="\u5173\u95ED"></button>
<p class="initiation-message-text"></p> <p class="initiation-message-text"></p>
`,n.dataset.fullMessage=t,n}function Ft(t,n,i=50){let o=t.querySelector(".initiation-message-text");if(!o)return;let h=0,r=setInterval(()=>{h<n.length?(o.textContent+=n.charAt(h),h++):(clearInterval(r),o.classList.add("typing-complete"),t.typeInterval=null)},i);t.typeInterval=r}function Ot(t,n){if(!t||!Rt())return;document.body.appendChild(t),setTimeout(()=>{t.classList.add("show"),setTimeout(()=>{let o=t.dataset.fullMessage;o&&Ft(t,o,40)},300)},1500),t.addEventListener("click",o=>{o.target.classList.contains("initiation-close")||(t.typeInterval&&clearInterval(t.typeInterval),ht(t),n(),dt())});let i=t.querySelector(".initiation-close");i&&i.addEventListener("click",o=>{o.stopPropagation(),t.typeInterval&&clearInterval(t.typeInterval),ht(t),dt()})}function ht(t){t&&(t.classList.remove("show"),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},400))}function pt(){y=!1,O();let t=document.createElement("div");t.id=e.buttonId,t.classList.add("replypier-pending"),t.innerHTML=` `,i.dataset.fullMessage=e,i}function Fe(e,i,n=50){let r=e.querySelector(".initiation-message-text");if(!r)return;let c=0,a=setInterval(()=>{c<i.length?(r.textContent+=i.charAt(c),c++):(clearInterval(a),r.classList.add("typing-complete"),e.typeInterval=null)},n);e.typeInterval=a}function Oe(e,i){if(!e||!Re())return;document.body.appendChild(e),setTimeout(()=>{e.classList.add("show"),setTimeout(()=>{let r=e.dataset.fullMessage;r&&Fe(e,r,40)},300)},1500),e.addEventListener("click",r=>{r.target.classList.contains("initiation-close")||(e.typeInterval&&clearInterval(e.typeInterval),ce(e),i(),pe())});let n=e.querySelector(".initiation-close");n&&n.addEventListener("click",r=>{r.stopPropagation(),e.typeInterval&&clearInterval(e.typeInterval),ce(e),pe()})}function ce(e){e&&(e.classList.remove("show"),setTimeout(()=>{e.parentNode&&e.parentNode.removeChild(e)},400))}function he(){x=!1,O();let e=document.createElement("div");e.id=t.buttonId,e.classList.add("replypier-pending"),e.innerHTML=`
<span class="rp-ring"></span> <span class="rp-ring"></span>
<span class="rp-ring r2"></span> <span class="rp-ring r2"></span>
<div class="rp-dots"><span class="rp-dot"></span><span class="rp-dot"></span><span class="rp-dot"></span></div> <div class="rp-dots"><span class="rp-dot"></span><span class="rp-dot"></span><span class="rp-dot"></span></div>
@@ -879,11 +879,11 @@
<span class="rp-search-placeholder"></span> <span class="rp-search-placeholder"></span>
</span> </span>
<span class="rp-badge"></span> <span class="rp-badge"></span>
`,E=setTimeout(P,8e3);let n=document.createElement("div");n.id=e.containerId;let i=document.createElement("div");i.id="replypier-mobile-close",i.innerHTML=` `,M=setTimeout(N,8e3);let i=document.createElement("div");i.id=t.containerId;let n=document.createElement("div");n.id="replypier-mobile-close",n.innerHTML=`
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 12H19" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M5 12H19" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
`;let o=document.createElement("div");o.id="replypier-mobile-topbar",o.innerHTML=` `;let r=document.createElement("div");r.id="replypier-mobile-topbar",r.innerHTML=`
<h3 class="topbar-title">\u804A\u5929</h3> <h3 class="topbar-title">\u804A\u5929</h3>
<div style="width: 44px;"></div> <div style="width: 44px;"></div>
`;let h=document.createElement("div");h.id=e.backdropId,document.body.appendChild(t),document.body.appendChild(h),document.body.appendChild(n),document.body.appendChild(i),document.body.appendChild(o),F(),vt();let r=!1,l=null,w=!1,f=null;function b(){r=!r,n.classList.toggle("active"),h.classList.toggle("active",r),t.classList.toggle("active"),i.classList.toggle("active"),r&&(e.hasOpened=!0,e.unreadCount=0),l&&l.contentWindow&&l.contentWindow.postMessage({type:"WIDGET_VISIBILITY",open:r},"*"),F(),r&&f&&(ht(f),f=null,dt()),H()?r?(t.classList.remove("mobile-closed"),t.style.animation=""):(t.classList.add("mobile-closed"),t.style.animation="replypier-float 4s ease-in-out infinite",o.classList.remove("active"),document.body.classList.remove("ask-anything-mobile")):r&&i.classList.remove("active"),r&&l&&l.contentWindow.postMessage({type:"SCROLL_TO_BOTTOM"},"*"),_(r?"open":"close")}let U=null,ft=!1;function Gt(a){let s=String(a||"").slice(0,2e3);s&&(ft&&l&&l.contentWindow?l.contentWindow.postMessage({type:"PREFILL_MESSAGE",text:s},"*"):U=s)}let K=0;function Ct(){for(K++;n.firstChild;)n.removeChild(n.firstChild);l=null,g=null,T=null,ft=!1,ot=!1,U=null,w=!1,t.classList.remove("loading"),N=0,q()}function jt(){r&&b(),Ct(),e.unreadCount=0,e.hasOpened=!1,F(),Q()}function qt(){r&&b(),Ct(),window.removeEventListener("resize",Tt),window.removeEventListener("message",At),f&&(f.typeInterval&&clearInterval(f.typeInterval),mt(f),f=null),[t,h,n,i,o].forEach(mt),mt(document.getElementById("replypier-styles")),document.body.classList.remove("ask-anything-mobile"),rt=!1,E&&(clearTimeout(E),E=null),st.length=0,e.unreadCount=0,e.hasOpened=!1,c=null,R.length=0,V=!1,y=!0}function St(a){let s=document.createElement("div");s.className="replypier-error-ui";let I=document.createElement("div");I.className="replypier-error-card";let v=document.createElement("button");v.type="button",v.className="replypier-error-close",v.setAttribute("aria-label","\u5173\u95ED\u804A\u5929"),v.textContent="\xD7",v.addEventListener("click",function(){r&&b()}),s.appendChild(v);let u=document.createElement("div");u.className="replypier-error-icon-wrapper";let p=document.createElementNS("http://www.w3.org/2000/svg","svg");p.setAttribute("class","replypier-error-icon"),p.setAttribute("viewBox","0 0 24 24"),p.setAttribute("fill","none"),p.setAttribute("stroke","currentColor"),p.setAttribute("stroke-width","1.5");let k=document.createElementNS("http://www.w3.org/2000/svg","path");k.setAttribute("d","M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z");let $=document.createElementNS("http://www.w3.org/2000/svg","path");$.setAttribute("d","M9 12l2 2 4-4"),p.appendChild(k),p.appendChild($),u.appendChild(p);let S=document.createElement("h2");S.className="replypier-error-title",S.textContent="\u5BA2\u670D\u6682\u4E0D\u53EF\u7528";let bt=document.createElement("p");bt.className="replypier-error-message",bt.textContent=a;let et=document.createElement("div");et.className="replypier-error-footer";let xt=document.createElement("span");xt.innerHTML=_t;let yt=xt.querySelector("svg");yt.setAttribute("width","20"),yt.setAttribute("height","16"),yt.setAttribute("aria-hidden","true");let L=document.createElement("a");return L.href="https://replypier.site",L.target="_blank",L.rel="noopener noreferrer",L.className="replypier-error-footer-link",L.textContent="\u7531 ReplyPier \u63D0\u4F9B\u652F\u6301",et.appendChild(xt),et.appendChild(L),I.appendChild(u),I.appendChild(S),I.appendChild(bt),I.appendChild(et),s.appendChild(I),s}function Q(){return wt(this,null,function*(){if(w||l)return;let a=K;try{w=!0,t.classList.add("loading");let s=ct(),I=`${e.baseUrl}/widgets/${window.replypierId}/data?widget_id=${window.replypierId}`,v={method:"GET",mode:"cors",headers:s?{Authorization:`Bearer ${s}`}:{}};fetch(I,v).then(u=>wt(null,null,function*(){if(a!==K)return null;if(u.status===401)try{let k=(yield u.json()).detail;if(k&&k.code==="identity_expired")return t.classList.remove("loading"),_("identityExpired"),Vt(),null;let $=typeof k=="string"?k:"";if($.includes("generate-token")||$.includes("API key")||$.includes("Token must be obtained")){t.classList.remove("loading"),W();let S=St("\u6B64\u804A\u5929\u7EC4\u4EF6\u5C1A\u672A\u914D\u7F6E\u5B8C\u6210\uFF0C\u8BF7\u8054\u7CFB\u7F51\u7AD9\u7BA1\u7406\u5458\u5F00\u901A\u5728\u7EBF\u5BA2\u670D\u3002");return n.appendChild(S),P(),null}}catch(p){}if(!u.ok)throw new Error("HTTP error! status: "+u.status);return u.text()})).then(u=>{u!==null&&a===K&&(l=document.createElement("iframe"),l.className="replypier-iframe",l.title="\u804A\u5929",l.srcdoc=u,l.addEventListener("load",function(){ft=!0,l.contentWindow.postMessage(It(),"*"),l.contentWindow.postMessage({type:"WIDGET_VISIBILITY",open:r},"*"),U&&(l.contentWindow.postMessage({type:"PREFILL_MESSAGE",text:U},"*"),U=null),ot=!0,D=!1,_("ready")}),n.appendChild(l),g=l.contentWindow,t.classList.remove("loading"),l.style.opacity="1")}).catch(u=>{if(a!==K)return;console.error("Failed to load widget:",u),t.classList.rem Line truncated `;let c=document.createElement("div");c.id=t.backdropId,document.body.appendChild(e),document.body.appendChild(c),document.body.appendChild(i),document.body.appendChild(n),document.body.appendChild(r),F(),ve();let a=!1,d=null,w=!1,f=null;function b(){a=!a,i.classList.toggle("active"),c.classList.toggle("active",a),e.classList.toggle("active"),n.classList.toggle("active"),a&&(t.hasOpened=!0,t.unreadCount=0),d&&d.contentWindow&&d.contentWindow.postMessage({type:"WIDGET_VISIBILITY",open:a},"*"),F(),a&&f&&(ce(f),f=null,pe()),H()?a?(e.classList.remove("mobile-closed"),e.style.animation=""):(e.classList.add("mobile-closed"),e.style.animation="replypier-float 4s ease-in-out infinite",r.classList.remove("active"),document.body.classList.remove("ask-anything-mobile")):a&&n.classList.remove("active"),a&&d&&d.contentWindow.postMessage({type:"SCROLL_TO_BOTTOM"},"*"),_(a?"open":"close")}let U=null,fe=!1;function Ge(o){let s=String(o||"").slice(0,2e3);s&&(fe&&d&&d.contentWindow?d.contentWindow.postMessage({type:"PREFILL_MESSAGE",text:s},"*"):U=s)}let K=0;function Ce(){for(K++;i.firstChild;)i.removeChild(i.firstChild);d=null,g=null,L=null,fe=!1,re=!1,U=null,w=!1,e.classList.remove("loading"),P=0,q()}function je(){a&&b(),Ce(),t.unreadCount=0,t.hasOpened=!1,F(),Q()}function qe(){a&&b(),Ce(),window.removeEventListener("resize",Le),window.removeEventListener("message",Ae),f&&(f.typeInterval&&clearInterval(f.typeInterval),me(f),f=null),[e,c,i,n,r].forEach(me),me(document.getElementById("replypier-styles")),document.body.classList.remove("ask-anything-mobile"),ae=!1,M&&(clearTimeout(M),M=null),se.length=0,t.unreadCount=0,t.hasOpened=!1,l=null,R.length=0,V=!1,x=!0}function Se(o){let s=document.createElement("div");s.className="replypier-error-ui";let I=document.createElement("div");I.className="replypier-error-card";let v=document.createElement("button");v.type="button",v.className="replypier-error-close",v.setAttribute("aria-label","\u5173\u95ED\u804A\u5929"),v.textContent="\xD7",v.addEventListener("click",function(){a&&b()}),s.appendChild(v);let u=document.createElement("div");u.className="replypier-error-icon-wrapper";let h=document.createElementNS("http://www.w3.org/2000/svg","svg");h.setAttribute("class","replypier-error-icon"),h.setAttribute("viewBox","0 0 24 24"),h.setAttribute("fill","none"),h.setAttribute("stroke","currentColor"),h.setAttribute("stroke-width","1.5");let k=document.createElementNS("http://www.w3.org/2000/svg","path");k.setAttribute("d","M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z");let $=document.createElementNS("http://www.w3.org/2000/svg","path");$.setAttribute("d","M9 12l2 2 4-4"),h.appendChild(k),h.appendChild($),u.appendChild(h);let S=document.createElement("h2");S.className="replypier-error-title",S.textContent="\u5BA2\u670D\u6682\u4E0D\u53EF\u7528";let be=document.createElement("p");be.className="replypier-error-message",be.textContent=o;let te=document.createElement("div");te.className="replypier-error-footer";let ye=document.createElement("span");ye.innerHTML=_e;let xe=ye.querySelector("svg");xe.setAttribute("width","20"),xe.setAttribute("height","16"),xe.setAttribute("aria-hidden","true");let T=document.createElement("a");return T.href="https://replypier.site",T.target="_blank",T.rel="noopener noreferrer",T.className="replypier-error-footer-link",T.textContent="\u7531 ReplyPier \u63D0\u4F9B\u652F\u6301",te.appendChild(ye),te.appendChild(T),I.appendChild(u),I.appendChild(S),I.appendChild(be),I.appendChild(te),s.appendChild(I),s}function Q(){return we(this,null,function*(){if(w||d)return;let o=K;try{w=!0,e.classList.add("loading");let s=le(),I=`${t.baseUrl}/widgets/${window.replypierId}/data?widget_id=${window.replypierId}`,v={method:"GET",mode:"cors",headers:s?{Authorization:`Bearer ${s}`}:{}};fetch(I,v).then(u=>we(null,null,function*(){if(o!==K)return null;if(u.status===401)try{let k=(yield u.json()).detail;if(k&&k.code==="identity_expired")return e.classList.remove("loading"),_("identityExpired"),Ve(),null;let $=typeof k=="string"?k:"";if($.includes("generate-token")||$.includes("API key")||$.includes("Token must be obtained")){e.classList.remove("loading"),W();let S=Se("\u6B64\u804A\u5929\u7EC4\u4EF6\u5C1A\u672A\u914D\u7F6E\u5B8C\u6210\uFF0C\u8BF7\u8054\u7CFB\u7F51\u7AD9\u7BA1\u7406\u5458\u5F00\u901A\u5728\u7EBF\u5BA2\u670D\u3002");return i.appendChild(S),N(),null}}catch(h){}if(!u.ok)throw new Error("HTTP error! status: "+u.status);return u.text()})).then(u=>{u!==null&&o===K&&(d=document.createElement("iframe"),d.className="replypier-iframe",d.title="\u804A\u5929",d.srcdoc=u,d.addEventListener("load",function(){fe=!0,d.contentWindow.postMessage(Ie(),"*"),d.contentWindow.postMessage({type:"WIDGET_VISIBILITY",open:a},"*"),U&&(d.contentWindow.postMessage({type:"PREFILL_MESSAGE",text:U},"*"),U=null),re=!0,Y=!1,_("ready")}),i.appendChild(d),g=d.contentWindow,e.classList.remove("loading"),d.style.opacity="1")}).catch(u=>{if(o!==K)return;console.error("Failed to load widget:",u),e.classList.rem Line truncated
+40 -35
View File
@@ -1,49 +1,54 @@
# ReplyPier # ReplyPier
> ReplyPier is an open-source AI customer-support chatbot platform. You can sign up, create AI > ReplyPier 是一个开源的 AI 客服系统:AI 先依据知识库答疑,转人工后在同一个会话里接着处理,
> agents, add knowledge sources, and embed a chat widget — all from a browser, the terminal (CLI), > 服务结束邀请访客评价。仓库包含后端、客服工作台、嵌入 Widget、帮助中心与端到端测试。
> or programmatically via an AI agent over MCP.
## Start here (AI agents & automation) ## 仓库
Use the **ReplyPier CLI** (the `replypier` command, installed with `pip install replypier-cli`) - 源码:http://149.71.241.228:10489/WangShen007/replypier
to sign up and manage everything from the terminal. It is the account/config CLI — distinct from the - 分支:`main`
self-host Docker tool (`npm install -g replypier-deploy`).
Minimal end-to-end flow: ## 本机运行
需要 PostgreSQL 16 + pgvector、Redis、Python 3.12 与 Node:
```bash ```bash
pip install replypier-cli ./deploy/local/local.sh setup # 首次运行:安装依赖并构建前端
replypier signup --name "Acme Inc" --domain acme.com --admin-email you@acme.com --admin-name "You" ./deploy/local/local.sh up # 启动数据库、后端与前端
replypier agent create --name "Support" --type customer_support -i "Be concise" --json ./deploy/local/local.sh status # 查看各服务状态
# Add knowledge from a URL or a local PDF:
replypier knowledge add-url --website https://docs.acme.com --agent-id <agent-id>
replypier knowledge add-file ./guide.pdf --agent-id <agent-id>
# Create a widget to get the embed id:
replypier widget create --agent-id <agent-id> --name "Website widget"
# Embed on your site:
# <script>window.replypierId='YOUR_WIDGET_ID';</script>
# <script src="https://app.replypier.site/webclient/replypier.min.js"></script>
``` ```
## Docs - 客服工作台:http://127.0.0.1:3018/
- 悬浮客服演示页:http://127.0.0.1:3018/local-demo.html
- 部署、账号与恢复说明:`LOCAL_DEPLOY.md`
- [AI Agent & Automation Quickstart](https://docs.replypier.site/features/agents-quickstart): copy-paste path from signup to a live widget, built for AI agents and CI. ## 网站接入
- [CLI reference](https://docs.replypier.site/features/cli): the account CLI — signup, login, tokens, agents, workflows, knowledge.
- [MCP Server](https://docs.replypier.site/features/mcp-server): let an AI agent configure ReplyPier over the Model Context Protocol (`replypier-mcp`).
- [Quickstart](https://docs.replypier.site/quickstart): hosted vs self-host, side by side.
- [Widget integration](https://docs.replypier.site/features/widget): embed the chat widget on any site.
- [Widget Apps & API keys](https://docs.replypier.site/features/widget-apps): authenticated widgets and programmatic tokens.
## Key URLs 在后台创建一个 Widget 拿到 id,然后在页面里引入加载器:
- Sign up / dashboard: https://app.replypier.site ```html
- Hosted API base: https://api.replypier.site (routes under `/api/v1`) <script>window.replypierId = "YOUR_WIDGET_ID";</script>
- Widget script: https://app.replypier.site/webclient/replypier.min.js <script src="https://<你的域名>/webclient/replypier.min.js" async></script>
- Documentation: https://docs.replypier.site ```
- GitHub: https://github.com/replypier/replypier.site
## Self-hosting (optional) 自建实例的域名即后端或前端对外地址;加载器默认读取 `window.replypierBaseUrl`,
未设置时用构建时写入的 API 地址。站点已登录用户可用
`window.ReplyPier.identify(token)` 把访客身份绑定到自己的账号。
- [Self-host with Docker](https://docs.replypier.site/quickstart#option-b-self-host-with-docker): run the full stack on your own infrastructure with the self-host CLI (`npm install -g replypier-deploy`, command `replypier-deploy init/start/stop`). ## 代码结构
- [Docker deployment](https://docs.replypier.site/deployment/docker)
- `backend/app/`:FastAPI 应用、Agent、知识处理、工单与后台任务
- `backend/tests/`:后端测试
- `frontend/src/`:管理后台、客服工作台与嵌入 Widget
- `frontend/src/webclient/replypier.js`:网站加载器源码,构建到 `frontend/public/webclient/replypier.min.js`
- `e2e/`:Playwright 端到端测试,约定见 `e2e/AUTHORING.md`
- `deploy/local/`:本机环境与启停脚本
## 技术栈
Python、FastAPI、SQLAlchemy、Alembic、PostgreSQL/pgvector、Redis、Agno;
Vue 3、TypeScript、Vite、Socket.IO。
## 许可
Apache License 2.0,见 `LICENSE` 与 `NOTICE`。