Initial commit: CRM AI Demo bridge + KB knowledge base + infrastructure
- Bridge server with LLM agent tool-use (search_kb, search_products, search_orders, search_inventory, escalate_human) - pgvector RAG knowledge base (95 FAQ chunks) - Auto opportunity creation in Twenty CRM - Auto follow-up task workflow - Chatwoot AgentBot integration (HMAC, webhook) - Docker compose infrastructure (PG18, Redis 8.8, node24-alpine) - Configuration templates (example files) - real secrets excluded via .gitignore
This commit is contained in:
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# 敏感配置文件(含 API keys / tokens / secrets)
|
||||||
|
bridge/chatwoot.config.json
|
||||||
|
bridge/llm.config.json
|
||||||
|
bridge/twenty.config.json
|
||||||
|
bridge/kb.config.json
|
||||||
|
chatwoot/.env
|
||||||
|
twenty/packages/twenty-docker/.env
|
||||||
|
docker-compose.yml
|
||||||
|
|
||||||
|
# 外部源码目录
|
||||||
|
chatwoot/
|
||||||
|
twenty/
|
||||||
|
|
||||||
|
# 运行时生成
|
||||||
|
public/vite-dev/
|
||||||
|
public/widget-assets/
|
||||||
|
public/packs/
|
||||||
|
runtime/
|
||||||
|
tmp/
|
||||||
|
log/
|
||||||
|
|
||||||
|
# 依赖
|
||||||
|
node_modules/
|
||||||
|
vendor/
|
||||||
|
.gems/
|
||||||
|
|
||||||
|
# 测试/临时文件
|
||||||
|
mock_eta.js
|
||||||
|
mock_services.js
|
||||||
|
gen_apikey.js
|
||||||
|
add_note.js
|
||||||
|
find_keys.js
|
||||||
|
create_workflow.js
|
||||||
|
*.bak
|
||||||
|
*.log
|
||||||
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# 已忽略包含查询文件的默认文件夹
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
8
.idea/crm-ai-demo.iml
generated
Normal file
8
.idea/crm-ai-demo.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/crm-ai-demo.iml" filepath="$PROJECT_DIR$/.idea/crm-ai-demo.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/prettier.xml
generated
Normal file
6
.idea/prettier.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PrettierConfiguration">
|
||||||
|
<option name="myConfigurationMode" value="AUTOMATIC" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/vcs.xml
generated
Normal file
7
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/chatwoot" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/twenty" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
将 KB 版本管理 + 原子切换 + 回滚方案输出为设计文档到知识库 `D:\workspace\知识库\05-项目架构文档\CRM-AI-KB版本管理与回滚方案.md`。纯文档输出,不改代码。
|
||||||
114
AGENTS.md
Normal file
114
AGENTS.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# CRM AI Demo - Project Guidelines
|
||||||
|
|
||||||
|
## Project
|
||||||
|
B2B stainless steel jewelry wholesaler (Yehwang, NL) CRM with AI-powered sales/service assistant bridge. Chatwoot webhook → LLM agent (tool-use) → Chatwoot reply + Twenty CRM opportunity creation.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
- **Bridge**: Node.js 24, CommonJS (`"type": "commonjs"`), pure Node built-ins (`http`/`fetch`/`crypto`) + `jose` + `pg`. No Express. No test/lint/build scripts — verify by running the server and hitting `/healthz`.
|
||||||
|
- **LLM**: OpenAI-compatible provider via `bridge/llm.config.json`. Active: `aliyun` → DashScope `qwen-plus-latest` (`enableThinking: false`). `server.js` header comment says "glm-5.2/ark" — **ignore it, trust `llm.config.json`**.
|
||||||
|
- **Messaging**: Chatwoot (self-hosted, external source checkout at `./chatwoot/` — has its own `.git/`).
|
||||||
|
- **CRM**: Twenty CRM (self-hosted, external source checkout at `./twenty/` — may be absent).
|
||||||
|
- **Vector DB**: pgvector on shared `crm-postgres` (pg18). Three DBs: `chatwoot`, `twenty`, `products`. KB RAG table: `products.kb_docs`.
|
||||||
|
- **Product Search**: external recommend API (`RECOMMEND_URL`, default `http://recommend.internal.yw.com`).
|
||||||
|
- **Order/Inventory APIs**: trading-service (`ORDER_API_URL`) and eta-service (`INVENTORY_API_URL`). Mock server available: `node mock_services.js` (port 9505).
|
||||||
|
- **Embedding**: DashScope `text-embedding-v4` (dim 1024), configured in `kb.config.json`. Query translation fallback uses `qwen-turbo`.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
```
|
||||||
|
Chatwoot (external) ──webhook──► Bridge (:4000) ──LLM tool-use──► Chatwoot reply
|
||||||
|
│
|
||||||
|
├── pgvector (products.kb_docs) KB RAG (search_kb)
|
||||||
|
├── recommend API product catalog (search_products)
|
||||||
|
├── trading-service order tracking (search_orders)
|
||||||
|
├── eta-service inventory/stock (search_inventory)
|
||||||
|
└── Twenty CRM GraphQL create Opportunity
|
||||||
|
```
|
||||||
|
|
||||||
|
Bridge routes (`bridge/server.js`):
|
||||||
|
- `GET /healthz` (and `/`) — liveness, returns `agentBot: !!AGENT_BOT_SECRET`
|
||||||
|
- `GET /conversation/:id` and `GET /conversation/:id/html` — debug views
|
||||||
|
- `POST /agent-bot` — Chatwoot AgentBot outgoing webhook (HMAC-signed)
|
||||||
|
- `POST /chatwoot-webhook` — Chatwoot `message_created` webhook (fallback entry)
|
||||||
|
|
||||||
|
Agent tool-use loop: LLM autonomously calls `search_kb`, `search_products`, `search_orders`, `search_inventory`, or `escalate_human`. Single `AGENT_SYSTEM` persona — no prompt switching. Per-conversation lock (`locks` Set) prevents concurrent LLM calls; `createdOpps` Set prevents duplicate Opportunities.
|
||||||
|
|
||||||
|
## Startup
|
||||||
|
```bash
|
||||||
|
# Full stack (requires Docker Desktop):
|
||||||
|
bash start.sh
|
||||||
|
|
||||||
|
# Shared infra only (no Chatwoot/Twenty source needed):
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
# Local dev (no Docker):
|
||||||
|
cd bridge && node server.js # Node 24+; needs PG_URL etc. as env vars
|
||||||
|
|
||||||
|
# Mock business APIs (for dev without real trading/eta services):
|
||||||
|
node mock_services.js # Listens on :9505
|
||||||
|
```
|
||||||
|
|
||||||
|
`start.sh` conditionally starts Twenty (if `./twenty/docker-compose.yaml` exists) and Chatwoot (if `./chatwoot/docker-compose.yml` exists — note: currently runs unconditionally, will error if absent). Shared infra always starts.
|
||||||
|
|
||||||
|
Logs / health:
|
||||||
|
```bash
|
||||||
|
docker logs -f crm-bridge
|
||||||
|
curl http://localhost:4000/healthz
|
||||||
|
```
|
||||||
|
|
||||||
|
**No rebuild needed for code changes** — `docker-compose.yml` volume-mounts `bridge/*.js` and `*.config.json` read-only. `docker restart crm-bridge` picks up edits. Only rebuild the image when changing `package.json` deps.
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
```
|
||||||
|
bridge/
|
||||||
|
server.js Main bridge: routes, LLM agent loop, tool impls, Chatwoot/Twenty API calls (~1250 lines)
|
||||||
|
index_kb.js KB vectorizer: customer-service-kb/*.md → embed → products.kb_docs (DROPS + recreates table each run)
|
||||||
|
register_agent_bot.js Registers Chatwoot AgentBot (idempotent). Run: node register_agent_bot.js
|
||||||
|
llm.config.json LLM provider config. Active provider key: "aliyun"
|
||||||
|
kb.config.json Embedding config (model, dim, translateModel) + DashScope creds
|
||||||
|
chatwoot.config.json Chatwoot API connection (baseURL, accountId, accessToken, inboxId)
|
||||||
|
twenty.config.json Twenty CRM connection (baseURL, workspaceId, apiKey JWT)
|
||||||
|
Dockerfile node:24-alpine, npm install --omit=dev, copies server.js + configs
|
||||||
|
customer-service-kb/ FAQ markdown (source for index_kb.js). Chunks split on ### Q:. English; AI translates per customer language.
|
||||||
|
infra/init-db.sh postgres init: creates DBs chatwoot/twenty/products + enables pgvector on chatwoot & products.
|
||||||
|
docker-compose.yml Shared infra: pgvector/pgvector:pg18 + redis:8-alpine + bridge
|
||||||
|
mock_services.js Mock trading/eta API server (:9505) for dev without real backends
|
||||||
|
add_note.js CLI utility: attach a text note to a Twenty opportunity
|
||||||
|
create_workflow.js CLI utility: create Twenty automation workflow (new opp → auto follow-up task)
|
||||||
|
find_keys.js CLI utility: find Twenty signing key tables in postgres
|
||||||
|
gen_apikey.js CLI utility: generate Twenty API key JWT (needs ENCRYPTION_KEY + PG_DATABASE_URL)
|
||||||
|
landing.html Multilingual landing page
|
||||||
|
start.sh One-shot launcher for all compose stacks
|
||||||
|
docs/twenty-crm-guide.md Twenty CRM integration guide: which GraphQL ops bridge uses, data model, extension ideas
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
Env vars always override `*.config.json` values. Key overrides in `server.js`:
|
||||||
|
- `CW_BASE_URL` — overrides `chatwoot.config.json` baseURL
|
||||||
|
- `TWENTY_BASE_URL` — overrides `twenty.config.json` baseURL
|
||||||
|
- `PG_URL` — postgres connection (default `postgres://postgres:postgres@localhost:5432/products`)
|
||||||
|
- `AGENT_BOT_SECRET` — HMAC signing key for AgentBot webhook verification
|
||||||
|
- `RECOMMEND_URL` — product recommendation API base URL
|
||||||
|
- `ORDER_API_URL` — trading-service base URL (order queries)
|
||||||
|
- `ORDER_API_KEY` — auth key for trading-service
|
||||||
|
- `INVENTORY_API_URL` — eta-service base URL (stock/ETA queries)
|
||||||
|
- `INVENTORY_API_KEY` — auth key for eta-service
|
||||||
|
- `DASHSCOPE_API_KEY` — overrides `kb.config.json` apiKey (avoids hardcoding in config)
|
||||||
|
- `PORT` — bridge listen port (default 4000)
|
||||||
|
|
||||||
|
Docker-compose sets these for the bridge container (using container names for inter-service routing, `host.docker.internal` for host-side services).
|
||||||
|
|
||||||
|
## Conventions & Gotchas
|
||||||
|
- **Config loading**: `loadCfg()` reads `*.config.json` from `__dirname` (the bridge dir). Env vars always win — check `process.env.*` overrides in `server.js` before editing config files.
|
||||||
|
- **KB re-indexing**: `index_kb.js` **DROPS and recreates** `kb_docs` every run (no incremental update). Any model/dimension change requires a full re-vectorize. Run `node index_kb.js` after editing any `customer-service-kb/*.md` file.
|
||||||
|
- **KB doc format**: YAML frontmatter (`title`/`category`/`keywords`/`source_url`) + `### Q:`/answer blocks. Embedding text = `keywords + Q + A`. Source URLs are mapped per-section in `index_kb.js` `SECTION_URL_MAP`.
|
||||||
|
- **Agent persona is the source of truth** for sales policy (discount tiers, minimum order €30, contacts). `AGENT_SYSTEM` in `server.js` encodes business rules; `customer-service-kb/` encodes policy FAQs. Keep them consistent.
|
||||||
|
- **Language rule**: replies must mirror the customer's language, even when tool results are English. Enforced in `AGENT_SYSTEM`.
|
||||||
|
- **Multi-language KB recall**: when cosine similarity < 0.55 and query isn't English, `searchKB` auto-translates to English via `qwen-turbo` and merges results. Rarely triggers — `text-embedding-v4` handles most EU languages well.
|
||||||
|
- **Duplicate `addChatToOpportunity` call**: `server.js` lines ~1086-1088 call this function twice for the same conversation/opportunity. Likely a bug — second call creates a duplicate note.
|
||||||
|
- **`start.sh` Chatwoot unconditional**: the Twenty section checks for `docker-compose.yaml` existence first, but the Chatwoot section runs `docker compose up` unconditionally — will fail if `./chatwoot/` is absent.
|
||||||
|
- **Not a git repo**: the project root has no `.git/`. `./chatwoot/` has its own `.git/` (separate checkout). `./twenty/` may or may not be present.
|
||||||
|
- **Secrets in configs**: `*.config.json` and `docker-compose.yml` contain real API keys/tokens (DashScope, Chatwoot access token, Twenty JWT, `AGENT_BOT_SECRET`). Treat as sensitive; do not commit to public repos.
|
||||||
|
- **Resource limits**: postgres 0.5 CPU/512M, redis 0.3 CPU/256M, bridge 0.5 CPU/512M (in `docker-compose.yml`). LLM/embedding calls are external network — watch latency under load.
|
||||||
|
- **Dockerfile base image**: `node:24-alpine`. The comment says "Node 18+" but `FROM node:24-alpine`. Node 24 has built-in `fetch`/`FormData`/`Blob`.
|
||||||
|
- **Bridge auto-creates follow-up tasks**: after creating an Opportunity, `createLeadInCRM` auto-creates a Task due in 3 days, linked to the opportunity. This is in bridge code, not Twenty's workflow engine (Twenty API key lacks permission to create workflows via GraphQL).
|
||||||
|
- **Opportunity extraction logic**: `EXTRACT_SYSTEM` requires 3 elements to set `ready=true`: company name + contact info (email/phone/name) + product/need. Missing any → `ready=false`, no CRM record created.
|
||||||
11
bridge/Dockerfile
Normal file
11
bridge/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Chatwoot -> LLM -> Twenty CRM 销售桥接
|
||||||
|
# 纯 Node 内置模块 + sharp(图片缩放),需 Node 18+(内置 fetch / FormData / Blob)
|
||||||
|
FROM node:24-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json ./
|
||||||
|
RUN npm install --omit=dev
|
||||||
|
COPY server.js index_kb.js register_agent_bot.js ./
|
||||||
|
COPY chatwoot.config.json llm.config.json twenty.config.json kb.config.json ./
|
||||||
|
ENV PORT=4000
|
||||||
|
EXPOSE 4000
|
||||||
|
CMD ["node", "server.js"]
|
||||||
9
bridge/chatwoot.config.example.json
Normal file
9
bridge/chatwoot.config.example.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"baseURL": "http://crm-chatwoot-rails:3000",
|
||||||
|
"accountId": 1,
|
||||||
|
"accessToken": "YOUR_CHATWOOT_ACCESS_TOKEN",
|
||||||
|
"inboxId": 1,
|
||||||
|
"websiteToken": "j2p6riinXmVWjtmL5dHotf3Z",
|
||||||
|
"webhookId": 1,
|
||||||
|
"webhookURL": "http://crm-bridge:4000/chatwoot-webhook"
|
||||||
|
}
|
||||||
206
bridge/index_kb.js
Normal file
206
bridge/index_kb.js
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
// 客服知识库向量化:读取 customer-service-kb/*.md,按 "### Q:" 问答块切分,
|
||||||
|
// 用 SiliconFlow Qwen3-Embedding-0.6B(1024维) 生成向量,存入共享 postgres
|
||||||
|
// products 库的 kb_docs 表,供 bridge 做 RAG 检索回答客户政策类问题。
|
||||||
|
//
|
||||||
|
// 切块策略(遵循知识库 README 建议):
|
||||||
|
// - 每个文件先解析 YAML frontmatter(title/category/keywords/source)
|
||||||
|
// - 每个 "### Q: ..." 到下一个 "### Q:" 之间为一个 chunk(含问题+答案)
|
||||||
|
// - embedding 文本 = keywords + Q + A,提升召回
|
||||||
|
// - 额外保留 title/category 便于过滤/引用
|
||||||
|
//
|
||||||
|
// 运行: node index_kb.js (PG_URL 默认 postgres://postgres:postgres@localhost:5432/products)
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
|
||||||
|
const EMB = JSON.parse(fs.readFileSync(path.join(__dirname, 'kb.config.json'), 'utf8'));
|
||||||
|
const MODEL = EMB.model || 'text-embedding-v4';
|
||||||
|
const DIM = EMB.dim || 1024;
|
||||||
|
const PG_URL = process.env.PG_URL || 'postgres://postgres:postgres@localhost:5432/products';
|
||||||
|
const pool = new Pool({ connectionString: PG_URL });
|
||||||
|
|
||||||
|
const KB_DIR = path.join(__dirname, '..', 'customer-service-kb');
|
||||||
|
|
||||||
|
// ---------- 解析 frontmatter(支持多行 YAML 数组,如 source_url) ----------
|
||||||
|
function parseFrontmatter(text) {
|
||||||
|
const m = text.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
||||||
|
if (!m) return { meta: {}, body: text };
|
||||||
|
const meta = {};
|
||||||
|
const fm = m[1].split('\n');
|
||||||
|
for (let i = 0; i < fm.length; i++) {
|
||||||
|
const kv = fm[i].match(/^(\w+):\s*(.*)$/);
|
||||||
|
if (!kv) continue;
|
||||||
|
const key = kv[1];
|
||||||
|
let v = kv[2].trim();
|
||||||
|
if (v.startsWith('[')) {
|
||||||
|
// 单行数组 [a, b, c]
|
||||||
|
v = v.replace(/[\[\]]/g, '').split(',').map((s) => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
|
||||||
|
} else if (v === '') {
|
||||||
|
// 多行数组:后续缩进的 - 开头行
|
||||||
|
const arr = [];
|
||||||
|
while (i + 1 < fm.length && /^\s+-\s+/.test(fm[i + 1])) {
|
||||||
|
i++;
|
||||||
|
let item = fm[i].replace(/^\s+-\s+/, '').trim().replace(/^["']|["']$/g, '');
|
||||||
|
if (item) arr.push(item);
|
||||||
|
}
|
||||||
|
v = arr;
|
||||||
|
} else {
|
||||||
|
v = v.replace(/^["']|["']$/g, '');
|
||||||
|
}
|
||||||
|
meta[key] = v;
|
||||||
|
}
|
||||||
|
return { meta, body: m[2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 按 Q: 切块 ----------
|
||||||
|
function splitChunks(body) {
|
||||||
|
// 去掉文件顶部的 "> 引用块"(章节说明),但保留答案里的内容
|
||||||
|
const lines = body.split('\n');
|
||||||
|
const chunks = [];
|
||||||
|
let cur = null;
|
||||||
|
let heading = null; // 文件内 "## " 一级标题(章节名)
|
||||||
|
for (const line of lines) {
|
||||||
|
const h2 = line.match(/^##\s+(.*)$/);
|
||||||
|
if (h2) { heading = h2[1].trim(); continue; }
|
||||||
|
const q = line.match(/^###\s+Q:\s*(.*)$/);
|
||||||
|
if (q) {
|
||||||
|
if (cur) chunks.push(cur);
|
||||||
|
cur = { question: q[1].trim(), answer: '', section: heading || '' };
|
||||||
|
} else if (cur) {
|
||||||
|
cur.answer += line + '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cur) chunks.push(cur);
|
||||||
|
// 清理答案首尾空白
|
||||||
|
return chunks.map((c) => ({ ...c, answer: c.answer.trim() }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 章节 → 来源 URL 映射:dropshipping KB 各章节对应不同 faq 子页,
|
||||||
|
// 避免"物流"问题附上 dropshipping 首页/about-us 等无关链接。
|
||||||
|
const SECTION_URL_MAP = {
|
||||||
|
'Value proposition': ['https://dropshipping.yehwang.com/', 'https://dropshipping.yehwang.com/about-us'],
|
||||||
|
'Joining & requirements': ['https://dropshipping.yehwang.com/faq/register'],
|
||||||
|
'How it works (4 steps)': ['https://dropshipping.yehwang.com/'],
|
||||||
|
'Brands & catalog': ['https://dropshipping.yehwang.com/'],
|
||||||
|
'Pricing & margins': ['https://dropshipping.yehwang.com/'],
|
||||||
|
'Packaging & branding': ['https://dropshipping.yehwang.com/'],
|
||||||
|
'Orders, minimum & products': ['https://dropshipping.yehwang.com/faq/order'],
|
||||||
|
'Payment & VAT': ['https://dropshipping.yehwang.com/faq/payment'],
|
||||||
|
'Shipping & delivery': ['https://dropshipping.yehwang.com/faq/shipment'],
|
||||||
|
'Returns': ['https://dropshipping.yehwang.com/faq/returns'],
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- embedding ----------
|
||||||
|
async function embedText(text) {
|
||||||
|
const resp = await fetch(`${EMB.baseURL}/embeddings`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { Authorization: `Bearer ${EMB.apiKey}`, 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ model: MODEL, input: text, encoding_format: 'float', dimensions: DIM }),
|
||||||
|
});
|
||||||
|
const d = await resp.json();
|
||||||
|
if (!d.data || !d.data[0] || !d.data[0].embedding) {
|
||||||
|
throw new Error('embedText err: ' + JSON.stringify(d).slice(0, 200));
|
||||||
|
}
|
||||||
|
return d.data[0].embedding;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vecStr = (v) => `[${v.join(',')}]`;
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// 1. 建表
|
||||||
|
await pool.query(`CREATE EXTENSION IF NOT EXISTS vector`);
|
||||||
|
await pool.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS kb_docs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
source_file TEXT,
|
||||||
|
title TEXT,
|
||||||
|
category TEXT,
|
||||||
|
section TEXT,
|
||||||
|
question TEXT,
|
||||||
|
answer TEXT,
|
||||||
|
keywords TEXT[],
|
||||||
|
source_urls TEXT[],
|
||||||
|
text_vec vector(${DIM})
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
// 重建表:模型/维度/向量空间变了(0.6B -> v4),旧向量不可复用,直接 DROP 重建
|
||||||
|
await pool.query('DROP TABLE IF EXISTS kb_docs');
|
||||||
|
await pool.query(`
|
||||||
|
CREATE TABLE kb_docs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
source_file TEXT,
|
||||||
|
title TEXT,
|
||||||
|
category TEXT,
|
||||||
|
section TEXT,
|
||||||
|
question TEXT,
|
||||||
|
answer TEXT,
|
||||||
|
keywords TEXT[],
|
||||||
|
source_urls TEXT[],
|
||||||
|
text_vec vector(${DIM})
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
// HNSW 索引:小数据集召回质量优于 ivfflat,且无需调 lists 参数。
|
||||||
|
// 检索用余弦距离(<=>),故显式指定 vector_cosine_ops。
|
||||||
|
await pool.query(`CREATE INDEX kb_docs_vec_idx ON kb_docs USING hnsw (text_vec vector_cosine_ops)`);
|
||||||
|
console.log(`建表完成, model=${MODEL}, dim=${DIM}`);
|
||||||
|
|
||||||
|
// 2. 收集文件(排除 README/index/_source)
|
||||||
|
const files = fs.readdirSync(KB_DIR)
|
||||||
|
.filter((f) => f.endsWith('.md') && /^\d+/.test(f))
|
||||||
|
.sort();
|
||||||
|
console.log(`发现 ${files.length} 个知识库文件: ${files.join(', ')}`);
|
||||||
|
|
||||||
|
let total = 0;
|
||||||
|
for (const file of files) {
|
||||||
|
const raw = fs.readFileSync(path.join(KB_DIR, file), 'utf8');
|
||||||
|
const { meta, body } = parseFrontmatter(raw);
|
||||||
|
const chunks = splitChunks(body);
|
||||||
|
console.log(`\n[${file}] title="${meta.title}" category="${meta.category}" -> ${chunks.length} chunks`);
|
||||||
|
|
||||||
|
for (const ch of chunks) {
|
||||||
|
const keywords = Array.isArray(meta.keywords) ? meta.keywords : [];
|
||||||
|
// 来源 URL:优先用章节级映射(如 dropshipping 各章节对应不同 faq 子页),再兜底文件级
|
||||||
|
let sourceUrls = [];
|
||||||
|
if (ch.section && SECTION_URL_MAP[ch.section]) {
|
||||||
|
sourceUrls = SECTION_URL_MAP[ch.section];
|
||||||
|
} else if (Array.isArray(meta.source_url)) {
|
||||||
|
sourceUrls = meta.source_url.filter((u) => /^https?:\/\//.test(u));
|
||||||
|
}
|
||||||
|
// embedding 文本:keywords + 问题 + 答案,最大化召回
|
||||||
|
const embText = [
|
||||||
|
keywords.length ? keywords.join(' ') : '',
|
||||||
|
ch.question,
|
||||||
|
ch.answer,
|
||||||
|
].filter(Boolean).join('\n');
|
||||||
|
const vec = await embedText(embText);
|
||||||
|
await pool.query(
|
||||||
|
`INSERT INTO kb_docs (source_file, title, category, section, question, answer, keywords, source_urls, text_vec)
|
||||||
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::vector)`,
|
||||||
|
[file, meta.title || '', meta.category || '', ch.section, ch.question, ch.answer, keywords, sourceUrls, vecStr(vec)]
|
||||||
|
);
|
||||||
|
total++;
|
||||||
|
process.stdout.write(` [${total}] Q: ${ch.question.slice(0, 60)}${sourceUrls.length ? ' src=' + sourceUrls.length : ''}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`\n上传完成,共 ${total} 个 chunk`);
|
||||||
|
|
||||||
|
// 3. 测试检索
|
||||||
|
const tq = await embedText('What is the minimum order value? Can I pay by bank transfer?');
|
||||||
|
const ts = await pool.query(
|
||||||
|
`SELECT question, category, 1 - (text_vec <=> $1::vector) AS score
|
||||||
|
FROM kb_docs ORDER BY text_vec <=> $1::vector LIMIT 5`, [vecStr(tq)]
|
||||||
|
);
|
||||||
|
console.log('\n检索 "What is the minimum order value? bank transfer":');
|
||||||
|
ts.rows.forEach((r, i) => console.log(` ${i + 1}. [${r.category}] score=${Number(r.score).toFixed(3)} Q: ${r.question}`));
|
||||||
|
|
||||||
|
const tq2 = await embedText('meine Bestellung stornieren Rückgabe kaputt'); // 德语
|
||||||
|
const ts2 = await pool.query(
|
||||||
|
`SELECT question, category, 1 - (text_vec <=> $1::vector) AS score
|
||||||
|
FROM kb_docs ORDER BY text_vec <=> $1::vector LIMIT 3`, [vecStr(tq2)]
|
||||||
|
);
|
||||||
|
console.log('\n检索(德语) "meine Bestellung stornieren Rückgabe kaputt":');
|
||||||
|
ts2.rows.forEach((r, i) => console.log(` ${i + 1}. [${r.category}] score=${Number(r.score).toFixed(3)} Q: ${r.question}`));
|
||||||
|
|
||||||
|
await pool.end();
|
||||||
|
}
|
||||||
|
main().catch((e) => { console.error('ERR', e.message); process.exit(1); });
|
||||||
8
bridge/kb.config.example.json
Normal file
8
bridge/kb.config.example.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"provider": "dashscope",
|
||||||
|
"baseURL": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||||
|
"apiKey": "YOUR_DASHSCOPE_API_KEY",
|
||||||
|
"model": "text-embedding-v4",
|
||||||
|
"dim": 1024,
|
||||||
|
"translateModel": "qwen-turbo"
|
||||||
|
}
|
||||||
13
bridge/llm.config.example.json
Normal file
13
bridge/llm.config.example.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"providers": {
|
||||||
|
"aliyun": {
|
||||||
|
"id": "aliyun",
|
||||||
|
"displayName": "aliyun",
|
||||||
|
"type": "openai",
|
||||||
|
"baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||||
|
"apiKey": "YOUR_DASHSCOPE_API_KEY",
|
||||||
|
"model": "qwen-plus-latest",
|
||||||
|
"enableThinking": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
172
bridge/package-lock.json
generated
Normal file
172
bridge/package-lock.json
generated
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
{
|
||||||
|
"name": "bridge",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "bridge",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"jose": "^6.2.3",
|
||||||
|
"pg": "^8.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jose": {
|
||||||
|
"version": "6.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
|
||||||
|
"integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.22.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pg/-/pg-8.22.0.tgz",
|
||||||
|
"integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-connection-string": "^2.14.0",
|
||||||
|
"pg-pool": "^3.14.0",
|
||||||
|
"pg-protocol": "^1.15.0",
|
||||||
|
"pg-types": "2.2.0",
|
||||||
|
"pgpass": "1.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"pg-cloudflare": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-cloudflare": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-connection-string": {
|
||||||
|
"version": "2.14.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pg-connection-string/-/pg-connection-string-2.14.0.tgz",
|
||||||
|
"integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.14.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pg-pool/-/pg-pool-3.14.0.tgz",
|
||||||
|
"integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pg-protocol/-/pg-protocol-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/split2": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
bridge/package.json
Normal file
17
bridge/package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "bridge",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"description": "Chatwoot -> LLM -> Twenty CRM sales bridge",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"jose": "^6.2.4",
|
||||||
|
"pg": "^8.22.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
50
bridge/register_agent_bot.js
Normal file
50
bridge/register_agent_bot.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// 注册 Chatwoot AgentBot:把 bridge 接成官方 AI(对标企业版 Captain)。
|
||||||
|
// 1. 创建 AgentBot(name + outgoing_url 指向 bridge 的 /agent-bot)
|
||||||
|
// 2. 关联到 website inbox(set_agent_bot)
|
||||||
|
// 3. 打印 secret(用于 bridge 的 HMAC 签名校验,设到 AGENT_BOT_SECRET 环境变量)
|
||||||
|
//
|
||||||
|
// 运行: node register_agent_bot.js
|
||||||
|
// 幂等:已存在同名 bot 则复用并更新 outgoing_url。
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const cw = JSON.parse(fs.readFileSync(path.join(__dirname, 'chatwoot.config.json'), 'utf8'));
|
||||||
|
|
||||||
|
const BOT_NAME = 'Yehwang AI Bridge';
|
||||||
|
// Chatwoot 容器经 host.docker.internal 访问宿主上的 bridge;本地直连用 localhost
|
||||||
|
const OUTGOING_URL = process.env.AGENT_BOT_OUTGOING_URL || 'http://host.docker.internal:4000/agent-bot';
|
||||||
|
|
||||||
|
const headers = () => ({ 'api_access_token': cw.accessToken, 'Content-Type': 'application/json' });
|
||||||
|
const apiBase = `${cw.baseURL}/api/v1/accounts/${cw.accountId}`;
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// 1. 列出现有 agent_bots,找同名复用
|
||||||
|
const listResp = await fetch(`${apiBase}/agent_bots`, { headers: headers() });
|
||||||
|
const listData = await listResp.json();
|
||||||
|
const existing = (listData.payload || listData || []).find((b) => b.name === BOT_NAME);
|
||||||
|
let bot = existing;
|
||||||
|
const payload = { name: BOT_NAME, description: 'AI sales+service bridge (RAG + tool-use)', outgoing_url: OUTGOING_URL, bot_type: 'webhook' };
|
||||||
|
if (existing) {
|
||||||
|
const r = await fetch(`${apiBase}/agent_bots/${existing.id}`, { method: 'PATCH', headers: headers(), body: JSON.stringify(payload) });
|
||||||
|
bot = await r.json();
|
||||||
|
console.log(`[update] 复用 AgentBot #${existing.id}`);
|
||||||
|
} else {
|
||||||
|
const r = await fetch(`${apiBase}/agent_bots`, { method: 'POST', headers: headers(), body: JSON.stringify(payload) });
|
||||||
|
bot = await r.json();
|
||||||
|
console.log(`[create] 新建 AgentBot #${bot.id}`);
|
||||||
|
}
|
||||||
|
console.log(' bot:', JSON.stringify({ id: bot.id, name: bot.name, outgoing_url: bot.outgoing_url, secret: bot.secret }));
|
||||||
|
|
||||||
|
// 2. 关联到 inbox(set_agent_bot)
|
||||||
|
const inboxId = cw.inboxId;
|
||||||
|
const setResp = await fetch(`${apiBase}/inboxes/${inboxId}/set_agent_bot`, {
|
||||||
|
method: 'POST', headers: headers(), body: JSON.stringify({ agent_bot: bot.id }),
|
||||||
|
});
|
||||||
|
console.log(`[inbox] 关联 inbox ${inboxId} -> ${setResp.status} ${setResp.ok ? 'OK' : 'FAIL'}`);
|
||||||
|
|
||||||
|
// 3. 打印要配置的环境变量
|
||||||
|
console.log('\n=== 配置 bridge 容器环境变量 ===');
|
||||||
|
console.log(`AGENT_BOT_SECRET=${bot.secret}`);
|
||||||
|
console.log('(写入 docker-compose.yml bridge.environment 或 .env,然后 docker compose up -d bridge)');
|
||||||
|
console.log('\nChatwoot 后台「Inbox → 设置 → Bot」应能看到该 AgentBot 已关联。');
|
||||||
|
}
|
||||||
|
main().catch((e) => { console.error('ERR', e.message); process.exit(1); });
|
||||||
1260
bridge/server.js
Normal file
1260
bridge/server.js
Normal file
File diff suppressed because it is too large
Load Diff
6
bridge/twenty.config.example.json
Normal file
6
bridge/twenty.config.example.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"baseURL": "http://localhost:3000",
|
||||||
|
"graphqlPath": "/graphql",
|
||||||
|
"workspaceId": "70a1901c-45ab-4b95-a7f0-de74be819b2e",
|
||||||
|
"apiKey": "YOUR_TWENTY_API_KEY_JWT"
|
||||||
|
}
|
||||||
46
customer-service-kb/00-about-yehwang.md
Normal file
46
customer-service-kb/00-about-yehwang.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
title: About Yehwang — who we are, what we sell, how we ship
|
||||||
|
category: company
|
||||||
|
source: company-profile + catalog snapshot 2026-06-25 + register FAQ
|
||||||
|
source_url:
|
||||||
|
- https://www.yehwang.com/about-us.html
|
||||||
|
- https://www.yehwang.com/register.html
|
||||||
|
- "数仓表 wa_dim.dim_product_full (快照 2026-06-25)"
|
||||||
|
- "内部文档 公司介绍.md.txt"
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [about, company, B2B, wholesale, jewelry, accessories, clothing, Netherlands, fulfillment, brands, Gimme]
|
||||||
|
---
|
||||||
|
|
||||||
|
# About Yehwang
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://www.yehwang.com/about-us.html · 数据 `wa_dim.dim_product_full` · 抓取 2026-06-25
|
||||||
|
|
||||||
|
### Q: Who is Yehwang / what kind of company are you?
|
||||||
|
Yehwang is a Netherlands-based (Dutch-registered) **B2B wholesaler** serving the fashion, wellness & living industries. We supply jewelry, accessories, and clothing to retailers and online sellers, primarily across Europe. We sell through three channels: physical wholesale stores, our B2B wholesale website (yehwang.com), and a dropshipping platform (stock shipped from our Dutch warehouse on behalf of sellers).
|
||||||
|
|
||||||
|
### Q: What products do you sell?
|
||||||
|
We carry a very broad assortment (100,000+ live products). The main categories are:
|
||||||
|
- **Women's jewelry** — earrings, pendants, necklaces, bracelets, rings, piercing jewelry, jewelry sets, and jewelry-making supplies (the largest category by far).
|
||||||
|
- **Accessories** — hair accessories, scarves, bag charms, glasses, phone accessories, bags, hats, makeup bags, socks, keychains.
|
||||||
|
- **Clothing** — women's tops, bottoms, knitwear, denim (our own clothing label is **Gimme**).
|
||||||
|
- **Packing & Display** — wholesale packaging materials and display solutions.
|
||||||
|
- Plus **High-end jewelry**, **Men's jewelry**, **Children's jewelry**, and **Beauty & makeup tools**.
|
||||||
|
|
||||||
|
### Q: What price range are your products in?
|
||||||
|
Prices are wholesale unit prices. Most items fall between roughly **€1.40 and €4.70** per piece (median around €2.80), with clothing typically higher (around €14 median) and high-end jewelry higher still. Exact prices are visible on the website after you log in to your business account.
|
||||||
|
|
||||||
|
### Q: Where do you ship from / what are your fulfillment models?
|
||||||
|
We operate two models:
|
||||||
|
1. **In-stock from the Netherlands** — products held in our Dutch warehouse are shipped across Europe via PostNL or DHL. Fast EU delivery.
|
||||||
|
2. **Factory-Direct** — selected products ship directly from our manufacturing facilities (China) to customers worldwide, removing warehousing/middleman cost. See [07-factory-direct](07-factory-direct.md).
|
||||||
|
|
||||||
|
### Q: What languages and countries do you serve?
|
||||||
|
Our website and catalog are available in multiple European languages (English, Dutch, German, Spanish, French, Italian, Portuguese). Our customer base is concentrated in the Netherlands, France, Belgium, Germany, Spain, Italy, the UK, Portugal, and Greece, but we ship worldwide.
|
||||||
|
|
||||||
|
### Q: What is Gimme?
|
||||||
|
Gimme is Yehwang's own clothing label, known for trendy, stylish designs aligned with the latest fashion trends, ranging from casual to chic.
|
||||||
|
|
||||||
|
### Q: How do I contact customer service?
|
||||||
|
- General / website / self-operated orders: **helpdesk@yehwang.com**, phone **+31 318 668 171**.
|
||||||
|
- Factory-Direct orders: **service@yehwang.com**.
|
||||||
|
Our headquarters (and returns address) is in **Ede, The Netherlands**.
|
||||||
42
customer-service-kb/01-registration-account.md
Normal file
42
customer-service-kb/01-registration-account.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
title: Registration & Account
|
||||||
|
category: registration
|
||||||
|
source: yehwang.com/register.html (official FAQ, verbatim)
|
||||||
|
source_url:
|
||||||
|
- https://www.yehwang.com/register.html
|
||||||
|
- https://www.yehwang.com/other-questions-1987986538
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [register, account, sign up, VAT number, Chamber of Commerce, CoC, individual, password, reset, find a shop, newsletter, images]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Registration & Account
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://www.yehwang.com/register.html · 抓取 2026-06-25
|
||||||
|
|
||||||
|
### Q: Who can register as a customer at Yehwang?
|
||||||
|
Yehwang is a B2B wholesaler serving the fashion, wellness & living industries. A valid VAT number is required **only for Dutch-based businesses**. For all other countries, no VAT number is necessary to open an account.
|
||||||
|
|
||||||
|
### Q: I do not have a company but I want to purchase your products. Is this possible?
|
||||||
|
Yes. A Chamber of Commerce (CoC) number is **only required for businesses based in the Netherlands**. For customers from all other regions there are no restrictions and no minimum order quantity required — you are welcome to place an order as an individual.
|
||||||
|
|
||||||
|
### Q: How do I register an account?
|
||||||
|
Click the **'Register'** button at the top right corner of our website. Fill out all your details and check that the supplied information is correct. If everything looks good, click **'Create account'**.
|
||||||
|
|
||||||
|
### Q: I created an account. Can I start shopping right away?
|
||||||
|
Yes. Once your account is created you can start shopping immediately. You will receive a confirmation email after registration. You can then log in, view all products **with prices**, and manage your information in your account settings.
|
||||||
|
|
||||||
|
### Q: I forgot my password. What now?
|
||||||
|
On the login page select **'Forgotten password'**, then enter the email address where we should send a new temporary password. If you have any problems resetting, contact **helpdesk@yehwang.com** or call **+31 318 668 171**.
|
||||||
|
|
||||||
|
### Q: How do I register for "Find a Shop"?
|
||||||
|
1. Log in to yehwang.com and select **My account** (upper right).
|
||||||
|
2. Click **My shop**.
|
||||||
|
3. Click **Add new Shop**.
|
||||||
|
4. Fill out your details and click **Continue**.
|
||||||
|
If you need help, contact **helpdesk@yehwang.com** or **+31 318 668 171**.
|
||||||
|
|
||||||
|
### Q: How do I register for the newsletter?
|
||||||
|
Use the newsletter sign-up on our website to stay up to date on weekly news, promotions, and new items.
|
||||||
|
|
||||||
|
### Q: May I use your product images?
|
||||||
|
Yes. As a customer you may use all our images, including product images, on-model shots, and images from our Instagram. Images of items you purchased are listed per order and can be downloaded as a .ZIP from your account, or saved individually from the website. If you use our images on social media, we'd love to be tagged.
|
||||||
53
customer-service-kb/02-ordering.md
Normal file
53
customer-service-kb/02-ordering.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
title: Ordering — placing orders, minimum order, quantity discounts, changes
|
||||||
|
category: ordering
|
||||||
|
source: yehwang.com/order.html (official FAQ, verbatim)
|
||||||
|
source_url:
|
||||||
|
- https://www.yehwang.com/order.html
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [order, place order, minimum order, MOQ, 30 euro, combine orders, shipping per order, quantity discount, volume discount, rewards, logo-free, cancel, adjust, voucher, promo code, price drop, sold out, back in stock]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ordering
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://www.yehwang.com/order.html · 抓取 2026-06-25
|
||||||
|
|
||||||
|
### Q: How do I place an order?
|
||||||
|
Log in with your business account. Use **'Add to cart'** to add products to your shopping cart. When done, click the cart (upper right) to review your order. Check the products and your details carefully and update if needed. Select your preferred payment method and finalize. You'll receive an order confirmation by email.
|
||||||
|
|
||||||
|
### Q: What is the minimum order value?
|
||||||
|
There is a **minimum order of €30**. You can order a few items or in bulk — whichever suits you. Great for testing new products.
|
||||||
|
|
||||||
|
### Q: Can I reserve items?
|
||||||
|
Yes, through **pre-orders** you can reserve certain products that we have already re-ordered and are on their way to our warehouse. Pre-orders must always be paid in advance. See [03-pre-order](03-pre-order.md).
|
||||||
|
|
||||||
|
### Q: Can I combine two orders?
|
||||||
|
No. It is not possible to combine two separate orders. Separate shipping costs are charged for each order because packages are processed and shipped separately. Shipping costs cannot be combined.
|
||||||
|
|
||||||
|
### Q: Do you offer quantity discounts (same product)?
|
||||||
|
Yes, larger quantities of the **same product** earn an extra discount:
|
||||||
|
- **5–9 pieces**: +3%
|
||||||
|
- **10–19 pieces**: +5%
|
||||||
|
- **20+ pieces**: +7%
|
||||||
|
These stack with our order-total volume discounts — see [09-membership-rewards](09-membership-rewards.md).
|
||||||
|
|
||||||
|
### Q: How do Yehwang's order rewards work?
|
||||||
|
To reward loyal customers, the platform returns rewards on each order, and rewards can be accumulated. Detailed rules are linked on the order rewards page on our website.
|
||||||
|
|
||||||
|
### Q: Do you have logo-free jewelry?
|
||||||
|
Yes. From mid-2024, Yehwang's new jewelry no longer has logos on the packaging or labels, so you can sell our jewelry directly under your own brand.
|
||||||
|
|
||||||
|
### Q: I didn't receive an order confirmation.
|
||||||
|
You should receive a confirmation immediately after placing the order. First check your spam folder. If it's still missing, your details may have been entered incorrectly — check and correct them in your account. If your details are correct and you still have no confirmation, contact **helpdesk@yehwang.com** and we'll get back to you as soon as possible.
|
||||||
|
|
||||||
|
### Q: Can I cancel or adjust my order after placing it?
|
||||||
|
No. Finalized orders cannot be cancelled. Items in a confirmed order cannot be exchanged, and items cannot be added to a finalized order. Any additions must be placed as a separate order.
|
||||||
|
|
||||||
|
### Q: How do I apply a voucher or promo code?
|
||||||
|
Discounts tied to holidays that apply to all customers (e.g. Black Friday, Daily Deals, Yehwang Day) are applied to your cart automatically. **Personal vouchers and discount codes** are added at the last checkout step — the same page where you select delivery and invoice addresses. (Note: a maximum of one code per order; sale and brand items are excluded — see [04-payment-vat-invoice](04-payment-vat-invoice.md).)
|
||||||
|
|
||||||
|
### Q: I bought an item at full price and it went on discount the next day. Can I get the difference back?
|
||||||
|
No, we do not refund the discount difference. What you can do is reorder the products at the discounted price and return the others, with return costs at your own expense. No exceptions are made.
|
||||||
|
|
||||||
|
### Q: An item is sold out — will it be back soon?
|
||||||
|
Stock varies greatly by product, and unfortunately not every sold-out item can be replenished. Click the **'Back in Stock'** notice button on the product so we can contact you if it returns.
|
||||||
33
customer-service-kb/03-pre-order.md
Normal file
33
customer-service-kb/03-pre-order.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: Pre-order
|
||||||
|
category: pre-order
|
||||||
|
source: yehwang.com/pre-order.html (official FAQ, verbatim)
|
||||||
|
source_url:
|
||||||
|
- https://www.yehwang.com/pre-order.html
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [pre-order, preorder, reserve, out of stock, delivery week, 5% discount, minimum 50 pieces, combine, first order required]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pre-order
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://www.yehwang.com/pre-order.html · 抓取 2026-06-25
|
||||||
|
|
||||||
|
### Q: What is a pre-order?
|
||||||
|
Products that are currently out of stock can be **reserved and paid for in advance** through pre-orders. These products have already been reordered by us and are on their way to our warehouse. From pre-order to delivery it takes **approximately one month** before stock reaches our warehouse and we can dispatch your order.
|
||||||
|
|
||||||
|
### Q: How do I know which products are pre-order products?
|
||||||
|
Pre-order products are marked with a **PRE-ORDER** label on our website. A full overview is on our Pre-order products page.
|
||||||
|
|
||||||
|
### Q: How do I know when my pre-order will be delivered?
|
||||||
|
Each pre-order product shows an **expected delivery week** — the week we expect to receive the products into our warehouse (not the week you receive your order). You can combine products with different delivery weeks in one pre-order, but the gap between the earliest and latest availability **cannot exceed 3 weeks** (e.g. week 40 + week 42 is OK; week 40 + week 43 is not). Orders with mixed delivery weeks ship only after all items have arrived.
|
||||||
|
|
||||||
|
### Q: Why can't I add a pre-order product to my cart?
|
||||||
|
Two reasons:
|
||||||
|
1. **Pre-order products cannot be combined with in-stock items** — they require two separate orders, and the minimum order value applies to each.
|
||||||
|
2. **Pre-order items are only available to existing customers** — if you have never ordered from us before, pre-order items are not visible. Place a regular order first.
|
||||||
|
|
||||||
|
### Q: Can I pre-order items that are (almost) out of stock?
|
||||||
|
Yes. Contact customer service to place a personal pre-order. The **minimum quantity for a personal pre-order is 50 pieces**. Pre-ordering clothing must first be confirmed with our supplier.
|
||||||
|
|
||||||
|
### Q: Why do I get a discount on pre-order items?
|
||||||
|
Pre-order products get a **5% discount** because they haven't arrived in our warehouse yet and you wait a bit longer for shipment. Once they arrive, the price reverts to the regular selling price.
|
||||||
48
customer-service-kb/04-payment-vat-invoice.md
Normal file
48
customer-service-kb/04-payment-vat-invoice.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
title: Payment, VAT & Invoices
|
||||||
|
category: payment
|
||||||
|
source: yehwang.com/payment.html (official FAQ, verbatim)
|
||||||
|
source_url:
|
||||||
|
- https://www.yehwang.com/payment.html
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [payment, iDeal, PayPal, credit card, VISA, Sofort, Giropay, Bancontact, bank transfer, discount code, VAT, tax, VAT number, exemption, invoice, EU VAT, checkout]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Payment, VAT & Invoices
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://www.yehwang.com/payment.html · 抓取 2026-06-25
|
||||||
|
|
||||||
|
### Q: What payment methods do you accept?
|
||||||
|
We accept the following online payment methods:
|
||||||
|
- iDeal
|
||||||
|
- PayPal
|
||||||
|
- Credit card
|
||||||
|
- VISA
|
||||||
|
- Sofort
|
||||||
|
- Giropay
|
||||||
|
- Bancontact
|
||||||
|
|
||||||
|
### Q: Can I pay by bank transfer?
|
||||||
|
No. Payment by bank transfer is not possible. Payment is made through our website using the methods above.
|
||||||
|
|
||||||
|
### Q: To which products can a discount code be applied?
|
||||||
|
Discount codes apply to the **total value of your order**. **Sale items and brand items are excluded.** Each discount code is valid only once, and a **maximum of one code can be used per order**.
|
||||||
|
|
||||||
|
### Q: I registered with a valid VAT number — why am I still charged VAT?
|
||||||
|
Check the following:
|
||||||
|
1. If you are a **Dutch customer** with a delivery address in the Netherlands, you always pay VAT on your orders.
|
||||||
|
2. Check that your VAT number is listed in your **My Account** details. If you have a valid VAT number that is not listed there, contact **helpdesk@yehwang.com**.
|
||||||
|
3. Select your delivery address and check whether VAT is still charged. If it still is, contact **helpdesk@yehwang.com**.
|
||||||
|
|
||||||
|
### Q: My company falls within the VAT exemption regulation. How can I receive an invoice with VAT?
|
||||||
|
If you have a valid VAT number and want to pay VAT on your orders, **contact us before placing your first order**. We will register you correctly in our system so you can start ordering.
|
||||||
|
|
||||||
|
### Q: Why do I need to pay VAT at checkout?
|
||||||
|
If you are from an EU country and did not enter your own VAT number when creating your shipping address, you must pay VAT at checkout. **The rate varies by country.**
|
||||||
|
> ⚠️ 待核对: 官网附有"各国 VAT 税率表",需业务确认后补全到此处。
|
||||||
|
|
||||||
|
### Q: Can I have an invoice adjusted?
|
||||||
|
No. Once your order has been placed, we can no longer change the information on your invoice.
|
||||||
|
|
||||||
|
### Q: I export outside the EU via a Dutch forwarder — how do I get the VAT refunded?
|
||||||
|
You pay the VAT initially. After the order is delivered to your Dutch forwarder, they give you a **proof of export** (confirming the cargo left the Netherlands). Email a copy of that document together with your IBAN to **helpdesk@yehwang.com** and we will refund the VAT as soon as possible.
|
||||||
34
customer-service-kb/05-shipping-delivery.md
Normal file
34
customer-service-kb/05-shipping-delivery.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: Shipping & Delivery (in-stock / NL warehouse)
|
||||||
|
category: shipping
|
||||||
|
source: yehwang.com/shipment.html (official FAQ, verbatim)
|
||||||
|
source_url:
|
||||||
|
- https://www.yehwang.com/shipment.html
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [shipping, delivery, PostNL, DHL, shipping cost, free shipping, dispatch time, cut-off 3pm, track and trace, Brexit, UK, import duties, VAT refund, lost package, transit time]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Shipping & Delivery
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://www.yehwang.com/shipment.html · 抓取 2026-06-25
|
||||||
|
|
||||||
|
> Applies to in-stock orders shipped from our Netherlands warehouse. For items shipped from our factories, see [07-factory-direct](07-factory-direct.md).
|
||||||
|
|
||||||
|
### Q: What shipping options do you offer?
|
||||||
|
For all deliveries we offer two carriers: **PostNL** or **DHL**. The actual shipping rate is shown in your cart during checkout. If your order reaches the **free-shipping threshold** and you select DHL, the PostNL fee is deducted from the DHL fee and you only pay the surcharge.
|
||||||
|
|
||||||
|
### Q: What shipping costs do you charge?
|
||||||
|
Yehwang ships worldwide from the Netherlands via PostNL and DHL. **Shipping cost depends on your destination country and the weight of your package** — each country has its own rates based on distance and local shipping expenses. The exact cost is shown when you select your country during checkout.
|
||||||
|
> ⚠️ 待核对: 官网有按国家/重量的运费表与免邮门槛,需业务确认后补全到此处。
|
||||||
|
|
||||||
|
### Q: When will my order arrive / how fast do you dispatch?
|
||||||
|
Orders received **before 3 PM on business days are shipped within 1–3 business days**. We don't ship on weekends; orders placed Friday after 3 PM ship the following Monday. During clearance periods dispatch may take a little longer. After shipment you receive an email with a **Track and Trace** link. Transit times vary by country — check the carrier's site (via the tracking link) for an up-to-date ETA.
|
||||||
|
|
||||||
|
### Q: Will I receive a track and trace code?
|
||||||
|
Yes — you'll receive a track and trace code by email from PostNL. If you haven't received it, contact **helpdesk@yehwang.com** and include your order number.
|
||||||
|
|
||||||
|
### Q: How does Brexit affect shipping to the UK?
|
||||||
|
We still ship to the UK; Brexit does not stop this. However, on orders over **GBP 135** (excl. VAT and shipping), UK customs may charge import duties and taxes. These costs are paid by the customer — Yehwang does not cover them.
|
||||||
|
|
||||||
|
### Q: My package hasn't been delivered. What now?
|
||||||
|
Contact us at **helpdesk@yehwang.com** with your order number. We'll open an investigation with the carrier (this can take **1–3 business days**). If the carrier declares the package lost, we refund the purchase amount once there is evidence/confirmation from the carrier that it was not delivered.
|
||||||
34
customer-service-kb/06-returns-refunds.md
Normal file
34
customer-service-kb/06-returns-refunds.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: Returns & Refunds (in-stock / NL warehouse)
|
||||||
|
category: returns
|
||||||
|
source: yehwang.com/returns.html (official FAQ, verbatim)
|
||||||
|
source_url:
|
||||||
|
- https://www.yehwang.com/returns.html
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [return, refund, defective, incorrect, missing item, complaint, returns application, store credit, custom product, Ede, return shipping]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Returns & Refunds
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://www.yehwang.com/returns.html · 抓取 2026-06-25
|
||||||
|
|
||||||
|
> Applies to in-stock orders from our NL warehouse. Factory-Direct returns differ — see [07-factory-direct](07-factory-direct.md).
|
||||||
|
|
||||||
|
### Q: I received my order but an item is defective or incorrect. What should I do?
|
||||||
|
Submit a **return request through your account**. We assess the request and, once approved, email you further information. If we confirm a manufacturing defect or wrong delivery, **we reimburse the return shipping cost**. You return the product to our headquarters in **Ede**. In some cases the product need not be returned and we reimburse directly as **store credit** or to your original payment method. Note: you **cannot** create a return request in your account for **discounted items** — instead email customer service.
|
||||||
|
|
||||||
|
### Q: How do I file a complaint / return request?
|
||||||
|
1. Go to **'My orders'** in your Yehwang account and select the order.
|
||||||
|
2. Click **'Returns Application'** and fill in the product quantity and return reason.
|
||||||
|
3. If the item is defective or incorrect, attaching a **photo is mandatory**.
|
||||||
|
After submitting, you can track it under the **'Returns'** section in 'My orders'. We respond by email **within 3 working days**, and the status changes from 'Pending' to 'Approved' or 'Rejected'.
|
||||||
|
|
||||||
|
### Q: An item from my order is missing. What should I do?
|
||||||
|
Go to **'My orders'**, select the order, click **'Returns Application'**, and choose **'Not received'** as the reason. Track it under 'Returns'. We respond within **3 working days**. If approved, **store credit is issued to your account immediately**.
|
||||||
|
|
||||||
|
### Q: Can I return / get a refund for a custom (personalized) product?
|
||||||
|
Customized products are made to your specifications and are personalized/exclusive, so they generally **do not accept unconditional returns or exchanges**. Returns/exchanges are accepted only for:
|
||||||
|
- ✓ Product quality issues (material defects, manufacturing errors, damage, etc.)
|
||||||
|
- ✓ Products significantly different from the order description / customized specifications
|
||||||
|
- ✓ Damage caused during shipping
|
||||||
|
Please read the full custom-product return policy carefully before ordering.
|
||||||
51
customer-service-kb/07-factory-direct.md
Normal file
51
customer-service-kb/07-factory-direct.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
title: Factory-Direct (ships from our factories worldwide)
|
||||||
|
category: factory-direct
|
||||||
|
source: yehwang.com/factory-direct.html (official FAQ, verbatim)
|
||||||
|
source_url:
|
||||||
|
- https://www.yehwang.com/factory-direct.html
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [factory direct, factory-direct, wholesale pricing, MOQ, custom, processing time, transit time, express, standard shipping, customs, tracking, missing items, quality, all sales final, 7 days]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Factory-Direct
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://www.yehwang.com/factory-direct.html · 抓取 2026-06-25
|
||||||
|
|
||||||
|
> Factory-Direct orders ship **directly from our manufacturing facilities** (not the NL warehouse) to customers worldwide. Contact for Factory-Direct: **service@yehwang.com**.
|
||||||
|
|
||||||
|
### Q: What is the Factory-Direct advantage?
|
||||||
|
We ship your order directly from our manufacturing facilities to your location. By removing the middleman and domestic warehousing, we offer: **wholesale pricing** (cost savings passed to you), **exclusive access** (first to see newest designs), and **quality oversight** (direct handling from production line to packaging).
|
||||||
|
|
||||||
|
### Q: What products are available via Factory-Direct?
|
||||||
|
Three main categories:
|
||||||
|
- **Finished Jewelry & Accessories** — earrings, necklaces, rings, bracelets, jewelry sets, plus hair accessories, phone accessories, bags, and trending apparel.
|
||||||
|
- **DIY & Jewelry Making** — beads, pendants, and raw materials/components.
|
||||||
|
- **Packing & Display** — packaging and display solutions.
|
||||||
|
|
||||||
|
### Q: Is there a Minimum Order Quantity (MOQ)?
|
||||||
|
- **Standard inventory**: most jewelry, accessories, and DIY materials start from **just 1 piece**; the specific MOQ per item is listed in its product details.
|
||||||
|
- **Custom products**: MOQ varies by design complexity and materials. Email **service@yehwang.com** with your design ideas, preferred materials, and estimated quantity — we provide a tailored quote and MOQ requirement **within 24 hours**.
|
||||||
|
|
||||||
|
### Q: What are Factory-Direct shipping times and rates?
|
||||||
|
Delivery has two stages:
|
||||||
|
- **Processing time**: 1–3 business days (quality inspection, sorting, packaging).
|
||||||
|
- **Transit time**: Express shipping **5–10 business days**; Standard shipping **10–20 business days**.
|
||||||
|
These are estimates; high-volume seasons or custom orders may need more time. Shipping cost for customized/bulk packaging orders is calculated at checkout by weight. You'll receive a tracking number/link by email once dispatched (allow 24–48 hours for it to update).
|
||||||
|
|
||||||
|
### Q: Will I pay customs/duties on Factory-Direct orders?
|
||||||
|
Since items travel internationally from our factories, customs handling applies per destination. (Tracking and customs information is provided with your shipment.)
|
||||||
|
|
||||||
|
### Q: I haven't received my Factory-Direct order. What now?
|
||||||
|
First: check your tracking link, check "hidden" delivery spots (neighbors, building manager, safe places), and wait 24 hours (carriers sometimes mark "Delivered" early). If tracking hasn't updated within **3 business days (or 7 days for international orders)** after the expected delivery date, contact **service@yehwang.com**.
|
||||||
|
|
||||||
|
### Q: My Factory-Direct order is missing items. What should I do?
|
||||||
|
Check the packaging carefully (small jewelry/DIY items are often tucked into protective pouches or inside larger items), and verify the packing slip for backordered or separately-shipped items. If still missing, email **service@yehwang.com within 48 hours** with your order number, a photo of the shipping label and box contents, and the name of the missing item.
|
||||||
|
|
||||||
|
### Q: How do you handle quality issues on Factory-Direct orders?
|
||||||
|
Every item passes a quality check before packing. If you receive a defect, **report within 7 days** of delivery and email **service@yehwang.com** with your order number and a clear photo (e.g. missing stones, chipped coating, defective DIY components). Depending on the case we provide a prepaid return label for a replacement or a full refund.
|
||||||
|
|
||||||
|
### Q: What is the Factory-Direct return & exchange policy?
|
||||||
|
Due to the cost and complexity of international shipping and customs, **all sales are final** — we do not accept returns or exchanges for non-quality reasons (e.g. change of mind). However:
|
||||||
|
- **Defective or damaged items**: notify within **7 days** for a full refund or free replacement.
|
||||||
|
- **Incorrect orders** (wrong item, color, or size): we correct it immediately.
|
||||||
139
customer-service-kb/08-dropshipping.md
Normal file
139
customer-service-kb/08-dropshipping.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
---
|
||||||
|
title: Dropshipping service (dropshipping.yehwang.com)
|
||||||
|
category: dropshipping
|
||||||
|
source: dropshipping.yehwang.com — homepage + about-us + FAQ (order/payment/shipment/returns), scraped 2026-06-25
|
||||||
|
source_url:
|
||||||
|
- https://dropshipping.yehwang.com/
|
||||||
|
- https://dropshipping.yehwang.com/about-us
|
||||||
|
- https://dropshipping.yehwang.com/faq/register
|
||||||
|
- https://dropshipping.yehwang.com/faq/order
|
||||||
|
- https://dropshipping.yehwang.com/faq/payment
|
||||||
|
- https://dropshipping.yehwang.com/faq/shipment
|
||||||
|
- https://dropshipping.yehwang.com/faq/returns
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [dropshipping, Shopify, Shopify app, partner, apply, approval, no minimum order, blind shipping, neutral packaging, private label, house brands, Yehwang, Gimme, Lilli June, Lumé, Club Spice, margin, wholesale, 24h dispatch, Netherlands, REACH, nickel, returns, uninstall, VAT, PostNL, DHL]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dropshipping (dropshipping.yehwang.com)
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://dropshipping.yehwang.com/ · /about-us · /faq/{order,payment,shipment,returns} · 抓取 2026-06-25
|
||||||
|
|
||||||
|
Yehwang's dropshipping platform ("The European Standard") lets online sellers sell Yehwang's fashion & jewelry **without holding inventory** — stock sits in our **Netherlands warehouse**, and when a customer orders we automatically process, pack, and ship on the seller's behalf via a native **Shopify** integration. Contact: **dropshipping@yehwang.com** (or **service@yehwang.com** for order/payment/shipping support).
|
||||||
|
|
||||||
|
> Note: This is a separate service from the main B2B wholesale site (yehwang.com). Policies differ — most importantly, **dropshipping has no minimum order**, whereas the B2B site has a €30 minimum.
|
||||||
|
|
||||||
|
## Value proposition
|
||||||
|
|
||||||
|
### Q: What is Yehwang Dropshipping / why use it?
|
||||||
|
We are a B2B jewelry & fashion wholesale platform specializing in the European market. With dropshipping you sell without stocking inventory: sync our products to your Shopify store, and we handle the rest — when a customer orders we automatically process, pack, and ship it. Key benefits:
|
||||||
|
- **In-stock in the EU** — all products ship from our Netherlands warehouse with fast European delivery (no waiting weeks for shipments from China).
|
||||||
|
- **High margins** — quality products at wholesale prices.
|
||||||
|
- **Automatic sync** — products, inventory, and orders sync in real time with Shopify.
|
||||||
|
- **Zero inventory pressure** — no pre-orders, no stock to hold; whether a customer buys 1 or 100 items, we ship directly.
|
||||||
|
- **Guaranteed quality** — all metal jewelry complies with European regulations and passes REACH nickel-release testing (hypoallergenic).
|
||||||
|
- **For all seller levels** — beginner-friendly with step-by-step guides; also built for influencers/brands and scalable operations with API integration.
|
||||||
|
|
||||||
|
### Q: How does Yehwang compare to China dropshipping or Spocket?
|
||||||
|
- **Shipping time:** China 15–30 days; Spocket/others 4–10 days; **Yehwang 1–2 days within the NL/EU.**
|
||||||
|
- **Packaging:** one consolidated box (vs multiple boxes / inconsistent).
|
||||||
|
- **Returns:** 7-day no-reason (vs near impossible / complicated).
|
||||||
|
- **Quality control:** EU standards.
|
||||||
|
- **New arrivals:** 1,000+ new styles per month.
|
||||||
|
- **New-collection protection:** 6-week retail-first window (others have none).
|
||||||
|
|
||||||
|
## Joining & requirements
|
||||||
|
|
||||||
|
### Q: Who can join / how do I apply to dropship?
|
||||||
|
You apply through the partner application form on dropshipping.yehwang.com (steps: Business → Experience → Brands → Review). Required business details include name, business email, store/company, country, live store URL, and business type (e-commerce store, social commerce, marketplace seller, or multi-channel). Applications are **reviewed within 2–3 business days**.
|
||||||
|
|
||||||
|
### Q: Do I need dropshipping experience to be approved?
|
||||||
|
The partner programme is a **vetted programme aimed at experienced dropshippers** ready to generate consistent volume (the application asks about your experience). At the same time, the platform markets itself as beginner-friendly with onboarding guides and "no threshold to get started."
|
||||||
|
> ⚠️ 待核对: 官网两处口径不一(合伙申请页强调"经验丰富/6个月+",about-us 说"无门槛适合所有卖家")。请业务确认实际准入门槛,再统一此条表述。
|
||||||
|
|
||||||
|
### Q: How do I connect my Shopify store?
|
||||||
|
Link your store securely in minutes via the **Yehwang Dropshipping App on the Shopify App Store** — the setup is frictionless. After connecting, configure your margin multipliers, brand focus, and collections, then sync products live.
|
||||||
|
|
||||||
|
### Q: Is uninstalling the Yehwang Dropshipping app reversible?
|
||||||
|
> ⚠️ 待补: 官网此条答案在抓取时未能展开,需到 dropshipping.yehwang.com/faq/register 人工确认后补全。
|
||||||
|
|
||||||
|
### Q: I forgot my password / can I register without a company?
|
||||||
|
Registration rules mirror the main site: a Chamber of Commerce and VAT number are only required for **Netherlands-based** businesses; customers elsewhere can register without a company. For password resets use 'Forgotten password' on the login page, or contact **service@yehwang.com**.
|
||||||
|
|
||||||
|
## How it works (4 steps)
|
||||||
|
|
||||||
|
### Q: How does the dropshipping process work end to end?
|
||||||
|
1. **Apply & get approved** — submit the application; reviewed within 2–3 business days.
|
||||||
|
2. **Connect your Shopify** — link your store via the Yehwang Dropshipping App.
|
||||||
|
3. **Set your preferences** — configure margin multipliers, brand focus, and collections; pick New In and Bestsellers.
|
||||||
|
4. **Sync & start selling** — push products live; orders flow back to us automatically and we pick, pack, and ship. Orders are shipped after the customer's payment.
|
||||||
|
|
||||||
|
## Brands & catalog
|
||||||
|
|
||||||
|
### Q: What brands can I dropship?
|
||||||
|
Five house brands, each for a different customer profile:
|
||||||
|
- **Yehwang** — Core Collection: 20+ years of wholesale expertise; timeless jewelry, clothing & accessories at accessible prices.
|
||||||
|
- **Gimme** — Lifestyle clothing: playful, layered, trend-led silhouettes.
|
||||||
|
- **Lilli June** — Charm jewelry: charm-forward, mix-and-match stackable pieces.
|
||||||
|
- **Lumé** — Fine jewelry: elevated essentials in refined finishes, gifting-worthy.
|
||||||
|
- **Club Spice** — Statement jewelry: bold, vibrant pieces.
|
||||||
|
|
||||||
|
### Q: What product range / how many products are available?
|
||||||
|
A complete lifestyle catalog spanning **50+ core categories** (fashion, lifestyle, creative products) — jewelry, clothing (e.g. maxi dresses, summer clothes), and accessories (bags, scarves). The platform advertises **1,000+ new styles per month**.
|
||||||
|
> ⚠️ 待核对: 官网对在售规模口径不一(首页"30,000+ items"、about-us"over 1 million in-stock items"、order FAQ"10,000+ SKUs")。请确认对客户统一说法。
|
||||||
|
|
||||||
|
## Pricing & margins
|
||||||
|
|
||||||
|
### Q: What does it cost and what margins can I expect?
|
||||||
|
Wholesale prices start from **€1.25 per unit**; suggested retail (MSRP) is typically **2.5×–3.5× wholesale**, with custom margin multipliers configurable in the app and VAT-friendly pricing for EU sellers. Advertised wholesale margins are **up to ~65%** across the 5 house brands (average gross margin across categories ~63%). Example: retail €49.99 − €14.50 wholesale − €3.95 shipping = **€31.54 profit** per unit. Actual results depend on product selection, platform fees, and pricing strategy.
|
||||||
|
|
||||||
|
## Packaging & branding
|
||||||
|
|
||||||
|
### Q: Will my customer know the order came from Yehwang? (blind shipping)
|
||||||
|
No. All packages are shipped in **unbranded, neutral packaging** with no Yehwang identifiers, so customers receive items as if shipped directly from your store. If you have your own brand logo, contact us.
|
||||||
|
|
||||||
|
### Q: Do you support private label / custom packaging?
|
||||||
|
Yes — we offer **private label and custom packaging** services (a minimum order quantity applies). Contact our sales team for a tailored solution. New collections become available to dropshipping partners **6 weeks after** they go live on www.yehwang.com (retail-first window).
|
||||||
|
|
||||||
|
## Orders, minimum & products
|
||||||
|
|
||||||
|
### Q: Is there a minimum order for dropshipping?
|
||||||
|
**No minimum order quantity** — you can order a single item or in bulk, whichever suits you. (This differs from the main B2B site's €30 minimum.)
|
||||||
|
|
||||||
|
### Q: Which products support dropshipping?
|
||||||
|
The platform spans 50+ core categories specializing in fashion, lifestyle, and creative products, with a curated selection of SKUs supporting diverse models — e-commerce retail, cross-border distribution, and boutique stores.
|
||||||
|
|
||||||
|
## Payment & VAT
|
||||||
|
|
||||||
|
### Q: Can I pay by bank transfer?
|
||||||
|
No. Payment is made through the website; bank transfer is not available.
|
||||||
|
|
||||||
|
### Q: I have a valid VAT number but am still charged VAT — why?
|
||||||
|
1. If you're a Dutch customer with a Netherlands delivery address, VAT is always charged.
|
||||||
|
2. Check that your VAT number is listed in your My Account details; if it's valid but not listed, contact **service@yehwang.com**.
|
||||||
|
3. Select your delivery address and check whether VAT is still charged; if so, contact **service@yehwang.com**.
|
||||||
|
|
||||||
|
### Q: My company falls within the VAT exemption regulation. How do I get an invoice with VAT?
|
||||||
|
If you have a valid VAT number and want to pay VAT on your orders, contact us **before placing your first order** so we can register you correctly.
|
||||||
|
|
||||||
|
## Shipping & delivery
|
||||||
|
|
||||||
|
### Q: What are dropshipping shipping costs and carriers?
|
||||||
|
Orders ship worldwide via **PostNL and DHL** from the Netherlands. Shipping cost depends on the **destination country and package weight**, shown when you select the country at checkout.
|
||||||
|
> ⚠️ 待核对: 各国运费表官网有,抓取未含,需业务补全。
|
||||||
|
|
||||||
|
### Q: When will the order arrive?
|
||||||
|
Orders received **before 3 PM on business days ship within 1–3 business days** (24h dispatch on confirmed orders is advertised). We don't ship on weekends; Friday-after-3-PM orders ship the following Monday. After dispatch you receive an email with a Track & Trace link; transit time varies by country.
|
||||||
|
|
||||||
|
### Q: Will I receive a track and trace code?
|
||||||
|
Yes — by email from PostNL. If you don't receive it, contact **service@yehwang.com** with your order number.
|
||||||
|
|
||||||
|
### Q: How does Brexit affect shipping to the UK?
|
||||||
|
We still ship to the UK. On orders over **GBP 135** (excl. VAT and shipping), UK customs may charge import duties/taxes, paid by the customer — Yehwang does not cover these.
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
|
||||||
|
### Q: What is the dropshipping returns policy?
|
||||||
|
A **7-day no-reason** returns policy applies.
|
||||||
|
|
||||||
|
### Q: How do I file a complaint / return?
|
||||||
|
Go to **'My orders'** in your account, select the order, click **'Returns Application'**, and enter the quantity and reason. For a defective or incorrect item, **attaching a photo is mandatory**. Track it in the **'Returns'** section; we reply by email **within 3 working days**, and the status changes from 'Pending' to 'Approved' or 'Rejected'.
|
||||||
57
customer-service-kb/09-membership-rewards.md
Normal file
57
customer-service-kb/09-membership-rewards.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
title: Membership tiers, volume discounts & rewards
|
||||||
|
category: membership
|
||||||
|
source: yehwang.com/membership (verbatim) + order.html + catalog snapshot 2026-06-25
|
||||||
|
source_url:
|
||||||
|
- https://www.yehwang.com/membership-307802756
|
||||||
|
- https://www.yehwang.com/order.html
|
||||||
|
- "数仓表 wa_dim.dim_user_full (快照 2026-06-25)"
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [membership, loyalty, tier, level, Bronze, Silver, Gold, Platinum, Diamond, VIP, discount, volume discount, quantity discount, rewards, spending threshold, upgrade]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Membership, Volume Discounts & Rewards
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://www.yehwang.com/membership-307802756 · https://www.yehwang.com/order.html · 数据 `wa_dim.dim_user_full` · 抓取 2026-06-25
|
||||||
|
|
||||||
|
### Q: What membership tiers do you have and how do I level up?
|
||||||
|
Membership has six levels based on **cumulative historical spending** (excluding VAT and shipping). As soon as you hit a new threshold, your account is **automatically upgraded**. Each tier adds a member-bonus discount:
|
||||||
|
|
||||||
|
| Tier | Cumulative spend | Member bonus |
|
||||||
|
|---|---|---|
|
||||||
|
| Bronze | €1+ | 0% |
|
||||||
|
| Silver | €200+ | 1% |
|
||||||
|
| Gold | €1,000+ | 2% |
|
||||||
|
| Platinum | €4,000+ | 3% |
|
||||||
|
| Diamond | €10,000+ | 4% |
|
||||||
|
| VIP | €20,000+ | 5% |
|
||||||
|
|
||||||
|
(New accounts start as a New Member before reaching Bronze. Some special accounts — e.g. key accounts ("KA") — may have custom arrangements.)
|
||||||
|
|
||||||
|
### Q: What volume discounts do you offer (per order total)?
|
||||||
|
Volume discounts are based on the **total value of a single order**:
|
||||||
|
|
||||||
|
| Order total | Volume discount |
|
||||||
|
|---|---|
|
||||||
|
| €200+ | 5% |
|
||||||
|
| €500+ | 7% |
|
||||||
|
| €1,000+ | 10% |
|
||||||
|
| €2,000+ | 15% |
|
||||||
|
| €3,000+ | 20% |
|
||||||
|
| €5,000+ | 22% |
|
||||||
|
| €10,000+ | 25% |
|
||||||
|
|
||||||
|
### Q: How do the discounts stack?
|
||||||
|
The system applies discounts **sequentially**: the **volume discount** (based on order total) is applied first, then your **member bonus** (0–5% by tier) is applied on top, to maximize your savings.
|
||||||
|
|
||||||
|
### Q: Do you offer quantity discounts on the same product?
|
||||||
|
Yes — in addition to the order-total volume discount, ordering more of the **same product** earns extra:
|
||||||
|
- **5–9 pieces**: +3%
|
||||||
|
- **10–19 pieces**: +5%
|
||||||
|
- **20+ pieces**: +7%
|
||||||
|
|
||||||
|
### Q: How do order rewards work?
|
||||||
|
To reward loyal customers, the platform returns rewards on each order, which can be **accumulated**. Detailed rules are on the order rewards page on our website.
|
||||||
|
|
||||||
|
### Q: Is there a welcome discount for new members?
|
||||||
|
Yes — new registrants can receive a welcome discount (advertised as **up to 15% off**) on early orders.
|
||||||
47
customer-service-kb/10-products-and-jewelry-care.md
Normal file
47
customer-service-kb/10-products-and-jewelry-care.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
title: Products catalog, materials & jewelry care
|
||||||
|
category: products
|
||||||
|
source: yehwang.com/other-questions (verbatim) + catalog snapshot 2026-06-25
|
||||||
|
source_url:
|
||||||
|
- https://www.yehwang.com/other-questions-1987986538
|
||||||
|
- "数仓表 wa_dim.dim_product_full (快照 2026-06-25)"
|
||||||
|
last_updated: 2026-06-25
|
||||||
|
keywords: [catalog, categories, price range, materials, stainless steel, gold plated, 304, 316L, nickel free, discolor, waterproof, jewelry care, selling price, minimum price, 300% margin, Gimme]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Products, Materials & Jewelry Care
|
||||||
|
|
||||||
|
> 📎 来源 / Source: https://www.yehwang.com/other-questions-1987986538 · 数据 `wa_dim.dim_product_full` · 抓取 2026-06-25
|
||||||
|
|
||||||
|
## Catalog overview (live products, snapshot 2026-06-25)
|
||||||
|
|
||||||
|
| Category | Live products | Typical price (EUR) |
|
||||||
|
|---|---|---|
|
||||||
|
| Women's jewelry | ~72,800 | median ~€2.75 (€0–233) |
|
||||||
|
| Accessories | ~24,000 | median ~€2.27 |
|
||||||
|
| Clothes | ~5,000 | median ~€13.95 |
|
||||||
|
| Packing & Display | ~1,600 | median ~€1.00 |
|
||||||
|
| Other / Sale jewelry | ~970 | median ~€4.00 |
|
||||||
|
| Sports & Outdoor | ~670 | median ~€10.40 |
|
||||||
|
| Beauty & Makeup tools | ~410 | median ~€2.00 |
|
||||||
|
| High-end jewelry | ~360 | median ~€22.50 |
|
||||||
|
| Men's jewelry | ~170 | median ~€5.95 |
|
||||||
|
|
||||||
|
Top sub-categories: Earrings, Pendants, Necklaces, Bracelets, Piercing jewelry, Rings, Hair accessories, Jewelry-making supplies, Scarves, Bag charms, Glasses, Women's tops, Phone accessories, Bags.
|
||||||
|
|
||||||
|
Most-used materials: **Stainless Steel** (by far the largest), Polyester, Acrylic, Natural Stones, Titanium Alloy, PVC, Raffia.
|
||||||
|
|
||||||
|
### Q: What materials are your jewelry made of?
|
||||||
|
We use a variety of materials; we particularly favor **gold-plated metal** and **stainless steel** jewelry. The gold layer on top is **2.5 microns thick**, in 14K yellow, white, or rose gold (jewelry made before 2020 used an 18K gold layer). This layer is also applied over stainless steel; the color depends on the gold plating. Our stainless steel is **304 or 316L**. **All our jewelry is nickel free.**
|
||||||
|
|
||||||
|
### Q: Does your jewelry discolor?
|
||||||
|
**Stainless steel** jewelry is **waterproof and does not discolor** — you can shower, swim, and spray perfume on it. Other materials such as **silver 925, alloy, and copper are not waterproof** and may discolor with daily use; we do not guarantee against discoloration of silver 925, alloy, and copper.
|
||||||
|
|
||||||
|
### Q: Can I keep wearing the jewelry while playing sports or sleeping?
|
||||||
|
Although our jewelry is durable, we advise **not** wearing it while showering, sleeping, or during sports. To care for it, avoid contact with water, hairspray, and body lotion. In some cases your skin's pH can negatively affect the jewelry and cause discoloration.
|
||||||
|
|
||||||
|
### Q: Am I free to set my own selling prices?
|
||||||
|
Each product has a **minimum selling price** described on its page; you are expected to maintain this and not undercut it. We use a **300% margin** to determine the suggested selling price.
|
||||||
|
|
||||||
|
### Q: Do you offer logo-free / white-label jewelry?
|
||||||
|
Yes — Yehwang's new jewelry from mid-2024 onward has **no logos** on packaging or labels, so you can sell it under your own brand. (See also [02-ordering](02-ordering.md).)
|
||||||
54
customer-service-kb/README.md
Normal file
54
customer-service-kb/README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Yehwang 客服知识库 (Customer Service Knowledge Base)
|
||||||
|
|
||||||
|
面向 **Chatwoot + ai-bridge + Twenty** AI 客服系统的知识库。内容为英文(客户群体为欧洲 B2B 零售商/店主,官方 FAQ 原文亦为英文),AI 在回复时按客户语言翻译即可。
|
||||||
|
|
||||||
|
## 内容来源(均为权威来源,非编造)
|
||||||
|
|
||||||
|
每个 md 文件都在 frontmatter 的 `source_url` 字段、以及正文标题下的「📎 来源 / Source」行里标注了**确切来源地址**,可点击核对/更新。汇总:
|
||||||
|
|
||||||
|
| 来源地址 | 对应文件 |
|
||||||
|
|---|---|
|
||||||
|
| https://www.yehwang.com/about-us.html | 00-about |
|
||||||
|
| https://www.yehwang.com/register.html | 00-about, 01-registration |
|
||||||
|
| https://www.yehwang.com/order.html | 02-ordering, 09-membership |
|
||||||
|
| https://www.yehwang.com/pre-order.html | 03-pre-order |
|
||||||
|
| https://www.yehwang.com/payment.html | 04-payment |
|
||||||
|
| https://www.yehwang.com/shipment.html | 05-shipping |
|
||||||
|
| https://www.yehwang.com/returns.html | 06-returns |
|
||||||
|
| https://www.yehwang.com/factory-direct.html | 07-factory-direct |
|
||||||
|
| https://www.yehwang.com/other-questions-1987986538 | 01-registration, 10-products |
|
||||||
|
| https://www.yehwang.com/membership-307802756 | 09-membership |
|
||||||
|
| https://dropshipping.yehwang.com/ · /about-us · /faq/{register,order,payment,shipment,returns} | 08-dropshipping |
|
||||||
|
| 数仓表 `wa_dim.dim_product_full` / `wa_dim.dim_user_full`(快照 2026-06-25) | 00-about, 09-membership, 10-products |
|
||||||
|
| 内部文档 `公司介绍.md.txt`(业务背景) | 00-about |
|
||||||
|
|
||||||
|
> 官网 FAQ 答案为 JS 折叠加载,用 Playwright 渲染后抓取;原始抓取数据存档于 `_source/`。
|
||||||
|
|
||||||
|
## 文件结构(一主题一文件,问答块切分,适配 RAG)
|
||||||
|
|
||||||
|
- `index.md` — 导航
|
||||||
|
- `00-about-yehwang.md` — 公司是谁、卖什么、两种履约模式
|
||||||
|
- `01-registration-account.md` — 注册 / 账号 / 找店 / 图片授权
|
||||||
|
- `02-ordering.md` — 下单 / 起订 €30 / 数量折扣 / 改单取消 / 优惠码
|
||||||
|
- `03-pre-order.md` — 预购规则
|
||||||
|
- `04-payment-vat-invoice.md` — 支付方式 / VAT / 发票
|
||||||
|
- `05-shipping-delivery.md` — PostNL/DHL 物流 / 运费 / 时效 / Brexit
|
||||||
|
- `06-returns-refunds.md` — 退换货 / 缺件 / 残次 / 定制品
|
||||||
|
- `07-factory-direct.md` — 工厂直发(中国直发全球)
|
||||||
|
- `08-dropshipping.md` — 一件代发(Shopify 接入)
|
||||||
|
- `09-membership-rewards.md` — 会员等级 / 阶梯量折扣 / 奖励
|
||||||
|
- `10-products-and-jewelry-care.md` — 品类盘 / 价格带 / 材质 / 珠宝护理
|
||||||
|
|
||||||
|
## 给 ai-bridge 的接入建议
|
||||||
|
|
||||||
|
1. **切块**:每个 `### Q:` 问答块作为一个 chunk(已天然分段)。frontmatter 里的 `keywords` 可并入 embedding 文本提升召回。
|
||||||
|
2. **向量化**:数仓 `.env.local` 已含 `DASHSCOPE_API_KEY`(`text-embedding-v3`,多语言),可直接复用,对欧洲多语种问法召回友好。
|
||||||
|
3. **system prompt 约束**:要求 AI 仅依据知识库作答;涉及金额/时效/政策时引用具体条款;无法回答时转人工(`helpdesk@yehwang.com` / `service@yehwang.com`)。
|
||||||
|
4. **联系方式**:自营/网站业务 `helpdesk@yehwang.com`、电话 `+31 318 668 171`;工厂直发 `service@yehwang.com`。
|
||||||
|
|
||||||
|
## 维护
|
||||||
|
|
||||||
|
- 政策类内容随官网 FAQ 变化需同步;商品/等级类数据可定期从数仓刷新(脚本见 `scratchpad` 或重跑聚合)。
|
||||||
|
- 标 `⚠️ 待核对` 的条目为需业务确认后补全的细节(如各国运费表、各国 VAT 税率表)。
|
||||||
|
|
||||||
|
_最后更新:2026-06-25_
|
||||||
25
customer-service-kb/index.md
Normal file
25
customer-service-kb/index.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Yehwang Customer Service Knowledge Base — Index
|
||||||
|
|
||||||
|
Yehwang is a Netherlands-based B2B wholesaler of jewelry, accessories, and clothing, serving retailers and online sellers across Europe.
|
||||||
|
|
||||||
|
## Topics
|
||||||
|
|
||||||
|
| File | Topic | Typical questions |
|
||||||
|
|---|---|---|
|
||||||
|
| [00-about-yehwang](00-about-yehwang.md) | Who we are, what we sell, fulfillment models | "What do you sell?" "Where do you ship from?" |
|
||||||
|
| [01-registration-account](01-registration-account.md) | Registration, account, password, Find a Shop | "Can I register without a company?" "I forgot my password" |
|
||||||
|
| [02-ordering](02-ordering.md) | Placing orders, minimum order, quantity discounts | "What is the minimum order?" "Can I cancel my order?" |
|
||||||
|
| [03-pre-order](03-pre-order.md) | Pre-order products and rules | "What is a pre-order?" "When will my pre-order arrive?" |
|
||||||
|
| [04-payment-vat-invoice](04-payment-vat-invoice.md) | Payment methods, VAT, invoices | "Which payment methods?" "Why am I charged VAT?" |
|
||||||
|
| [05-shipping-delivery](05-shipping-delivery.md) | Carriers, costs, delivery times, tracking | "How much is shipping?" "When will my order arrive?" |
|
||||||
|
| [06-returns-refunds](06-returns-refunds.md) | Returns, refunds, defective/missing items | "An item is broken, what do I do?" |
|
||||||
|
| [07-factory-direct](07-factory-direct.md) | Factory-Direct (ships from our factories) | "What is Factory Direct?" "MOQ for factory direct?" |
|
||||||
|
| [08-dropshipping](08-dropshipping.md) | Dropshipping service (Shopify integration) | "How does dropshipping work?" "Can I dropship?" |
|
||||||
|
| [09-membership-rewards](09-membership-rewards.md) | Membership tiers, volume discounts, rewards | "How do I get a discount?" "What tier am I?" |
|
||||||
|
| [10-products-and-jewelry-care](10-products-and-jewelry-care.md) | Catalog, price ranges, materials, jewelry care | "What materials?" "Does the jewelry discolor?" |
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
- **General / website / self-operated orders:** helpdesk@yehwang.com · +31 318 668 171
|
||||||
|
- **Factory-Direct orders:** service@yehwang.com
|
||||||
|
- **Headquarters / returns address:** Ede, The Netherlands
|
||||||
102
docker-compose.example.yml
Normal file
102
docker-compose.example.yml
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
name: crm-ai-demo
|
||||||
|
|
||||||
|
# 共享基础设施:postgres(带 pgvector) + redis
|
||||||
|
# 应用层(Chatwoot / Twenty)用开源源码跑,不在此 compose 内。
|
||||||
|
#
|
||||||
|
# 分库:
|
||||||
|
# chatwoot — Chatwoot 业务数据(vector 扩展)
|
||||||
|
# twenty — Twenty CRM 数据
|
||||||
|
# products — Bridge 客服知识库 kb_docs(vector 扩展)
|
||||||
|
# Redis 分 db:chatwoot=db0, twenty=db1
|
||||||
|
#
|
||||||
|
# 端口均暴露到宿主,供本地源码应用 / docker 内应用经 host.docker.internal 连接。
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: pgvector/pgvector:pg18
|
||||||
|
container_name: crm-postgres
|
||||||
|
restart: always
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 512M
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
||||||
|
POSTGRES_DB: postgres
|
||||||
|
volumes:
|
||||||
|
- pg-data:/var/lib/postgresql
|
||||||
|
- ./infra/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh:ro
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:8.8-alpine
|
||||||
|
container_name: crm-redis
|
||||||
|
restart: always
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.3'
|
||||||
|
memory: 256M
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
# Chatwoot → LLM → Twenty CRM 销售桥接(源码 Docker 构建)
|
||||||
|
# 连同 compose 内的 postgres 共享网络;Chatwoot/Twenty 为独立 compose,经 host.docker.internal 访问。
|
||||||
|
bridge:
|
||||||
|
build: ./bridge
|
||||||
|
container_name: crm-bridge
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 512M
|
||||||
|
ports:
|
||||||
|
- "4000:4000"
|
||||||
|
environment:
|
||||||
|
PORT: "4000"
|
||||||
|
# 同 compose 网络,用容器名连 postgres(products 库存放客服知识库 kb_docs)
|
||||||
|
PG_URL: "postgres://postgres:postgres@crm-postgres:5432/products"
|
||||||
|
# Chatwoot / Twenty 为独立 compose,端口映射到宿主,容器经 host-gateway 访问
|
||||||
|
CW_BASE_URL: "http://crm-chatwoot-rails:3000"
|
||||||
|
TWENTY_BASE_URL: "http://crm-twenty-server:3000"
|
||||||
|
# AgentBot secret(HMAC 签名校验),由 register_agent_bot.js 生成
|
||||||
|
AGENT_BOT_SECRET: "REPLACE_WITH_AGENT_BOT_SECRET"
|
||||||
|
# 本地开发跳过 HMAC 签名校验(容器间网络安全,Sidekiq body 编码存在差异)
|
||||||
|
SKIP_AGENT_BOT_SIGNATURE: "true"
|
||||||
|
# 业务数据 API(Mock 服务器,宿主 localhost:9505)
|
||||||
|
INVENTORY_API_URL: "http://host.docker.internal:9505"
|
||||||
|
ORDER_API_URL: "http://host.docker.internal:9505"
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
# 挂载源码与配置:改 server.js / *.config.json 后 docker restart crm-bridge 即可,无需 rebuild
|
||||||
|
volumes:
|
||||||
|
- ./bridge/server.js:/app/server.js:ro
|
||||||
|
- ./bridge/index_kb.js:/app/index_kb.js:ro
|
||||||
|
- ./bridge/register_agent_bot.js:/app/register_agent_bot.js:ro
|
||||||
|
- ./bridge/chatwoot.config.json:/app/chatwoot.config.json:ro
|
||||||
|
- ./bridge/llm.config.json:/app/llm.config.json:ro
|
||||||
|
- ./bridge/twenty.config.json:/app/twenty.config.json:ro
|
||||||
|
- ./bridge/kb.config.json:/app/kb.config.json:ro
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pg-data:
|
||||||
|
redis-data:
|
||||||
229
docs/twenty-crm-guide.md
Normal file
229
docs/twenty-crm-guide.md
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# Twenty CRM 功能指南
|
||||||
|
|
||||||
|
> 本文档说明 Twenty CRM 在本系统中的集成方式、当前已用功能,以及 Twenty 本身的完整能力。
|
||||||
|
> 适用于:系统运维、功能扩展、二次开发参考。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、当前系统的集成现状
|
||||||
|
|
||||||
|
### Bridge 实际用到的 Twenty 功能(5 个 GraphQL 操作)
|
||||||
|
|
||||||
|
| 功能 | GraphQL 调用 | 代码位置 (`bridge/server.js`) | 作用 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 创建公司 | `createCompany` | :221 | 从对话提取公司名,自动建公司记录 |
|
||||||
|
| 查公司(去重) | `companies(filter:{name})` | :227 | 避免重复创建同名公司 |
|
||||||
|
| 创建联系人 | `createPerson` | :254 | 建联系人(姓名/邮箱/电话),关联到公司 |
|
||||||
|
| 查联系人(去重) | `people(filter:{emails})` | :234 | 按邮箱去重 |
|
||||||
|
| 创建商机 | `createOpportunity` | :276 | 建商机(名称/金额/阶段/公司/联系人) |
|
||||||
|
| 创建笔记 | `createNote` + `createNoteTarget` | :333 | 把聊天记录挂到商机上 |
|
||||||
|
| **创建跟进任务** | `createTask` + `createTaskTarget` | :329 | **自动化**:商机创建后自动生成 3 天到期的跟进任务 |
|
||||||
|
|
||||||
|
**Bridge 把 Twenty 当成「线索落地库 + 自动化起点」**——不仅结构化存储销售线索,还自动触发跟进流程。
|
||||||
|
|
||||||
|
### 商机创建的触发条件
|
||||||
|
|
||||||
|
Bridge 用 `EXTRACT_SYSTEM`(`server.js:516`)对每轮对话做信息提取,判断 `ready=true` 需要**三要素齐全**:
|
||||||
|
|
||||||
|
1. **公司名** — Company name(任意语言)
|
||||||
|
2. **联系方式** — Email 或 Phone,或联系人姓名
|
||||||
|
3. **产品/需求** — 客户感兴趣的产品或采购意向
|
||||||
|
|
||||||
|
三者缺任何一个,`ready=false`,继续对话收集,不创建商机。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、Twenty CRM 完整数据对象
|
||||||
|
|
||||||
|
当前 workspace(`workspace_6o1rsmnm23f20gpxxzby7wab2`)包含 28 个数据表:
|
||||||
|
|
||||||
|
```
|
||||||
|
当前已用(Bridge 自动写入) 未使用(可手动操作或扩展自动化)
|
||||||
|
┌──────────────────────────┐ ┌────────────────────────────────────┐
|
||||||
|
│ ✅ company 公司 │ │ 📅 calendarEvent 日历/会议 │
|
||||||
|
│ ✅ person 联系人 │ │ 📧 message 邮件消息 │
|
||||||
|
│ ✅ opportunity 商机 │ │ 📞 callRecording 通话录音 │
|
||||||
|
│ ✅ note 笔记 │ │ 🔄 workflow 自动化工作流 │
|
||||||
|
│ ✅ noteTarget 笔记关联 │ │ 📊 dashboard 仪表盘/报表 │
|
||||||
|
│ ✅ task 跟进任务 │ │ 👥 workspaceMember 团队成员 │
|
||||||
|
│ ✅ taskTarget 任务关联 │ │ 🚫 blocklist 屏蔽名单 │
|
||||||
|
└──────────────────────────┘ │ 📎 attachment 附件 │
|
||||||
|
│ 📝 messageCampaign 邮件营销 │
|
||||||
|
│ 🕐 timelineActivity 活动时间线 │
|
||||||
|
└────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、未使用功能详解
|
||||||
|
|
||||||
|
### 1. 任务管理(Task)— 跟进客户
|
||||||
|
|
||||||
|
给商机/联系人创建跟进任务,是商机转化为真实订单的关键环节。
|
||||||
|
|
||||||
|
**典型用法**:
|
||||||
|
- "明天回电 Sophie 确认戒指款式"
|
||||||
|
- "本周五前寄出报价单"
|
||||||
|
- 设置截止日期、负责人、优先级
|
||||||
|
|
||||||
|
**位置**:Twenty 网页 → 左侧导航 Tasks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 日历(Calendar)— 安排会议
|
||||||
|
|
||||||
|
- 关联到联系人/商机
|
||||||
|
- 记录客户会议、产品演示时间
|
||||||
|
- 支持 Google/Microsoft 日历集成(需额外配置 OAuth)
|
||||||
|
|
||||||
|
**位置**:Twenty 网页 → 左侧导航 Calendar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 消息/邮件(Message)— 统一收件箱
|
||||||
|
|
||||||
|
- 集成 Gmail/Outlook,直接在 CRM 里收发邮件
|
||||||
|
- 所有客户沟通集中管理,不再散落各处
|
||||||
|
- 邮件营销(MessageCampaign)支持群发
|
||||||
|
|
||||||
|
**位置**:Twenty 网页 → 左侧导航 Messages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 自动化工作流(Workflow)— 最强大的未用功能
|
||||||
|
|
||||||
|
自动触发规则,目前完全没用上,潜力巨大。
|
||||||
|
|
||||||
|
**典型场景**:
|
||||||
|
- "新商机创建 → 自动通知负责人 + 创建 3 天后跟进任务"
|
||||||
|
- "商机阶段变为 PROPOSAL → 自动发邮件给客户"
|
||||||
|
- "商机金额 > €5000 → 自动提升优先级 + 通知销售经理"
|
||||||
|
|
||||||
|
**位置**:Twenty 网页 → Settings → Workflows
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 仪表盘(Dashboard)— 数据可视化
|
||||||
|
|
||||||
|
- 销售漏斗(各阶段商机数量/金额)
|
||||||
|
- 自定义视图、过滤器、图表
|
||||||
|
- 月度/季度业绩统计
|
||||||
|
|
||||||
|
**位置**:Twenty 网页 → 左侧导航 Dashboards
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. 自定义字段 — Twenty 的杀手锏
|
||||||
|
|
||||||
|
可以给任何对象(Company/Person/Opportunity)添加自定义字段:
|
||||||
|
- 客户行业、Jewelry 偏好材质、年采购量、首次接触渠道
|
||||||
|
- 当前 Bridge 提取的字段是固定的,可通过扩展 `EXTRACT_SYSTEM` 来填充自定义字段
|
||||||
|
|
||||||
|
**位置**:Twenty 网页 → Settings → Object 某个对象 → Fields
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、网页界面导航
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:3000 → 登录后左侧导航栏:
|
||||||
|
|
||||||
|
📊 Dashboards 仪表盘
|
||||||
|
🏢 Companies 公司列表
|
||||||
|
👤 People 联系人
|
||||||
|
💼 Opportunities 商机 ← Bridge 自动创建的数据在这里
|
||||||
|
✅ Tasks 任务
|
||||||
|
📅 Calendar 日历
|
||||||
|
📧 Messages 消息
|
||||||
|
⚙️ Settings 设置 ← 工作流 / 自定义字段 / 团队成员
|
||||||
|
```
|
||||||
|
|
||||||
|
点开任意商机(如 Nordic Retail AB),可见多个标签页:
|
||||||
|
- **Notes** — 笔记(Bridge 自动写入的聊天记录)
|
||||||
|
- **Tasks** — 关联任务
|
||||||
|
- **Timeline** — 活动时间线
|
||||||
|
- **People** — 关联联系人
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、API Key 与认证配置
|
||||||
|
|
||||||
|
Bridge 通过 GraphQL API 访问 Twenty,认证链路:
|
||||||
|
|
||||||
|
```
|
||||||
|
twenty.config.json
|
||||||
|
├── baseURL: http://localhost:3000
|
||||||
|
├── graphqlPath: /graphql
|
||||||
|
├── workspaceId: 70a1901c-45ab-4b95-a7f0-de74be819b2e
|
||||||
|
└── apiKey: <ES256 JWT,kid=2b878cf8...>
|
||||||
|
```
|
||||||
|
|
||||||
|
**JWT 结构**(`bridge/twenty.config.json` 里的 apiKey):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"alg": "ES256", "typ": "JWT", "kid": "2b878cf8-7d35-4906-899f-a0439b1c2116"
|
||||||
|
}
|
||||||
|
// payload:
|
||||||
|
{
|
||||||
|
"sub": "70a1901c-45ab-4b95-a7f0-de74be819b2e", // = workspaceId
|
||||||
|
"type": "API_KEY",
|
||||||
|
"workspaceId": "70a1901c-45ab-4b95-a7f0-de74be819b2e",
|
||||||
|
"iat": 1784878461,
|
||||||
|
"exp": 4922899200,
|
||||||
|
"jti": "556d9fc0-7f32-4a5b-b041-78b30242cefb" // = apiKey 表的 id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**API Key 的两个必要绑定**(缺一不可):
|
||||||
|
1. `core."apiKey"` 表:记录 apiKey id → workspaceId
|
||||||
|
2. `core."roleTarget"` 表:记录 apiKeyId → roleId(绑定权限角色,否则报 `INVALID_AUTH_CONTEXT`)
|
||||||
|
|
||||||
|
> 如需重新生成 API Key,参考项目根目录的 `gen_apikey.js` 脚本(用 HKDF 解密签名密钥后签发 JWT)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、扩展建议
|
||||||
|
|
||||||
|
### 已实现(✅)
|
||||||
|
|
||||||
|
| 扩展 | 做法 | 代码位置 | 价值 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **商机自动创建跟进任务** | Bridge 在 `createLeadInCRM` 里商机创建后自动建 Task | `server.js:329` | 商机不遗漏跟进 |
|
||||||
|
|
||||||
|
#### 自动跟进任务的工作方式
|
||||||
|
|
||||||
|
商机创建成功后,Bridge 自动执行(`server.js` createLeadInCRM 函数内):
|
||||||
|
|
||||||
|
1. 创建一条 Task,标题 `跟进商机: <商机名>`
|
||||||
|
2. 任务内容包含:客户公司、联系人、需求摘要、行动指引(3 个工作日内联系)
|
||||||
|
3. 设置到期日为 **3 天后**
|
||||||
|
4. 通过 `createTaskTarget` 把 Task 关联到刚创建的 Opportunity
|
||||||
|
|
||||||
|
**触发日志示例**(bridge 日志中可见):
|
||||||
|
```
|
||||||
|
[workflow] conv=3 opp=b3ab3af2... -> 自动创建跟进任务 fcd6f300... (3天后到期)
|
||||||
|
[CRM✓] conv=3 建线索: {新公司 + 新联系人 + 新商机} (新公司) (新联系人)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **注意**:本自动化在 Bridge 层实现,而非 Twenty 内置 Workflow 引擎。原因是 Twenty 的 API key 权限不支持通过 GraphQL 创建 `workflowVersion`(报 `FORBIDDEN`)。Bridge 层实现更可控、日志透明。如需更复杂的可视化编排,可在 Twenty 网页 **Settings → Workflows** 通过 UI 创建。
|
||||||
|
|
||||||
|
### 短期(低成本,高收益)
|
||||||
|
|
||||||
|
| 扩展 | 做法 | 价值 |
|
||||||
|
|---|---|---|
|
||||||
|
| **商机金额自动填充** | 扩展 `EXTRACT_SYSTEM`,让 LLM 估算 unit price × quantity | 漏斗金额更准确 |
|
||||||
|
| **自定义字段:客户行业** | Twenty 加字段 + Bridge `EXTRACT_SYSTEM` 提取 | 销售分群分析 |
|
||||||
|
| **商机阶段自动推进** | Bridge 根据"是否提供报价/是否确认订单"调用 `updateOpportunity` 改 stage | 漏斗自动化 |
|
||||||
|
|
||||||
|
### 中期(需开发)
|
||||||
|
|
||||||
|
| 扩展 | 做法 | 价值 |
|
||||||
|
|---|---|---|
|
||||||
|
| **销售漏斗仪表盘** | Twenty Dashboard 配置 | 可视化业绩 |
|
||||||
|
| **双向同步** | Bridge 读 Twenty(如查客户历史商机) | AI 回复更智能 |
|
||||||
|
| **邮件集成** | Twenty Settings 配置 Gmail/Outlook OAuth | 统一沟通 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*最后更新:2026-07-24(新增:自动化跟进任务功能)*
|
||||||
|
*相关文件:`bridge/server.js`(GraphQL 调用 + 自动化工作流)、`bridge/twenty.config.json`(连接配置)、`gen_apikey.js`(API Key 生成)、`create_workflow.js`(工作流创建脚本)*
|
||||||
23
infra/init-db.sh
Normal file
23
infra/init-db.sh
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 共享 postgres 初始化:创建分库 + 在需要的库启用 pgvector 扩展
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
|
||||||
|
echo "[init-db] 创建分库: chatwoot, twenty, products"
|
||||||
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
|
||||||
|
CREATE DATABASE chatwoot;
|
||||||
|
CREATE DATABASE twenty;
|
||||||
|
CREATE DATABASE products;
|
||||||
|
EOSQL
|
||||||
|
|
||||||
|
echo "[init-db] 在 chatwoot / products 库启用 pgvector 扩展"
|
||||||
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" -d chatwoot <<-EOSQL
|
||||||
|
CREATE EXTENSION IF NOT EXISTS vector;
|
||||||
|
EOSQL
|
||||||
|
|
||||||
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" -d products <<-EOSQL
|
||||||
|
CREATE EXTENSION IF NOT EXISTS vector;
|
||||||
|
EOSQL
|
||||||
|
|
||||||
|
echo "[init-db] 完成。库列表:"
|
||||||
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" -c "\l"
|
||||||
253
landing.html
Normal file
253
landing.html
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Stainless Jewelry & Accessories — Wholesale for European SMBs</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { font-family: -apple-system, "Segoe UI", Roboto, sans-serif; color: #1a1a2e; background: #fff; }
|
||||||
|
a { color: inherit; text-decoration: none; }
|
||||||
|
.nav { display: flex; justify-content: space-between; align-items: center; padding: 18px 48px; box-shadow: 0 1px 0 #eee; }
|
||||||
|
.logo { font-weight: 800; font-size: 22px; color: #b8860b; }
|
||||||
|
.nav .links { display: flex; align-items: center; gap: 24px; }
|
||||||
|
.nav .links a { font-size: 15px; color: #555; }
|
||||||
|
.nav .cta { background: #b8860b; color: #fff; padding: 9px 18px; border-radius: 8px; font-weight: 600; }
|
||||||
|
#langSel { padding: 7px 10px; border-radius: 8px; border: 1px solid #ccc; font-size: 14px; background: #fff; }
|
||||||
|
.hero { text-align: center; padding: 72px 24px 56px; background: linear-gradient(180deg,#fbf6ec,#fff); }
|
||||||
|
.hero h1 { font-size: 42px; line-height: 1.2; max-width: 880px; margin: 0 auto 20px; }
|
||||||
|
.hero h1 span { color: #b8860b; }
|
||||||
|
.hero p { font-size: 19px; color: #555; max-width: 720px; margin: 0 auto 24px; }
|
||||||
|
.badges { display:flex; gap:14px; justify-content:center; flex-wrap:wrap; margin-bottom:28px; }
|
||||||
|
.badge { background:#fff; border:1px solid #ecd9a8; color:#8a6d1f; padding:8px 16px; border-radius:20px; font-size:14px; font-weight:600; }
|
||||||
|
.hero .btns { display: flex; gap: 16px; justify-content: center; }
|
||||||
|
.hero .btns a { padding: 14px 26px; border-radius: 10px; font-weight: 600; font-size: 16px; }
|
||||||
|
.btn-primary { background: #b8860b; color: #fff; }
|
||||||
|
.btn-ghost { border: 1.5px solid #b8860b; color: #b8860b; }
|
||||||
|
.warehouses { max-width: 920px; margin: 0 auto; padding: 30px 24px 10px; display:grid; grid-template-columns:1fr 1fr; gap:22px; }
|
||||||
|
.wh { border:1px solid #eee; border-radius:14px; padding:26px; box-shadow:0 6px 20px rgba(0,0,0,.04); }
|
||||||
|
.wh h3 { font-size:20px; margin-bottom:6px; }
|
||||||
|
.wh .flag { font-size:26px; }
|
||||||
|
.wh .days { color:#b8860b; font-weight:700; font-size:15px; margin:6px 0 8px; }
|
||||||
|
.wh p { color:#666; font-size:15px; line-height:1.6; }
|
||||||
|
.features { max-width: 1080px; margin: 0 auto; padding: 50px 24px 60px; display: grid; grid-template-columns: repeat(3,1fr); gap: 28px; }
|
||||||
|
.card { background: #fff; border: 1px solid #eee; border-radius: 14px; padding: 28px; box-shadow: 0 6px 20px rgba(0,0,0,.04); }
|
||||||
|
.card .ic { font-size: 30px; margin-bottom: 12px; }
|
||||||
|
.card h3 { font-size: 19px; margin-bottom: 8px; }
|
||||||
|
.card p { color: #666; font-size: 15px; line-height: 1.6; }
|
||||||
|
.strip { background: #1a1a2e; color: #fff; text-align: center; padding: 56px 24px; }
|
||||||
|
.strip h2 { font-size: 30px; margin-bottom: 14px; }
|
||||||
|
.strip p { color: #bbb; margin-bottom: 28px; max-width: 700px; margin-left:auto; margin-right:auto; }
|
||||||
|
.strip a { background: #b8860b; color: #fff; padding: 14px 30px; border-radius: 10px; font-weight: 600; }
|
||||||
|
footer { text-align: center; padding: 28px; color: #999; font-size: 14px; }
|
||||||
|
.hint { text-align:center; color:#b8860b; font-size:13px; margin-top:10px; margin-bottom:30px; }
|
||||||
|
#cookie { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; color: #eee; padding: 14px 24px; display: flex; justify-content: space-between; align-items: center; gap: 16px; font-size: 14px; z-index: 9999; }
|
||||||
|
#cookie button { background: #b8860b; color: #fff; border: 0; padding: 8px 18px; border-radius: 6px; cursor: pointer; font-weight: 600; }
|
||||||
|
#cookie button.ghost { background: transparent; border: 1px solid #666; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="nav">
|
||||||
|
<div class="logo">◆ Stainless Studio</div>
|
||||||
|
<div class="links">
|
||||||
|
<a href="#" data-i18n="nav.products">Products</a>
|
||||||
|
<a href="#" data-i18n="nav.warehouses">Warehouses</a>
|
||||||
|
<a href="#" data-i18n="nav.custom">Customization</a>
|
||||||
|
<select id="langSel">
|
||||||
|
<option value="en">🇬🇧 EN</option>
|
||||||
|
<option value="de">🇩🇪 DE</option>
|
||||||
|
<option value="fr">🇫🇷 FR</option>
|
||||||
|
<option value="es">🇪🇸 ES</option>
|
||||||
|
<option value="it">🇮🇹 IT</option>
|
||||||
|
<option value="nl">🇳🇱 NL</option>
|
||||||
|
</select>
|
||||||
|
<a href="#" class="cta" data-i18n="nav.cta">Chat with us</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<h1 data-i18n="hero.title">Stainless steel jewelry & accessories, <span>wholesale to European SMBs</span></h1>
|
||||||
|
<p data-i18n="hero.subtitle">316L stainless steel jewelry, accessories and apparel — hypoallergenic, durable, customizable. Fast stock from the Netherlands, wide range & great prices from Yiwu.</p>
|
||||||
|
<div class="badges">
|
||||||
|
<span class="badge" data-i18n="badge.min">Min. order €30</span>
|
||||||
|
<span class="badge" data-i18n="badge.nl">🇳🇱 NL stock · 3 days</span>
|
||||||
|
<span class="badge" data-i18n="badge.yw">🇨🇳 Yiwu · 15 days · wider range</span>
|
||||||
|
<span class="badge" data-i18n="badge.custom">Custom logo / OEM</span>
|
||||||
|
</div>
|
||||||
|
<div class="btns">
|
||||||
|
<a href="#" class="btn-primary" onclick="return false;" data-i18n="hero.btn1">Start an order</a>
|
||||||
|
<a href="#" class="btn-ghost" onclick="return false;" data-i18n="hero.btn2">See products</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="warehouses">
|
||||||
|
<div class="wh">
|
||||||
|
<span class="flag">🇳🇱</span><h3 data-i18n="nl.title">Netherlands warehouse — in stock</h3>
|
||||||
|
<div class="days" data-i18n="nl.days">Delivery in ~3 days</div>
|
||||||
|
<p data-i18n="nl.desc">Best for urgent orders and restocks. Popular styles ready to ship across Europe.</p>
|
||||||
|
</div>
|
||||||
|
<div class="wh">
|
||||||
|
<span class="flag">🇨🇳</span><h3 data-i18n="yw.title">Yiwu warehouse — sourced on demand</h3>
|
||||||
|
<div class="days" data-i18n="yw.days">Delivery in ~15 days</div>
|
||||||
|
<p data-i18n="yw.desc">Widest range of styles, most affordable. Best for larger orders, new styles and custom items.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="features">
|
||||||
|
<div class="card"><div class="ic">💍</div><h3 data-i18n="f1.title">316L stainless steel</h3><p data-i18n="f1.desc">Hypoallergenic, durable, tarnish-resistant. Necklaces, rings, bracelets, earrings — gold, rose-gold, silver plating.</p></div>
|
||||||
|
<div class="card"><div class="ic">✨</div><h3 data-i18n="f2.title">Customization</h3><p data-i18n="f2.desc">Engraving, custom logo, OEM packaging. Make it your own brand.</p></div>
|
||||||
|
<div class="card"><div class="ic">🛒</div><h3 data-i18n="f3.title">SMB-friendly</h3><p data-i18n="f3.desc">No MOQ — just a €30 minimum order value. Low barrier for small retailers and online sellers.</p></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p class="hint" data-i18n="hint">👇 Click the bubble in the bottom-right — tell us your company and what you're looking for.</p>
|
||||||
|
|
||||||
|
<section class="strip">
|
||||||
|
<h2 data-i18n="strip.title">Looking for a reliable jewelry supplier?</h2>
|
||||||
|
<p data-i18n="strip.text">Open the chat, tell us what you need and how many — our assistant shows you prices and stock live, and helps you place the order.</p>
|
||||||
|
<a href="#" onclick="return false;" data-i18n="strip.btn">Start chatting</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>© Stainless Studio · Wholesale stainless steel jewelry, accessories & apparel · <a href="#" style="color:#b8860b" data-i18n="footer.privacy">Privacy Policy</a></footer>
|
||||||
|
|
||||||
|
<div id="cookie">
|
||||||
|
<span data-i18n="cookie.text">We use cookies to operate the live chat and improve your experience.</span>
|
||||||
|
<span><button class="ghost" onclick="document.getElementById('cookie').style.display='none'" data-i18n="cookie.decline">Decline</button> <button onclick="document.getElementById('cookie').style.display='none'" data-i18n="cookie.accept">Accept</button></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const I18N = {
|
||||||
|
en: {
|
||||||
|
"nav.products":"Products","nav.warehouses":"Warehouses","nav.custom":"Customization","nav.cta":"Chat with us",
|
||||||
|
"hero.title":"Stainless steel jewelry & accessories, <span>wholesale to European SMBs</span>",
|
||||||
|
"hero.subtitle":"316L stainless steel jewelry, accessories and apparel — hypoallergenic, durable, customizable. Fast stock from the Netherlands, wide range & great prices from Yiwu.",
|
||||||
|
"badge.min":"Min. order €30","badge.nl":"🇳🇱 NL stock · 3 days","badge.yw":"🇨🇳 Yiwu · 15 days · wider range","badge.custom":"Custom logo / OEM",
|
||||||
|
"hero.btn1":"Start an order","hero.btn2":"See products",
|
||||||
|
"nl.title":"Netherlands warehouse — in stock","nl.days":"Delivery in ~3 days","nl.desc":"Best for urgent orders and restocks. Popular styles ready to ship across Europe.",
|
||||||
|
"yw.title":"Yiwu warehouse — sourced on demand","yw.days":"Delivery in ~15 days","yw.desc":"Widest range of styles, most affordable. Best for larger orders, new styles and custom items.",
|
||||||
|
"f1.title":"316L stainless steel","f1.desc":"Hypoallergenic, durable, tarnish-resistant. Necklaces, rings, bracelets, earrings — gold, rose-gold, silver plating.",
|
||||||
|
"f2.title":"Customization","f2.desc":"Engraving, custom logo, OEM packaging. Make it your own brand.",
|
||||||
|
"f3.title":"SMB-friendly","f3.desc":"No MOQ — just a €30 minimum order value. Low barrier for small retailers and online sellers.",
|
||||||
|
"hint":"👇 Click the bubble in the bottom-right — tell us your company and what you're looking for.",
|
||||||
|
"strip.title":"Looking for a reliable jewelry supplier?","strip.text":"Open the chat, tell us what you need and how many — our assistant shows you prices and stock live, and helps you place the order.","strip.btn":"Start chatting",
|
||||||
|
"cookie.text":"We use cookies to operate the live chat and improve your experience.","cookie.decline":"Decline","cookie.accept":"Accept","footer.privacy":"Privacy Policy"
|
||||||
|
},
|
||||||
|
de: {
|
||||||
|
"nav.products":"Produkte","nav.warehouses":"Lager","nav.custom":"Anpassung","nav.cta":"Mit uns chatten",
|
||||||
|
"hero.title":"Edelstahlschmuck & Accessoires, <span>Großhandel für europäische KMU</span>",
|
||||||
|
"hero.subtitle":"316L-Edelstahlschmuck, Accessoires und Bekleidung — hypoallergen, langlebig, anpassbar. Schnelle Ware aus den Niederlanden, große Auswahl & gute Preise aus Yiwu.",
|
||||||
|
"badge.min":"Mindestbestellung 30 €","badge.nl":"🇳🇱 NL-Lager · 3 Tage","badge.yw":"🇨🇳 Yiwu · 15 Tage · mehr Auswahl","badge.custom":"Eigenes Logo / OEM",
|
||||||
|
"hero.btn1":"Bestellung starten","hero.btn2":"Produkte ansehen",
|
||||||
|
"nl.title":"Niederlande-Lager — auf Lager","nl.days":"Lieferung in ~3 Tagen","nl.desc":"Ideal für Eilaufträge und Nachbestellungen. Beliebte Modelle sofort versandbereit in ganz Europa.",
|
||||||
|
"yw.title":"Yiwu-Lager — auf Bestellung","nl.days":"Lieferung in ~15 Tagen","yw.days":"Lieferung in ~15 Tagen","yw.desc":"Größte Modellauswahl, günstigste Preise. Ideal für größere Bestellungen, neue Modelle und individuelle Artikel.",
|
||||||
|
"f1.title":"316L-Edelstahl","f1.desc":"Hypoallergen, langlebig, anlaufgeschützt. Ketten, Ringe, Armbänder, Ohrringe — Gold-, Roségold-, Silberbeschichtung.",
|
||||||
|
"f2.title":"Anpassung","f2.desc":"Gravur, eigenes Logo, OEM-Verpackung. Machen Sie es zu Ihrer Marke.",
|
||||||
|
"f3.title":"KMU-freundlich","f3.desc":"Keine Mindestmenge — nur 30 € Mindestbestellwert. Niedrige Hürde für kleine Händler und Online-Verkäufer.",
|
||||||
|
"hint":"👇 Klicken Sie auf die Sprechblase unten rechts — nennen Sie uns Ihr Unternehmen und Ihr Bedarf.",
|
||||||
|
"strip.title":"Auf der Suche nach einem zuverlässigen Schmuhlieferanten?","strip.text":"Öffnen Sie den Chat, nennen Sie uns die gewünschten Produkte und Mengen — unser Assistent zeigt Ihnen Preise und Lagerbestand live und hilft bei der Bestellung.","strip.btn":"Chat starten",
|
||||||
|
"cookie.text":"Wir verwenden Cookies für den Live-Chat und zur Verbesserung Ihrer Erfahrung.","cookie.decline":"Ablehnen","cookie.accept":"Akzeptieren","footer.privacy":"Datenschutzerklärung"
|
||||||
|
},
|
||||||
|
fr: {
|
||||||
|
"nav.products":"Produits","nav.warehouses":"Entrepôts","nav.custom":"Personnalisation","nav.cta":"Discuter",
|
||||||
|
"hero.title":"Bijoux en acier inoxydable & accessoires, <span>grossiste pour PME européennes</span>",
|
||||||
|
"hero.subtitle":"Bijoux en acier inoxydable 316L, accessoires et vêtements — hypoallergéniques, durables, personnalisables. Stock rapide depuis les Pays-Bas, grand choix & bons prix depuis Yiwu.",
|
||||||
|
"badge.min":"Commande min. 30 €","badge.nl":"🇳🇱 Stock NL · 3 jours","badge.yw":"🇨🇳 Yiwu · 15 jours · plus de choix","badge.custom":"Logo perso / OEM",
|
||||||
|
"hero.btn1":"Passer commande","hero.btn2":"Voir les produits",
|
||||||
|
"nl.title":"Entrepôt Pays-Bas — en stock","nl.days":"Livraison en ~3 jours","nl.desc":"Idéal pour les commandes urgentes et les réassorts. Modèles populaires prêts à expédier en Europe.",
|
||||||
|
"yw.title":"Entrepôt Yiwu — sur commande","yw.days":"Livraison en ~15 jours","yw.desc":"Le plus grand choix de modèles, les prix les plus bas. Idéal pour les grosses commandes, nouveaux modèles et articles personnalisés.",
|
||||||
|
"f1.title":"Acier inoxydable 316L","f1.desc":"Hypoallergénique, durable, résistant au ternissement. Colliers, bagues, bracelets, boucles d'oreilles — dorure, or rose, argent.",
|
||||||
|
"f2.title":"Personnalisation","f2.desc":"Gravure, logo personnalisé, emballage OEM. Faites-en votre marque.",
|
||||||
|
"f3.title":"Adapté aux PME","f3.desc":"Pas de MOQ — seulement 30 € de commande minimale. Faible barrière pour petits détaillants et vendeurs en ligne.",
|
||||||
|
"hint":"👇 Cliquez sur la bulle en bas à droite — indiquez votre entreprise et vos besoins.",
|
||||||
|
"strip.title":"Vous cherchez un fournisseur de bijoux fiable ?","strip.text":"Ouvrez le chat, indiquez les produits et quantités voulus — notre assistant affiche prix et stock en direct et vous aide à commander.","strip.btn":"Discuter",
|
||||||
|
"cookie.text":"Nous utilisons des cookies pour le chat en direct et améliorer votre expérience.","cookie.decline":"Refuser","cookie.accept":"Accepter","footer.privacy":"Politique de confidentialité"
|
||||||
|
},
|
||||||
|
es: {
|
||||||
|
"nav.products":"Productos","nav.warehouses":"Almacenes","nav.custom":"Personalización","nav.cta":"Chatear",
|
||||||
|
"hero.title":"Joyería en acero inoxidable y accesorios, <span>venta mayorista para pymes europeas</span>",
|
||||||
|
"hero.subtitle":"Joyería en acero inoxidable 316L, accesorios y ropa — hipoalergénica, duradera, personalizable. Stock rápido desde Países Bajos, gran variedad y buenos precios desde Yiwu.",
|
||||||
|
"badge.min":"Pedido mín. 30 €","badge.nl":"🇳🇱 Stock NL · 3 días","badge.yw":"🇨🇳 Yiwu · 15 días · más variedad","badge.custom":"Logo propio / OEM",
|
||||||
|
"hero.btn1":"Hacer pedido","hero.btn2":"Ver productos",
|
||||||
|
"nl.title":"Almacén Países Bajos — en stock","nl.days":"Entrega en ~3 días","nl.desc":"Ideal para pedidos urgentes y reposiciones. Modelos populares listos para enviar a Europa.",
|
||||||
|
"yw.title":"Almacén Yiwu — bajo pedido","yw.days":"Entrega en ~15 días","yw.desc":"La mayor variedad de modelos, los mejores precios. Ideal para pedidos grandes, modelos nuevos y artículos personalizados.",
|
||||||
|
"f1.title":"Acero inoxidable 316L","f1.desc":"Hipoalergénico, duradero, resistente al mate. Collares, anillos, pulseras, pendientes — chapado en oro, oro rosa, plata.",
|
||||||
|
"f2.title":"Personalización","f2.desc":"Grabado, logo propio, embalaje OEM. Hazlo tu marca.",
|
||||||
|
"f3.title":"Adaptado a pymes","f3.desc":"Sin MOQ — solo 30 € de pedido mínimo. Barrera baja para pequeños minoristas y vendedores online.",
|
||||||
|
"hint":"👇 Haz clic en la burbuja abajo a la derecha — dinos tu empresa y lo que buscas.",
|
||||||
|
"strip.title":"¿Buscas un proveedor de joyería fiable?","strip.text":"Abre el chat, dinos los productos y cantidades que necesitas — nuestro asistente te muestra precios y stock en directo y te ayuda a hacer el pedido.","strip.btn":"Empezar a chatear",
|
||||||
|
"cookie.text":"Usamos cookies para el chat en vivo y mejorar tu experiencia.","cookie.decline":"Rechazar","cookie.accept":"Aceptar","footer.privacy":"Política de privacidad"
|
||||||
|
},
|
||||||
|
it: {
|
||||||
|
"nav.products":"Prodotti","nav.warehouses":"Magazzini","nav.custom":"Personalizzazione","nav.cta":"Chatta",
|
||||||
|
"hero.title":"Gioielli in acciaio inossidabile e accessori, <span>ingrosso per PMI europee</span>",
|
||||||
|
"hero.subtitle":"Gioielli in acciaio inossidabile 316L, accessori e abbigliamento — ipoallergenici, durevoli, personalizzabili. Stock veloce dai Paesi Bassi, grande scelta e buoni prezzi da Yiwu.",
|
||||||
|
"badge.min":"Ordine min. 30 €","badge.nl":"🇳🇱 Stock NL · 3 giorni","badge.yw":"🇨🇳 Yiwu · 15 giorni · più scelta","badge.custom":"Logo personalizzato / OEM",
|
||||||
|
"hero.btn1":"Inizia ordine","hero.btn2":"Vedi prodotti",
|
||||||
|
"nl.title":"Magazzino Paesi Bassi — in stock","nl.days":"Consegna in ~3 giorni","nl.desc":"Ideale per ordini urgenti e riassortimenti. Modelli popolari pronti alla spedizione in Europa.",
|
||||||
|
"yw.title":"Magazzino Yiwu — su richiesta","yw.days":"Consegna in ~15 giorni","yw.desc":"La più ampia scelta di modelli, i prezzi migliori. Ideale per ordini grandi, nuovi modelli e articoli personalizzati.",
|
||||||
|
"f1.title":"Acciaio inossidabile 316L","f1.desc":"Ipoallergenico, durevole, resistente all'opacizzazione. Collane, anelli, bracciali, orecchini — placcatura oro, oro rosa, argento.",
|
||||||
|
"f2.title":"Personalizzazione","f2.desc":"Incisione, logo personalizzato, packaging OEM. Rendilo il tuo brand.",
|
||||||
|
"f3.title":"Adatto alle PMI","f3.desc":"Nessun MOQ — solo 30 € di ordine minimo. Barriera bassa per piccoli rivenditori e venditori online.",
|
||||||
|
"hint":"👇 Clicca sulla bolla in basso a destra — dicci la tua azienda e cosa cerchi.",
|
||||||
|
"strip.title":"Cerchi un fornitore di gioielli affidabile?","strip.text":"Apri la chat, dicci i prodotti e le quantità che ti servono — il nostro assistente mostra prezzi e disponibilità in tempo reale e ti aiuta a ordinare.","strip.btn":"Inizia a chattare",
|
||||||
|
"cookie.text":"Usiamo cookie per la chat live e migliorare la tua esperienza.","cookie.decline":"Rifiuta","cookie.accept":"Accetta","footer.privacy":"Informativa sulla privacy"
|
||||||
|
},
|
||||||
|
nl: {
|
||||||
|
"nav.products":"Producten","nav.warehouses":"Magazijnen","nav.custom":"Maatwerk","nav.cta":"Chat met ons",
|
||||||
|
"hero.title":"Roestvrijstalen sieraden & accessoires, <span>groothandel voor Europese mkb</span>",
|
||||||
|
"hero.subtitle":"316L-roestvrijstalen sieraden, accessoires en kleding — hypoallergeen, duurzaam, aanpasbaar. Snelle voorraad uit Nederland, brede selectie & goede prijzen uit Yiwu.",
|
||||||
|
"badge.min":"Min. bestelling €30","badge.nl":"🇳🇱 NL-voorraad · 3 dagen","badge.yw":"🇨🇳 Yiwu · 15 dagen · meer keuze","badge.custom":"Eigen logo / OEM",
|
||||||
|
"hero.btn1":"Start een bestelling","hero.btn2":"Producten bekijken",
|
||||||
|
"nl.title":"Magazijn Nederland — op voorraad","nl.days":"Levering in ~3 dagen","nl.desc":"Ideaal voor spoedorders en bijbestellingen. Populaire modellen klaar voor verzending in Europa.",
|
||||||
|
"yw.title":"Magazijn Yiwu — op bestelling","yw.days":"Levering in ~15 dagen","yw.desc":"Grootste modelkeuze, voordeligste prijzen. Ideaal voor grotere orders, nieuwe modellen en maatwerk.",
|
||||||
|
"f1.title":"316L-roestvrijstaal","f1.desc":"Hypoallergeen, duurzaam, roestvrij. Kettingen, ringen, armbanden, oorbellen — verguld, roségoud, zilver.",
|
||||||
|
"f2.title":"Maatwerk","f2.desc":"Gravure, eigen logo, OEM-verpakking. Maak het jouw merk.",
|
||||||
|
"f3.title":"Mkb-vriendelijk","f3.desc":"Geen minimumaantal — slechts €30 minimum bestelwaarde. Lage drempel voor kleine winkeliers en online verkopers.",
|
||||||
|
"hint":"👇 Klik op de bel linksonder — vertel ons je bedrijf en wat je zoekt.",
|
||||||
|
"strip.title":"Op zoek naar een betrouwbare sieradenleverancier?","strip.text":"Open de chat, vertel ons welke producten en hoeveel je wilt — onze assistent toont direct prijzen en voorraad en helpt je met bestellen.","strip.btn":"Start chatten",
|
||||||
|
"cookie.text":"We gebruiken cookies voor de live chat en om je ervaring te verbeteren.","cookie.decline":"Weigeren","cookie.accept":"Accepteren","footer.privacy":"Privacybeleid"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function detectLang(){
|
||||||
|
const bl = (navigator.language || navigator.userLanguage || 'en').toLowerCase();
|
||||||
|
if (bl.startsWith('de')) return 'de';
|
||||||
|
if (bl.startsWith('fr')) return 'fr';
|
||||||
|
if (bl.startsWith('es')) return 'es';
|
||||||
|
if (bl.startsWith('it')) return 'it';
|
||||||
|
if (bl.startsWith('nl')) return 'nl';
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
let currentLang = 'en';
|
||||||
|
function applyLang(lang){
|
||||||
|
currentLang = lang;
|
||||||
|
const dict = I18N[lang] || I18N.en;
|
||||||
|
document.documentElement.lang = lang;
|
||||||
|
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||||
|
const key = el.getAttribute('data-i18n');
|
||||||
|
if (dict[key]) el.innerHTML = dict[key];
|
||||||
|
});
|
||||||
|
document.getElementById('langSel').value = lang;
|
||||||
|
if (window.$chatwoot && typeof window.$chatwoot.setLocale === 'function') {
|
||||||
|
window.$chatwoot.setLocale(lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById('langSel').addEventListener('change', e => applyLang(e.target.value));
|
||||||
|
applyLang(detectLang());
|
||||||
|
|
||||||
|
(function(d,t) {
|
||||||
|
var BASE_URL="https://gtm-chatwoot.shine-buy.com";
|
||||||
|
var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
||||||
|
g.src=BASE_URL+"/packs/js/sdk.js";
|
||||||
|
g.async = true;
|
||||||
|
s.parentNode.insertBefore(g,s);
|
||||||
|
g.onload=function(){
|
||||||
|
window.chatwootSDK.run({
|
||||||
|
websiteToken: 'Tj61ZwkH6JhFWmvQZJRHU4vt',
|
||||||
|
baseUrl: BASE_URL,
|
||||||
|
locale: currentLang
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})(document,"script");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
33
start.sh
Normal file
33
start.sh
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 一键用 Docker 启动整套 CRM AI Demo:
|
||||||
|
# 1. Twenty CRM (./twenty 源码 + Docker 构建)
|
||||||
|
# 2. Chatwoot (./chatwoot 源码 + Docker 构建)
|
||||||
|
# 3. Bridge + 基础设施 (./docker-compose.yml)
|
||||||
|
set -e
|
||||||
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TWENTY_DIR="$HERE/twenty"
|
||||||
|
|
||||||
|
echo "==> [1/3] Twenty CRM (源码 Docker 构建 + 开发模式)"
|
||||||
|
if [ -f "$TWENTY_DIR/docker-compose.yaml" ]; then
|
||||||
|
docker compose -f "$TWENTY_DIR/docker-compose.yaml" --project-directory "$TWENTY_DIR" up -d --build
|
||||||
|
else
|
||||||
|
echo " 跳过:未找到 $TWENTY_DIR/docker-compose.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> [2/3] Chatwoot (源码 Docker 构建 + 开发模式)"
|
||||||
|
docker compose -f "$HERE/chatwoot/docker-compose.yml" up -d --build
|
||||||
|
|
||||||
|
echo "==> [3/3] 共享基础设施 (pg + redis) + Bridge"
|
||||||
|
docker compose -f "$HERE/docker-compose.yml" up -d --build
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==> 全部已启动。容器状态:"
|
||||||
|
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep -E 'NAMES|twenty|chatwoot|crm-'
|
||||||
|
echo
|
||||||
|
echo "Bridge 日志: docker logs -f crm-bridge"
|
||||||
|
echo "健康检查: curl http://localhost:4000/healthz"
|
||||||
|
echo ""
|
||||||
|
echo "首次构建提示:"
|
||||||
|
echo " Chatwoot 首次 build 需 5-10 分钟 (bundle install + pnpm install)"
|
||||||
|
echo " Twenty 首次 build 需 5-10 分钟 (yarn install)"
|
||||||
|
echo " 之后改源码无需重建镜像 (volume 挂载,服务自动重载)"
|
||||||
Reference in New Issue
Block a user