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
+4070 -3951

No files matched your search

+1 -1
View File
@@ -31,7 +31,7 @@ uploads/
!/wordpress/replypier-chat.zip
# Firebase service account key (contains sensitive credentials)
replypier-13e0a-firebase-adminsdk-7dqo6-57f3de88cb.json
*-firebase-adminsdk-*.json
test.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
files need **no changes**.
> **As deployed on the ReplyPier cloud VPS (2026-07-14):** the base domain is
> **`help.replypier.site`** (orgs serve at `{slug}.help.replypier.site`), not
> the `help.replypier.site` this doc originally planned — that domain was never
> registered, and the org only owns `replypier.site`. DNS is **Route53**, so the
> wildcard cert is issued and auto-renewed with the `dns-route53` plugin.
> **As deployed on the ReplyPier VPS (2026-07-14):** the base domain is
> **`help.replypier.site`** (orgs serve at `{slug}.help.replypier.site`). DNS is
> **Route53**, so the wildcard cert is issued and auto-renewed with the
> `dns-route53` plugin.
> 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.
+1 -1
View File
@@ -70,7 +70,7 @@ Python 后端、知识库 worker、前端预览服务在宿主机运行;Postgr
- Widget 与演示配置:[test-environment.json](deploy/local/.state/test-environment.json)。
- 模型配置:DeepSeek / `deepseek-flash`;API Key 由应用加密保存在数据库中。
启动脚本使用上述 context、项目名和卷。不要随意改名、删除数据卷或关闭承载数据库的 Colima 虚拟机。
启动脚本使用上述 context、项目名和卷。不要随意改动这些名称、删除数据卷或关闭承载数据库的 Colima 虚拟机。
恢复数据时须同时保留数据库和 `backend/.env` 中的加密配置,否则加密保存的模型配置及消息可能无法读取。不要覆盖密钥、重置管理员,或将私有环境文件提交和对外分享。
+5 -4
View File
@@ -1,8 +1,9 @@
ReplyPier
Copyright 2024-2026 ReplyPier
This product includes software developed by ReplyPier
(https://replypier.site).
This product is the ReplyPier AI customer-support platform: an AI agent answers
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
the full license text.
Licensed under the Apache License, Version 2.0. See the LICENSE file for the
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["domain_status"] == "pending"
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), \
patch.object(dv, "check_cname_record", return_value=True), \
@@ -359,7 +359,7 @@ class TestRowScopeCaseInsensitivity:
)
def test_predicate_folds_both_sides(self):
result = self._ci("Arun@replypier.site")
result = self._ci("Arun@ReplyPier.Site")
assert result.ok
assert "LOWER(customer_email) = '%s'" % OWNER in result.sql
@@ -369,10 +369,10 @@ class TestRowScopeCaseInsensitivity:
assert "'x'' or ''1''=''1'" in result.sql.lower()
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 "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):
result = self._ci(value=None)
@@ -41,7 +41,7 @@ class TestScrubOutbound:
assert scrub_outbound(text, (OWNER_EMAIL,)) == text
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
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:
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):
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
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
+14 -17
View File
@@ -3,30 +3,27 @@
// in a page self === window, so window.APP_CONFIG still works.
self.APP_CONFIG = {
// API URLs
API_URL: "https://devapi.replypier.site/api/v1",
WS_URL: "wss://devapi.replypier.site",
WIDGET_URL: "https://devapp.replypier.site",
API_URL: "http://localhost:8000/api/v1",
WS_URL: "ws://localhost:8000",
WIDGET_URL: "http://localhost:8000",
// Firebase Configuration
FIREBASE_API_KEY: "AIzaSyDTwDgANnBItYx39qm-6shq4cUws6QY4wI",
FIREBASE_AUTH_DOMAIN: "replypier-13e0a.firebaseapp.com",
FIREBASE_PROJECT_ID: "replypier-13e0a",
FIREBASE_MESSAGING_SENDER_ID: "680125690355",
FIREBASE_APP_ID: "1:680125690355:web:466f50999a6c91935ae286",
FIREBASE_STORAGE_BUCKET: "replypier-13e0a.firebasestorage.app",
FIREBASE_MEASUREMENT_ID: "G-7LGB58R2Q7",
FIREBASE_VAPID_KEY: "BJKnggErez2qK3AxBhHyEV-EoS9lfcWO1GDXhRwUKIHbT_PH23vhY_8HRIz_KyuC-0gbuViSrtSMiFp7zgyYY4g",
// Firebase Configuration (fill in for your own project; empty disables it)
FIREBASE_API_KEY: "",
FIREBASE_AUTH_DOMAIN: "",
FIREBASE_PROJECT_ID: "",
FIREBASE_MESSAGING_SENDER_ID: "",
FIREBASE_APP_ID: "",
FIREBASE_STORAGE_BUCKET: "",
FIREBASE_MEASUREMENT_ID: "",
FIREBASE_VAPID_KEY: "",
// Razorpay Checkout
RAZORPAY_KEY_ID: "rzp_test_T92OR9gqPBht0N",
GTM_ID: "",
RAZORPAY_KEY_ID: "",
// Google Fonts API
GOOGLE_FONTS_API_KEY: "AIzaSyCTQCSK_ybLEglabeusbyekQGEyvFr2C5o",
GOOGLE_FONTS_API_KEY: "",
DEMO_WIDGET_ID: "a6ee36e9-b70e-4b12-8f94-cc5a5b147f26",
// Node Environment
NODE_ENV: "development",
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>
`;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=`
(()=>{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 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) ===== */
#${e.buttonId} {
#${t.buttonId} {
position: fixed;
bottom: ${e.launcherBottom}px;
${i}: ${e.launcherRight}px;
bottom: ${t.launcherBottom}px;
${n}: ${t.launcherRight}px;
width: 64px;
height: 64px;
border-radius: 20px 20px 20px 6px;
background: ${e.chatBubbleColor};
color: ${zt(e.chatBubbleColor)};
box-shadow: 0 16px 40px -8px ${e.chatBubbleColor}cc, inset 0 0 0 1px rgba(255,255,255,0.08);
background: ${t.chatBubbleColor};
color: ${ze(t.chatBubbleColor)};
box-shadow: 0 16px 40px -8px ${t.chatBubbleColor}cc, inset 0 0 0 1px rgba(255,255,255,0.08);
cursor: pointer;
z-index: ${e.zIndex};
z-index: ${t.zIndex};
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s cubic-bezier(.34,1.3,.5,1), opacity 320ms ease;
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
widget errors out / times out) \u2014 avoids ever flashing the wrong color. */
#${e.buttonId}.replypier-pending {
#${t.buttonId}.replypier-pending {
opacity: 0;
pointer-events: none;
}
#${e.buttonId}:hover { transform: scale(1.06); }
#${t.buttonId}:hover { transform: scale(1.06); }
/* Expanding rings (closed state only) */
#${e.buttonId} .rp-ring {
#${t.buttonId} .rp-ring {
position: absolute;
inset: 0;
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;
pointer-events: none;
}
#${e.buttonId} .rp-ring.r2 { animation-delay: 1.2s; }
#${e.buttonId}.active .rp-ring { display: none; }
#${t.buttonId} .rp-ring.r2 { animation-delay: 1.2s; }
#${t.buttonId}.active .rp-ring { display: none; }
/* 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,
block when .mobile-closed) or viewport. */
#${e.buttonId} .rp-dots {
#${t.buttonId} .rp-dots {
display: flex;
gap: 5px;
position: absolute;
@@ -51,19 +51,19 @@
left: 50%;
transform: translate(-50%, -50%);
}
#${e.buttonId} .rp-dot {
#${t.buttonId} .rp-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
animation: replypier-pulse 1.5s ease-in-out infinite;
}
#${e.buttonId} .rp-dot:nth-child(2) { animation-delay: .18s; }
#${e.buttonId} .rp-dot:nth-child(3) { animation-delay: .36s; }
#${e.buttonId}.active .rp-dots { display: none; }
#${t.buttonId} .rp-dot:nth-child(2) { animation-delay: .18s; }
#${t.buttonId} .rp-dot:nth-child(3) { animation-delay: .36s; }
#${t.buttonId}.active .rp-dots { display: none; }
/* Chevron (open) \u2014 absolutely centered, pinned to the button center. */
#${e.buttonId} .rp-chevron {
#${t.buttonId} .rp-chevron {
display: none;
position: absolute;
top: 50%;
@@ -74,10 +74,10 @@
line-height: 1;
color: currentColor;
}
#${e.buttonId}.active .rp-chevron { display: block; }
#${t.buttonId}.active .rp-chevron { display: block; }
/* Unread badge = number of nudges */
#${e.buttonId} .rp-badge {
#${t.buttonId} .rp-badge {
position: absolute;
top: -5px;
right: -5px;
@@ -94,11 +94,11 @@
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
display: none;
}
#${e.buttonId}.active .rp-badge { display: none; }
#${t.buttonId}.active .rp-badge { display: none; }
#${e.buttonId}.loading .rp-dots,
#${e.buttonId}.loading .rp-chevron { visibility: hidden; }
#${e.buttonId}.loading:after {
#${t.buttonId}.loading .rp-dots,
#${t.buttonId}.loading .rp-chevron { visibility: hidden; }
#${t.buttonId}.loading:after {
content: '';
position: absolute;
width: 16px;
@@ -113,47 +113,47 @@
}
/* 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) {
/* ===== Search-bar mode: the launcher becomes a search-input-like bar.
Same element, same click/toggle/badge logic. Mobile keeps the bubble. ===== */
#${e.buttonId} {
width: ${e.searchBarWidth}px;
#${t.buttonId} {
width: ${t.searchBarWidth}px;
height: 48px;
border-radius: 24px;
padding: 0 16px;
background: #ffffff;
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;
justify-content: flex-start;
box-sizing: border-box;
}
#${e.buttonId}:hover { transform: scale(1.02); }
#${e.buttonId} .rp-ring,
#${e.buttonId} .rp-dots,
#${e.buttonId} .rp-chevron,
#${e.buttonId}.active .rp-chevron { display: none; }
#${t.buttonId}:hover { transform: scale(1.02); }
#${t.buttonId} .rp-ring,
#${t.buttonId} .rp-dots,
#${t.buttonId} .rp-chevron,
#${t.buttonId}.active .rp-chevron { display: none; }
/* Dark spinner \u2014 the stock white one vanishes on the white bar. */
#${e.buttonId}.loading .rp-search { visibility: hidden; }
#${e.buttonId}.loading:after {
#${t.buttonId}.loading .rp-search { visibility: hidden; }
#${t.buttonId}.loading:after {
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;
align-items: center;
gap: 10px;
min-width: 0;
width: 100%;
}
#${e.buttonId} .rp-search svg {
#${t.buttonId} .rp-search svg {
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;
color: #6b7280;
white-space: nowrap;
@@ -169,30 +169,30 @@
@keyframes replypier-pulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
#${e.buttonId},
#${e.buttonId} .rp-ring,
#${e.buttonId} .rp-dot,
#${t.buttonId},
#${t.buttonId} .rp-ring,
#${t.buttonId} .rp-dot,
.initiation-orb { animation: none !important; }
/* Open and close in place: no slide, scale or fade for people who asked for less motion. */
#${e.buttonId},
#${e.containerId},
#${e.backdropId} { transition: none !important; }
#${t.buttonId},
#${t.containerId},
#${t.backdropId} { transition: none !important; }
}
#${e.containerId} {
#${t.containerId} {
position: fixed;
/* The window sits above the launcher; when the launcher is moved (via
ReplyPier.init position / setPosition) the window shifts by the same
delta so the two stay together. Mobile is full-screen (media query below)
and is unaffected. */
bottom: ${(e.containerBottom||100)+((e.launcherBottom||20)-20)}px;
${i}: ${(e.containerRight||20)+((e.launcherRight||20)-20)}px;
width: ${e.containerWidth||384}px;
bottom: ${(t.containerBottom||100)+((t.launcherBottom||20)-20)}px;
${n}: ${(t.containerRight||20)+((t.launcherRight||20)-20)}px;
width: ${t.containerWidth||384}px;
max-width: calc(100vw - 48px);
height: ${e.containerHeight}px;
height: ${t.containerHeight}px;
max-height: calc(100vh - 132px);
background: transparent;
z-index: ${e.zIndex};
z-index: ${t.zIndex};
overflow: hidden;
/* Animated open/close with slide + fade */
opacity: 0;
@@ -205,7 +205,7 @@
pointer-events: none;
}
#${e.containerId}.active {
#${t.containerId}.active {
opacity: 1;
visibility: visible;
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
even when it is not in use \u2014 an unstyled div in <body> adds a phantom row to
a flex/grid body layout. */
#${e.backdropId} {
#${t.backdropId} {
position: fixed;
inset: 0;
display: none;
}
${r?`
${a?`
/* ===== 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
the input rather than the middle of the screen. ===== */
#${e.containerId} {
#${t.containerId} {
top: 12vh;
bottom: auto;
left: 50%;
right: auto;
width: min(${l.width}px, calc(100vw - 32px));
width: min(${d.width}px, calc(100vw - 32px));
max-width: none;
/* 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,
which sets an inline height (clamped to this ceiling). height:100% against
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;
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),
transform 360ms cubic-bezier(0.22, 1, 0.36, 1),
height 200ms cubic-bezier(0.22, 1, 0.36, 1),
visibility 0s linear 360ms;
}
#${e.containerId}.active {
#${t.containerId}.active {
transform: translate(-50%, 0) scale(1);
transition: opacity 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);
}
#${e.containerId} .replypier-iframe {
#${t.containerId} .replypier-iframe {
border-radius: 16px;
}
#${e.backdropId} {
#${t.backdropId} {
display: block;
background: rgba(9, 9, 14, 0.45);
backdrop-filter: blur(2px);
z-index: ${e.zIndex};
z-index: ${t.zIndex};
opacity: 0;
visibility: hidden;
transition: opacity 220ms ease, visibility 0s linear 220ms;
}
#${e.backdropId}.active {
#${t.backdropId}.active {
opacity: 1;
visibility: visible;
transition: opacity 220ms ease;
}
/* The palette dims the page and closes on backdrop click / Esc, so the launcher
would just be a stray button floating over the dim. */
#${e.buttonId}.active {
#${t.buttonId}.active {
display: none;
}
@media (max-width: 768px) {
/* 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
translate would shove the panel half a screen to the left. */
#${e.containerId},
#${e.containerId}.active { transform: none !important; }
#${e.backdropId} { display: none; }
#${t.containerId},
#${t.containerId}.active { transform: none !important; }
#${t.backdropId} { display: none; }
}
`:""}
${n?`
/* ===== Sidebar mode: full-height drawer hugging the ${i} edge ===== */
#${e.containerId} {
${i?`
/* ===== Sidebar mode: full-height drawer hugging the ${n} edge ===== */
#${t.containerId} {
top: 0;
bottom: auto;
${i}: 0;
${i==="left"?"right: auto;":"left: auto;"}
width: ${e.sidebarWidth}px;
${n}: 0;
${n==="left"?"right: auto;":"left: auto;"}
width: ${t.sidebarWidth}px;
max-width: 100vw;
height: 100dvh;
max-height: 100dvh;
transform: translateX(${i==="left"?"-100%":"100%"});
box-shadow: ${i==="left"?"":"-"}12px 0 40px rgba(0, 0, 0, 0.12);
transform: translateX(${n==="left"?"-100%":"100%"});
box-shadow: ${n==="left"?"":"-"}12px 0 40px rgba(0, 0, 0, 0.12);
}
#${e.containerId}.active {
#${t.containerId}.active {
transform: translateX(0);
}
#${e.containerId} .replypier-iframe {
#${t.containerId} .replypier-iframe {
border-radius: 0;
}
${h?`
${c?`
/* 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. */
#${e.buttonId}.active {
#${t.buttonId}.active {
display: none;
}
`:`
/* 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
reachable: slide it out beside the drawer edge, one layer up. */
#${e.buttonId} {
z-index: ${e.zIndex+1};
#${t.buttonId} {
z-index: ${t.zIndex+1};
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);
}
#${e.buttonId}.active {
${i}: ${e.sidebarWidth+20}px;
#${t.buttonId}.active {
${n}: ${t.sidebarWidth+20}px;
}
`}
`:""}
@@ -353,7 +353,7 @@
align-items: center;
justify-content: center;
cursor: pointer;
z-index: ${e.zIndex+1};
z-index: ${t.zIndex+1};
transition: all 0.3s ease;
}
@@ -367,7 +367,7 @@
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
z-index: ${e.zIndex};
z-index: ${t.zIndex};
align-items: center;
justify-content: space-between;
padding: 0 20px;
@@ -392,7 +392,7 @@
}
@media (max-width: 768px) {
#${e.containerId} {
#${t.containerId} {
width: 100vw !important;
height: 100vh !important;
height: 100dvh !important; /* Dynamic viewport height for mobile browsers */
@@ -412,7 +412,7 @@
padding: 0 !important;
}
#${e.containerId}.active {
#${t.containerId}.active {
bottom: 0 !important;
}
@@ -428,11 +428,11 @@
padding: 0 !important;
}
#${e.buttonId} {
#${t.buttonId} {
display: none;
}
#${e.buttonId}.mobile-closed {
#${t.buttonId}.mobile-closed {
/* flex (not block) keeps the launcher's centering context; the dots/chevron
are absolutely centered regardless, but this keeps it consistent. */
display: flex !important;
@@ -440,8 +440,8 @@
height: 56px !important;
/* 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). */
bottom: ${e.launcherBottom}px !important;
${i}: ${e.launcherRight}px !important;
bottom: ${t.launcherBottom}px !important;
${n}: ${t.launcherRight}px !important;
}
.ask-anything-mobile #replypier-mobile-close.active {
@@ -460,7 +460,7 @@
.ask-anything-mobile #replypier-mobile-close.active {
top: 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 */
@@ -478,18 +478,18 @@
}
/* Chat Initiation Message Styles */
#${e.initiationMessageId} {
#${t.initiationMessageId} {
position: fixed !important;
/* 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. */
bottom: ${(e.launcherBottom||20)+o+12}px !important;
${i}: ${e.launcherRight||20}px !important;
bottom: ${(t.launcherBottom||20)+r+12}px !important;
${n}: ${t.launcherRight||20}px !important;
max-width: 260px !important;
background: white !important;
padding: 12px 36px 12px 14px !important;
border-radius: 14px !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;
opacity: 0;
visibility: hidden;
@@ -514,7 +514,7 @@
/* 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.
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;
display: -webkit-box !important;
-webkit-line-clamp: 4 !important;
@@ -536,7 +536,7 @@
z-index: 1 !important;
}
@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
off-screen \u2014 mirror it to the bubble's page-facing side. */
.initiation-orb {
@@ -545,7 +545,7 @@
}
`:""}
#${e.initiationMessageId}.show {
#${t.initiationMessageId}.show {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
@@ -566,16 +566,16 @@
}
}
#${e.initiationMessageId}:hover {
#${t.initiationMessageId}:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.14);
}
#${e.initiationMessageId}::after {
#${t.initiationMessageId}::after {
content: '' !important;
position: absolute !important;
bottom: -7px !important;
${i}: 30px !important;
${n}: 30px !important;
width: 14px !important;
height: 14px !important;
min-width: 14px !important;
@@ -627,7 +627,7 @@
content: '|';
animation: blink 1s step-end infinite;
margin-left: 2px;
color: ${e.chatBubbleColor};
color: ${t.chatBubbleColor};
font-weight: 500;
}
@@ -707,7 +707,7 @@
}
@media (max-width: 768px) {
#${e.initiationMessageId} {
#${t.initiationMessageId} {
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
the JS API. Last in the sheet so it also beats the mobile .mobile-closed
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>
<button class="initiation-close" aria-label="\u5173\u95ED"></button>
<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 r2"></span>
<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>
<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">
<path d="M5 12H19" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</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>
<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 is an open-source AI customer-support chatbot platform. You can sign up, create AI
> agents, add knowledge sources, and embed a chat widget — all from a browser, the terminal (CLI),
> or programmatically via an AI agent over MCP.
> ReplyPier 是一个开源的 AI 客服系统:AI 先依据知识库答疑,转人工后在同一个会话里接着处理,
> 服务结束邀请访客评价。仓库包含后端、客服工作台、嵌入 Widget、帮助中心与端到端测试。
## Start here (AI agents & automation)
## 仓库
Use the **ReplyPier CLI** (the `replypier` command, installed with `pip install replypier-cli`)
to sign up and manage everything from the terminal. It is the account/config CLI — distinct from the
self-host Docker tool (`npm install -g replypier-deploy`).
- 源码:http://149.71.241.228:10489/WangShen007/replypier
- 分支:`main`
Minimal end-to-end flow:
## 本机运行
需要 PostgreSQL 16 + pgvector、Redis、Python 3.12 与 Node:
```bash
pip install replypier-cli
replypier signup --name "Acme Inc" --domain acme.com --admin-email you@acme.com --admin-name "You"
replypier agent create --name "Support" --type customer_support -i "Be concise" --json
# 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>
./deploy/local/local.sh setup # 首次运行:安装依赖并构建前端
./deploy/local/local.sh up # 启动数据库、后端与前端
./deploy/local/local.sh status # 查看各服务状态
```
## 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
- Hosted API base: https://api.replypier.site (routes under `/api/v1`)
- Widget script: https://app.replypier.site/webclient/replypier.min.js
- Documentation: https://docs.replypier.site
- GitHub: https://github.com/replypier/replypier.site
```html
<script>window.replypierId = "YOUR_WIDGET_ID";</script>
<script src="https://<你的域名>/webclient/replypier.min.js" async></script>
```
## 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`。