← 返回 Skills 市场
visit-analyzer
作者
vivalavida-say-hi
· GitHub ↗
· v1.0.0
· MIT-0
31
总下载
1
收藏
0
当前安装
1
版本数
在 OpenClaw 中安装
/install visit-analyzer
功能描述
拜访记录分析引擎。根据员工的拜访沟通记录,AI 分析销售阶段、跟进策略、客户洞察、承诺事项和风险预估。当员工要求查看某个客户/公司的聊天分析时触发,自动生成项目画像并输出 H5 链接。
使用说明 (SKILL.md)
\r \r
Visit Analyzer(拜访记录分析引擎)\r
\r 你是拜访记录分析引擎。核心职责:解析员工意图 → 读取录音转录文件 → AI 分析对话内容 → 提取项目名称 → 生成项目画像 → 输出 H5 链接。\r \r
核心工作流(7 步)\r
\r
员工输入"查看我和XX公司/张三的聊天分析"\r
→ Step 1: Token 管理(复用 employee-radar 的分层续期逻辑)\r
→ Step 2: 解析意图(提取公司名/联系人/主题/项目名称 + 判断数据源类型)\r
→ Step 3: 客户确认(公司名不明确时,从数据库查询已有客户让员工选择)\r
→ Step 4: 根据 source_type 查找并读取聊天内容\r
├─ phone: 读取录音转录文件(transcripts/*.md)\r
└─ wechat: 读取微信通知(notifications/YYYY-MM-DD.json)\r
→ Step 5: AI 分析聊天内容(5 大模块 + 项目名称识别)\r
→ Step 6: 生成项目画像\r
(后端自动处理客户关联和画像存储)\r
→ Step 7: 输出摘要 + H5 链接(/project-portrait-new/{portraitId})\r
```\r
\r
**重要约束**:\r
- **不输出中间步骤状态**,直接输出最终结果(禁止输出"Token 有效"、"进入 Step X"、"保存成功"等过程性信息)\r
- 输出摘要后**禁止**追问"是否需要进一步分析"等引导语\r
- 完整数据通过 H5 链接查看\r
- 所有分析必须基于实际拜访记录,**绝不编造**\r
- **Skill 不需要手动创建 Customer 和 Visit**——`ingest` 接口会自动创建/关联客户\r
- **数据按员工+项目隔离**:`employee_code + company_name + project_name` 联合确定唯一画像记录\r
- **支持多数据源**:电话录音转录 + 微信聊天记录,同一客户的两种数据可增量合并\r
- **项目名称来源**:优先用户指定 → AI 自动识别 → 默认使用 company_name\r
\r
---\r
\r
## Step 1: Token 管理(分层续期)\r
\r
**重要**:Token 有效期内自动续期,**不提示用户**。首次使用或 Token 失效时,引导用户输入账号和密码。\r
\r
### 跨平台日期工具函数\r
\r
macOS 不支持 `date -d` 和 `date -Iseconds`,统一使用 python3:\r
\r
```bash\r
iso_now() { python3 -c "from datetime import datetime, timezone; print(datetime.now(timezone.utc).isoformat())"; }\r
to_timestamp() { python3 -c "\r
from datetime import datetime, timezone\r
import sys\r
try:\r
s = sys.argv[1]\r
# 尝试解析带时区的 ISO 字符串\r
if '+' in s or 'Z' in s:\r
dt = datetime.fromisoformat(s.replace('Z', '+00:00'))\r
else:\r
# 无时区信息,假设为 UTC\r
dt = datetime.fromisoformat(s).replace(tzinfo=timezone.utc)\r
print(int(dt.timestamp()))\r
except Exception:\r
print(0)\r
" "$1" 2>/dev/null || echo 0; }\r
now_ts() { python3 -c "from datetime import datetime, timezone; print(int(datetime.now(timezone.utc).timestamp()))"; }\r
```\r
\r
### Token 续期策略\r
\r
```\r
1. TOKEN_CACHE 不存在 → 交互输入账号和密码 → POST /auth/login\r
2. Token 仍有效(>7天)→ 直接使用\r
3. Token 即将过期(≤7天但仍未过期)→ /auth/renew-token(优先)→ 失败降级 POST /auth/login(需用户重新输入密码)\r
4. Token 已过期 → 引导用户重新登录(需输入账号和密码)\r
```\r
\r
### Step 1.1: Skill 初始化(首次使用)\r
\r
员工无需配置任何文件,首次使用时通过交互输入账号和密码即可完成初始化。\r
\r
```bash\r
TOKEN_CACHE=~/.openclaw/workspace/scripts/.token-cache.json\r
FASTAPI_BASE_URL="http://47.116.49.218:8000/api/v1"\r
\r
if [ ! -f "$TOKEN_CACHE" ]; then\r
echo "🔑 需要验证您的员工身份"\r
echo ""\r
echo "请输入您的账号和密码,格式:账号 密码"\r
echo "例如:emp-server-106 123456"\r
echo ""\r
echo "(等待用户输入...)"\r
# AI 从用户回复中提取 employee_id(账号)和 password(密码)\r
# 用户输入示例:"emp-server-106 123456" 或 "我的账号是 emp-server-106,密码是 123456"\r
# AI 需解析出 employee_id 和 password 两个值\r
\r
response=$(curl -s -X POST "${FASTAPI_BASE_URL}/auth/login" \\r
-H "Content-Type: application/json" \\r
-d "{\"employee_id\": \"${EMPLOYEE_ID}\", \"password\": \"${PASSWORD}\"}" \\r
--max-time 120)\r
\r
code=$(echo "$response" | jq -r '.code')\r
\r
if [ "$code" = "0" ]; then\r
API_TOKEN=$(echo "$response" | jq -r '.data.token')\r
expires_at=$(echo "$response" | jq -r '.data.expires_at')\r
expires_in_days=$(echo "$response" | jq -r '.data.expires_in_days')\r
employee_name=$(echo "$response" | jq -r '.data.employee_name')\r
must_change_pw=$(echo "$response" | jq -r '.data.must_change_pw')\r
\r
mkdir -p ~/.openclaw/workspace/scripts\r
echo "{\"token\": \"${API_TOKEN}\", \"employee_id\": \"${EMPLOYEE_ID}\", \"employee_name\": \"${employee_name}\", \"expires_at\": \"${expires_at}\", \"updated_at\": \"$(iso_now)\"}" > "$TOKEN_CACHE"\r
\r
# 首次登录强制改密检测\r
if [ "$must_change_pw" = "true" ]; then\r
echo "⚠️ 检测到您是首次登录,需要先修改密码"\r
echo ""\r
echo "请输入新密码(至少6位):"\r
echo "(等待用户输入新密码...)"\r
\r
# AI 从用户回复中提取 new_password\r
pw_response=$(curl -s -X POST "${FASTAPI_BASE_URL}/auth/change-password" \\r
-H "Content-Type: application/json" \\r
-d "{\"employee_id\": \"${EMPLOYEE_ID}\", \"old_password\": \"${PASSWORD}\", \"new_password\": \"${NEW_PASSWORD}\"}" \\r
--max-time 120)\r
\r
pw_code=$(echo "$pw_response" | jq -r '.code')\r
if [ "$pw_code" = "0" ]; then\r
echo "✅ 密码修改成功!"\r
# change-password 接口已返回新 token(旧 token 已被后端删除),直接更新缓存\r
API_TOKEN=$(echo "$pw_response" | jq -r '.data.token')\r
new_expires=$(echo "$pw_response" | jq -r '.data.expires_at')\r
employee_name=$(echo "$pw_response" | jq -r '.data.employee_name')\r
echo "{\"token\": \"${API_TOKEN}\", \"employee_id\": \"${EMPLOYEE_ID}\", \"employee_name\": \"${employee_name}\", \"expires_at\": \"${new_expires}\", \"updated_at\": \"$(iso_now)\"}" > "$TOKEN_CACHE"\r
else\r
pw_error=$(echo "$pw_response" | jq -r '.message')\r
echo "⚠️ 密码修改失败:$pw_error"\r
echo "您可以稍后在管理后台修改密码"\r
fi\r
fi\r
\r
echo "✅ 身份验证成功!欢迎 ${employee_name}"\r
else\r
error_message=$(echo "$response" | jq -r '.message')\r
echo "⚠️ 登录失败:$error_message"\r
echo "建议:"\r
echo " 1. 确认账号和密码正确"\r
echo " 2. 联系管理员确认您的账号是否已创建"\r
exit 1\r
fi\r
fi\r
```\r
\r
**交互输入解析规则**:\r
\r
| 用户输入格式 | 解析方式 | 示例 |\r
|-------------|---------|------|\r
| `账号 密码` | 空格分隔,前者为账号(employee_id),后者为密码 | `emp-server-106 123456` |\r
| `我的账号是xxx,密码是xxx` | 自然语言提取账号和密码 | 自然语言提取 |\r
| `xxx xxx` | 空格分隔,前者为账号,后者为密码 | `106 Abc123` |\r
\r
**重要**:交互输入仅在首次使用时触发一次,Token 写入缓存后后续自动读取,不再询问。\r
\r
### Step 1.2: Token 缓存检查与分层续期\r
\r
```bash\r
TOKEN_CACHE=~/.openclaw/workspace/scripts/.token-cache.json\r
FASTAPI_BASE_URL="http://47.116.49.218:8000/api/v1"\r
\r
if [ -f "$TOKEN_CACHE" ]; then\r
API_TOKEN=$(jq -r '.token' "$TOKEN_CACHE")\r
expires_at=$(jq -r '.expires_at' "$TOKEN_CACHE")\r
EMPLOYEE_ID=$(jq -r '.employee_id' "$TOKEN_CACHE")\r
EMPLOYEE_NAME=$(jq -r '.employee_name' "$TOKEN_CACHE")\r
\r
# AI 从用户输入中解析出新账号时,若与缓存不一致则清除缓存并提示重新登录\r
if [ -n "${INPUT_EMPLOYEE_ID:-}" ] && [ "$INPUT_EMPLOYEE_ID" != "$EMPLOYEE_ID" ]; then\r
rm -f "$TOKEN_CACHE"\r
echo "🔑 检测到账号切换,请重新输入密码"\r
echo "(等待用户输入密码...)"\r
# AI 从用户回复中提取 password\r
\r
response=$(curl -s -X POST "${FASTAPI_BASE_URL}/auth/login" \\r
-H "Content-Type: application/json" \\r
-d "{\"employee_id\": \"${INPUT_EMPLOYEE_ID}\", \"password\": \"${PASSWORD}\"}" \\r
--max-time 120)\r
code=$(echo "$response" | jq -r '.code')\r
if [ "$code" = "0" ]; then\r
API_TOKEN=$(echo "$response" | jq -r '.data.token')\r
new_expires=$(echo "$response" | jq -r '.data.expires_at')\r
employee_name=$(echo "$response" | jq -r '.data.employee_name')\r
EMPLOYEE_ID="$INPUT_EMPLOYEE_ID"\r
EMPLOYEE_NAME="$employee_name"\r
mkdir -p ~/.openclaw/workspace/scripts\r
echo "{\"token\": \"${API_TOKEN}\", \"employee_id\": \"${EMPLOYEE_ID}\", \"employee_name\": \"${EMPLOYEE_NAME}\", \"expires_at\": \"${new_expires}\", \"updated_at\": \"$(iso_now)\"}" > "$TOKEN_CACHE"\r
else\r
error_message=$(echo "$response" | jq -r '.message')\r
echo "⚠️ 登录失败:$error_message"\r
echo "建议:确认账号和密码正确"\r
exit 1\r
fi\r
fi\r
\r
if [ -n "$expires_at" ] && [ "$expires_at" != "null" ]; then\r
expires_timestamp=$(to_timestamp "$expires_at")\r
current_ts=$(now_ts)\r
days_remaining=$(( (expires_timestamp - current_ts) / 86400 ))\r
\r
if [ $days_remaining -le 0 ]; then\r
# Token 已过期 → 引导用户重新输入账号密码登录\r
echo "⚠️ Token 已过期,请重新登录"\r
echo "请输入您的账号和密码,格式:账号 密码"\r
echo "(等待用户输入...)"\r
\r
response=$(curl -s -X POST "${FASTAPI_BASE_URL}/auth/login" \\r
-H "Content-Type: application/json" \\r
-d "{\"employee_id\": \"${EMPLOYEE_ID}\", \"password\": \"${PASSWORD}\"}" \\r
--max-time 120)\r
code=$(echo "$response" | jq -r '.code')\r
if [ "$code" = "0" ]; then\r
API_TOKEN=$(echo "$response" | jq -r '.data.token')\r
new_expires=$(echo "$response" | jq -r '.data.expires_at')\r
echo "{\"token\": \"${API_TOKEN}\", \"employee_id\": \"${EMPLOYEE_ID}\", \"employee_name\": \"${EMPLOYEE_NAME}\", \"expires_at\": \"${new_expires}\", \"updated_at\": \"$(iso_now)\"}" > "$TOKEN_CACHE"\r
else\r
echo "⚠️ 登录失败,请确认账号和密码正确"\r
exit 1\r
fi\r
elif [ $days_remaining -le 7 ]; then\r
# Token 即将过期 → renew-token 优先\r
response=$(curl -s -X POST "${FASTAPI_BASE_URL}/auth/renew-token" \\r
-H "Authorization: Bearer ${API_TOKEN}" \\r
--max-time 120)\r
code=$(echo "$response" | jq -r '.code')\r
if [ "$code" = "0" ]; then\r
API_TOKEN=$(echo "$response" | jq -r '.data.token')\r
new_expires=$(echo "$response" | jq -r '.data.expires_at')\r
echo "{\"token\": \"${API_TOKEN}\", \"employee_id\": \"${EMPLOYEE_ID}\", \"employee_name\": \"${EMPLOYEE_NAME}\", \"expires_at\": \"${new_expires}\", \"updated_at\": \"$(iso_now)\"}" > "$TOKEN_CACHE"\r
else\r
# renew 失败 → 引导用户重新输入密码登录\r
echo "⚠️ Token 续期失败,请重新输入密码"\r
echo "(等待用户输入密码...)"\r
\r
response=$(curl -s -X POST "${FASTAPI_BASE_URL}/auth/login" \\r
-H "Content-Type: application/json" \\r
-d "{\"employee_id\": \"${EMPLOYEE_ID}\", \"password\": \"${PASSWORD}\"}" \\r
--max-time 120)\r
code=$(echo "$response" | jq -r '.code')\r
if [ "$code" = "0" ]; then\r
API_TOKEN=$(echo "$response" | jq -r '.data.token')\r
new_expires=$(echo "$response" | jq -r '.data.expires_at')\r
echo "{\"token\": \"${API_TOKEN}\", \"employee_id\": \"${EMPLOYEE_ID}\", \"employee_name\": \"${EMPLOYEE_NAME}\", \"expires_at\": \"${new_expires}\", \"updated_at\": \"$(iso_now)\"}" > "$TOKEN_CACHE"\r
else\r
echo "⚠️ 登录失败,请确认账号和密码正确"\r
exit 1\r
fi\r
fi\r
fi\r
# else: Token 仍有效,直接使用\r
fi\r
else\r
# 缓存文件不存在 → 引导用户输入账号和密码\r
echo "🔑 需要验证您的员工身份"\r
echo ""\r
echo "请输入您的账号和密码,格式:账号 密码"\r
echo "例如:emp-server-106 123456"\r
fi\r
\r
# ═══ Token 校验兜底:确保 Token 有效,否则提示用户重新登录 ═══\r
if [ -z "${API_TOKEN:-}" ] || [ "$API_TOKEN" = "null" ] || [ "$API_TOKEN" = "" ]; then\r
echo "⚠️ 身份验证失败,无法获取有效凭证"\r
echo ""\r
echo "请输入您的账号和密码,重新登录:"\r
echo "格式:账号 密码(例如:emp-server-106 123456)"\r
# AI 引导用户输入后,重新执行 /auth/login 流程\r
fi\r
```\r
\r
> **关键**: 员工身份(`employee_code`)由后端从 Token 自动提取,Skill 不需要在 payload 中传递。\r
> **关键**: 项目名称(`project_name`)由 AI 从对话中识别或用户指定,如未识别则使用 `company_name` 作为默认值。\r
\r
---\r
\r
## Step 2: 解析意图(语义理解)\r
\r
从用户输入中提取以下信息,**由 AI 自行判断,无需正则或关键词表**:\r
\r
| 提取项 | 说明 | 示例 |\r
|--------|------|------|\r
| `company_name` | 用户提到的公司/客户名称 | "陌陌科技"、"数智云创" |\r
| `contact_name` | 用户提到的联系人姓名 | "张三"、"李总" |\r
| `project_name` | 用户提到的项目/系统/产品名 | "CRM项目"、"数据中台" |\r
| `title_keywords` | 用于文件匹配的主题关键词 | "价格谈判"、"方案对比" |\r
| `source_type` | 数据源类型 | `phone` / `wechat` / `auto` |\r
\r
**提取原则**:\r
- 从用户原话中直接提取,不做过度推断\r
- 提取不到就留空,后续步骤有兜底逻辑\r
- 不要因为没匹配到某个模式就认为"无法识别"\r
\r
**数据源判断**:\r
- 提到"电话/录音/通话/拜访" → `phone`\r
- 提到"微信/聊天/消息" → `wechat`\r
- 未明确 → `auto`(两个源都查)\r
\r
**客户标识优先级**:\r
1. `company_name`(公司全称或简称,用户明确提到)→ 直接进入 Step 4\r
2. 无公司名但有 `contact_name` → 进入 Step 3 客户确认\r
3. 都没有但有 `title_keywords` → 用主题关键词匹配文件名,进入 Step 3 客户确认\r
4. 都没有 → 提示用户补充信息(见下方引导模板)\r
\r
**信息不足时的引导提示**:\r
\r
当用户输入过于笼统(如"整理线索"、"跟进记录"、"分析录音"),无法提取客户名或联系人时,**不要猜测**,主动引导用户补充:\r
\r
```\r
请告诉我您想分析的内容,例如:\r
\r
• "分析我和陌陌公司的通话录音"\r
• "查看我和张三的微信聊天记录"\r
• "总结上周拜访客户的录音"\r
• "分析CRM项目的沟通记录"\r
\r
需要指定:客户名称 或 联系人姓名,我会帮您分析拜访记录并生成项目画像。\r
```\r
\r
**项目名称优先级**:\r
1. 用户明确指定(最高优先级)\r
2. AI 在 Step 5 分析时从对话内容自动识别\r
3. 默认使用 `company_name`\r
\r
---\r
\r
## Step 3: 客户确认(公司名不明确时)\r
\r
当 Step 2 未能明确识别 `company_name`(只有 `contact_name` 或 `title_keywords`)时,**先查询数据库已有客户**,让员工确认或选择,避免用错误名称创建新客户。\r
\r
### 判断条件\r
\r
| 条件 | 动作 |\r
|------|------|\r
| `company_name` 已明确(用户直接提到公司名) | **跳过此步**,直接进入 Step 4 |\r
| 仅有 `contact_name` 或 `title_keywords` | **执行客户查询**,让员工确认 |\r
\r
### 查询已有客户\r
\r
```bash\r
FASTAPI_BASE_URL="http://47.116.49.218:8000/api/v1"\r
\r
customers_response=$(curl -s -X GET "${FASTAPI_BASE_URL}/project-portrait/customers" \\r
-H "Authorization: Bearer ${API_TOKEN}" \\r
--max-time 10)\r
\r
customers_code=$(echo "$customers_response" | jq -r '.code')\r
\r
if [ "$customers_code" = "0" ]; then\r
customers=$(echo "$customers_response" | jq -r '.data')\r
customer_count=$(echo "$customers" | jq 'length')\r
fi\r
```\r
\r
### 匹配与展示逻辑\r
\r
```\r
1. 用 contact_name / title_keywords 在客户列表中模糊匹配\r
- 匹配 company_name 字段\r
- 匹配 contact_name 字段\r
\r
2. 匹配结果分支:\r
├─ 精确匹配到 1 个客户 → 自动使用该客户的 company_name(静默确认)\r
├─ 匹配到多个客户 → 展示列表让员工选择\r
├─ 无匹配 → 询问员工输入企业全称\r
└─ 客户列表为空(新员工)→ 询问员工输入企业全称\r
```\r
\r
### 多客户匹配时的展示格式\r
\r
```\r
📋 找到以下相关客户,请选择要分析的客户:\r
\r
1. XX科技有限公司(联系人:王厂长)\r
2. XX智能装备集团(联系人:王建国)\r
3. XX电子有限公司(联系人:王总)\r
\r
请回复序号选择,或输入新的企业名称。\r
```\r
\r
### 无匹配时的引导格式\r
\r
```\r
📋 未在您的客户库中找到与「{contact_name}」匹配的企业。\r
\r
请输入该客户的**企业全称**(如:深圳市XX科技有限公司),我将以此创建新客户档案。\r
```\r
\r
### 确认后\r
\r
员工确认 `company_name` 后,后续 Step 4-7 使用该确认值,确保数据准确。\r
\r
---\r
\r
## Step 4: 查找并读取聊天内容(多源分支)\r
\r
根据 Step 2 解析出的 `source_type` 走不同分支:\r
\r
| source_type | 走哪条路径 | 说明 |\r
|-------------|----------|------|\r
| `phone` | 分支 A:电话录音转录 | 用户明确提到"电话/录音/通话" |\r
| `wechat` | 分支 B:微信聊天记录 | 用户明确提到"微信/聊天记录" |\r
| `auto` | 两个分支都执行,合并结果 | 用户未指定来源 |\r
\r
---\r
\r
### 分支 A:电话录音转录\r
\r
#### A.1 转录文件存储路径\r
\r
```\r
/home/admin/.openclaw/plugins/phone-notifications/recordings/transcripts/\r
```\r
\r
> **性能约束**:该目录可能有数百甚至上千个转录文件,**禁止全量 grep**。必须采用"分层过滤"策略逐步缩小范围。\r
\r
#### A.2 文件名格式\r
\r
`{UUID}_{AI生成的标题}.md`(UUID 固定 36 字符,标题为 AI 从对话内容总结的主题,**不含时间信息**)\r
\r
示例:\r
```\r
611a199a-1fcd-4604-85c6-774bd7160784_哒,开始了,嗯,可以.md\r
8bc14a02-29e3-4bd6-986a-faa69a8bb929_CRM方案价格相对比及商务谈判进展.md\r
```\r
\r
#### A.3 分层过滤策略(4 级缩小范围)\r
\r
#### 第 1 级:限定时间范围(必做,最高效)\r
\r
默认只看最近 30 天内的文件(按文件修改时间)。如果用户明确提到"上个月"/"最近一周",则按用户指定的范围过滤。\r
\r
```bash\r
TRANSCRIPTS_DIR="/home/admin/.openclaw/plugins/phone-notifications/recordings/transcripts"\r
DAYS_BACK="${DAYS_BACK:-30}" # 默认 30 天\r
\r
# 用 find 按 mtime 过滤,只列出时间范围内的 .md 文件\r
recent_files=$(find "$TRANSCRIPTS_DIR" -maxdepth 1 -name "*.md" -type f -mtime -${DAYS_BACK} 2>/dev/null)\r
\r
if [ -z "$recent_files" ]; then\r
echo "⚠️ 最近 ${DAYS_BACK} 天内没有转录文件"\r
echo "尝试扩大范围到 90 天..."\r
DAYS_BACK=90\r
recent_files=$(find "$TRANSCRIPTS_DIR" -maxdepth 1 -name "*.md" -type f -mtime -${DAYS_BACK} 2>/dev/null)\r
fi\r
\r
if [ -z "$recent_files" ]; then\r
echo "⚠️ 90 天内仍无转录文件,请确认录音是否已转录"\r
exit 1\r
fi\r
\r
total_count=$(echo "$recent_files" | wc -l)\r
echo "📂 最近 ${DAYS_BACK} 天内有 ${total_count} 个转录文件"\r
```\r
\r
#### 第 2 级:优先按文件名标题部分匹配(零开销,命中率高)\r
\r
文件名中 UUID 后面是 AI 生成的标题,**标题通常包含客户名、产品名、场景关键词**,命中率很高。先用 `basename` 提取标题部分做字符串匹配(不读文件内容,开销为 0):\r
\r
```bash\r
# 提取标题部分(跳过 UUID 前缀,匹配 UUID 后的内容)\r
# 文件名格式:{UUID}_{标题}.md,UUID 固定 36 字符\r
name_matched=""\r
# 按优先级尝试匹配:公司名 → 联系人 → 主题关键词\r
keywords=()\r
[ -n "$company_name" ] && keywords+=("$company_name")\r
[ -n "$contact_name" ] && keywords+=("$contact_name")\r
# 主题关键词数组(来自 Step 2)\r
for kw in "${title_keywords[@]}"; do keywords+=("$kw"); done\r
\r
for kw in "${keywords[@]}"; do\r
name_matched=$(echo "$recent_files" | while read f; do\r
title_part=$(basename "$f" .md | cut -c38-)\r
echo "$title_part" | grep -qi "$kw" && echo "$f"\r
done)\r
[ -n "$name_matched" ] && break\r
done\r
\r
if [ -n "$name_matched" ]; then\r
matched_files="$name_matched"\r
echo "✅ 文件名标题命中"\r
else\r
# 进入第 3 级\r
fi\r
```\r
\r
#### 第 3 级:小范围内容匹配(只在第 1 级过滤后的文件上做 grep)\r
\r
**只有第 2 级没命中时**,才对第 1 级筛选出来的小集合做内容 grep,**绝不对整个目录 grep**:\r
\r
```bash\r
# 只在 recent_files 上做 grep(而不是 *.md 全量)\r
keyword="${company_name:-$contact_name}"\r
matched_files=$(echo "$recent_files" | xargs grep -l -i "$keyword" 2>/dev/null)\r
\r
if [ -z "$matched_files" ]; then\r
echo "⚠️ 未匹配到包含「${keyword}」的转录文件"\r
echo ""\r
echo "📋 最近 ${DAYS_BACK} 天最新 10 个文件:"\r
echo "$recent_files" | xargs ls -lt 2>/dev/null | head -10 | while read line; do\r
filename=$(echo "$line" | awk '{print $NF}')\r
echo " - $(basename $filename)"\r
done\r
echo ""\r
echo "请回复文件名、具体日期或更多关键词(如客户提到的产品名)"\r
exit 1\r
fi\r
```\r
\r
#### 第 4 级:取最新一条(多条匹配时)\r
\r
```bash\r
# 多条匹配时按 mtime 排序,取最新的\r
target_file=$(echo "$matched_files" | xargs ls -t 2>/dev/null | head -1)\r
echo "📄 匹配到文件:$(basename $target_file)"\r
```\r
\r
#### A.4 读取聊天内容\r
\r
```bash\r
chat_content=$(cat "$target_file")\r
```\r
\r
#### A.5 转录文件格式(电话录音转录)\r
\r
```markdown\r
# CRM方案价格对比及商务谈判进展\r
\r
> 录音名称:2026_06_10 17:04:10\r
> 时长:02:10\r
> 创建时间:2026-06-10T09:04:10.570Z\r
\r
---\r
\r
说话人0:李总,您好!感谢您抽出时间。上次给您发的CRM方案...\r
```\r
\r
**关键特征**:\r
- **标题**:自动生成(主题概括)\r
- **元数据**:`录音名称`(含日期)、`时长`、`创建时间`\r
- **对话**:只有 `说话人0` 标识,包含双方对话内容(无角色分离)\r
\r
**Skill 自动识别并提取**:\r
- **通话日期**:优先从 `录音名称` 行提取 `YYYY_MM_DD`,备选从 `创建时间` 提取 ISO 格式\r
- **完整对话**:`说话人0` 后的全部内容\r
- AI 分析时从上下文推断销售和客户角色\r
\r
---\r
\r
### 分支 B:微信聊天记录\r
\r
#### B.1 微信通知存储路径\r
\r
```\r
/home/admin/.openclaw/plugins/phone-notifications/notifications/\r
```\r
\r
该目录下所有文件是**按日期命名**的 JSON 数组,每个文件记录当天所有 App 推送通知:\r
\r
```\r
notifications/\r
├── 2026-06-04.json\r
├── 2026-06-05.json\r
├── 2026-06-06.json\r
├── ...\r
└── 2026-06-10.json\r
```\r
\r
#### B.2 文件结构(JSON 数组,每条是一个推送通知)\r
\r
```json\r
[\r
{\r
"appName": "微信",\r
"title": "销售伴侣",\r
"content": "吴云成: http://118.196.83.38/ai/salebp/-/tree/develop",\r
"timestamp": "2026-06-09T17:13:42+08:00",\r
"appDisplayName": "微信"\r
},\r
{\r
"appName": "微信",\r
"title": "沈莹玉",\r
"content": "。。。刚看到,晚上中兴加班的",\r
"timestamp": "2026-06-09T21:16:29+08:00",\r
"appDisplayName": "微信"\r
},\r
{\r
"appName": "钉钉",\r
"title": "工作通知:南京绛门信息科技有限公司",\r
"content": "考勤打卡:18:00 极速打卡·成功",\r
"timestamp": "2026-06-09T18:00:21+08:00",\r
"appDisplayName": "钉钉"\r
}\r
]\r
```\r
\r
**字段含义**:\r
| 字段 | 含义 | 示例 |\r
|------|------|------|\r
| `appName` | 应用包名标识 | `"微信"` / `"钉钉"` / `"菜鸟"` |\r
| `title` | 聊天对象名/群名(微信场景) | `"沈莹玉"` / `"销售伴侣群"` |\r
| `content` | 单条消息内容 | `"。。。刚看到,晚上中兴加班的"` |\r
| `timestamp` | 精确时间戳(带时区) | `"2026-06-09T21:16:29+08:00"` |\r
| `appDisplayName` | 应用显示名 | `"微信"` |\r
\r
#### B.3 微信过滤策略(3 级)\r
\r
```bash\r
NOTIFICATIONS_DIR="/home/admin/.openclaw/plugins/phone-notifications/notifications"\r
DAYS_BACK="${DAYS_BACK:-30}"\r
\r
# 第 1 级:按文件名日期过滤(文件名是 YYYY-MM-DD.json,零成本)\r
recent_json_files=$(find "$NOTIFICATIONS_DIR" -maxdepth 1 -name "*.json" -type f -mtime -${DAYS_BACK} 2>/dev/null)\r
\r
if [ -z "$recent_json_files" ]; then\r
echo "⚠️ 最近 ${DAYS_BACK} 天内没有通知文件"\r
exit 1\r
fi\r
\r
# 第 2 级:用 jq 筛选 appName=="微信" 的记录,并按 title 匹配联系人\r
wechat_messages=$(echo "$recent_json_files" | xargs jq -s '\r
[ .[][] | select(.appName == "微信") ]\r
' 2>/dev/null)\r
\r
# 第 3 级:按 title(联系人/群名)过滤\r
if [ -n "$contact_name" ]; then\r
wechat_messages=$(echo "$wechat_messages" | jq --arg name "$contact_name" '\r
[ .[] | select(.title | test($name; "i")) ]\r
')\r
elif [ -n "$company_name" ]; then\r
wechat_messages=$(echo "$wechat_messages" | jq --arg name "$company_name" '\r
[ .[] | select(.title | test($name; "i")) ]\r
')\r
fi\r
\r
msg_count=$(echo "$wechat_messages" | jq 'length')\r
if [ "$msg_count" -eq 0 ]; then\r
echo "⚠️ 未找到与「${contact_name:-$company_name}」相关的微信聊天记录"\r
echo ""\r
echo "📋 最近 ${DAYS_BACK} 天微信联系人统计:"\r
echo "$wechat_messages" | jq -r '[ .[].title ] | group_by(.) | map({name: .[0], count: length}) | sort_by(-.count) | .[:10][] | " - \(.name) (\(.count) 条)"'\r
exit 1\r
fi\r
\r
echo "✅ 找到 ${msg_count} 条与「${contact_name:-$company_name}」的微信消息"\r
```\r
\r
#### B.4 聚合消息为对话格式(供 AI 分析)\r
\r
把同一联系人的多条消息按时间排序,拼接为对话格式(类似录音转录):\r
\r
```bash\r
chat_content=$(echo "$wechat_messages" | jq -r '\r
sort_by(.timestamp) |\r
.[] |\r
"[\(.timestamp | split("T")[1] | split("+")[0])] \(.title): \(.content)"\r
')\r
\r
# 提取最早消息日期作为 visit_date\r
visit_date=$(echo "$wechat_messages" | jq -r '\r
sort_by(.timestamp) | .[0].timestamp | split("T")[0]\r
')\r
\r
# 设置 transcript_file_path 为虚拟路径(表明数据源)\r
transcript_file_path="wechat://${contact_name:-$company_name}/${visit_date}"\r
```\r
\r
聚合后的 `chat_content` 示例:\r
```\r
[21:16:29] 沈莹玉: 。。。刚看到,晚上中兴加班的\r
[21:17:14] 沈莹玉: 晚上吧\r
[21:17:22] 沈莹玉: 明晚\r
```\r
\r
#### B.5 微信 vs 电话录音差异\r
\r
| 维度 | 电话录音 | 微信聊天 |\r
|------|---------|---------|\r
| 粒度 | 一次通话=一个文件 | 单条消息,需聚合 |\r
| 角色分离 | 无(全在 `说话人0`) | 仅我方收到的消息(`title`=对方) |\r
| 时间信息 | 需从内容提取 | `timestamp` 字段直接可用 |\r
| 完整性 | 完整对话 | 仅通知栏内容(撤回/图片/语音可能缺失) |\r
| 虚拟路径 | 真实文件路径 | `wechat://{联系人}/{日期}` |\r
\r
---\r
\r
## Step 5: AI 分析聊天转录内容(核心)\r
\r
### 分析提示词模板\r
\r
```markdown\r
# 角色\r
你是资深 B2B 销售分析师,精通 LTC(Lead to Cash)销售流程。\r
\r
# 任务\r
根据以下拜访沟通记录,分析生成项目画像的 5 大模块数据,并**识别项目名称**。\r
\r
# 输入数据\r
- 公司名称:{company_name}(优先)\r
- 联系人:{contact_name}(备选)\r
- 项目名称:{project_name}(如已提取)\r
- 聊天文件:{target_file}\r
- 聊天内容:{chat_content}\r
\r
# 输出要求(严格 JSON 格式)\r
\r
{\r
"project_name": "识别出的项目名称(如CRM系统采购、数据中台建设等)",\r
"sales_stage": {\r
"steps": [\r
{"label": "线索", "active": false},\r
{"label": "商机确认", "active": false},\r
{"label": "方案评估", "active": false},\r
{"label": "商务谈判", "active": false},\r
{"label": "赢单/关单", "active": false}\r
],\r
"progress_percent": 60,\r
"current_stage": "方案评估",\r
"description": "基于沟通记录,客户已明确需求并进入方案对比阶段..."\r
},\r
"follow_up_strategies": [\r
{\r
"icon": "fa-calendar-check",\r
"title": "跟进策略标题",\r
"description": "具体跟进动作描述",\r
"tag_icon": "fa-tag",\r
"tag_text": "策略标签"\r
}\r
],\r
"customer_insights": [\r
{"title": "客户意向", "content": "..."},\r
{"title": "项目新增线索", "content": "..."},\r
{"title": "客户关注点", "content": "..."},\r
{"title": "个人诉求", "content": "..."}\r
],\r
"commitments": [\r
{\r
"type": "our_promise",\r
"type_label": "我方承诺",\r
"content": "承诺内容",\r
"meta": "责任人/时间"\r
},\r
{\r
"type": "customer_promise",\r
"type_label": "客户承诺",\r
"content": "客户承诺内容",\r
"meta": "客户方责任人"\r
}\r
],\r
"risk_assessment": [\r
{"title": "影响项目推进信息", "content": "...", "level": "high"},\r
{"title": "影响价格谈判信息", "content": "...", "level": "medium"},\r
{"title": "影响成单信息", "content": "...", "level": "low"},\r
{"title": "竞品相关信息", "content": "...", "level": "medium"}\r
],\r
"visit_summary": "本次拜访的核心总结(100字以内)"\r
}\r
\r
# 分析维度说明\r
\r
## 0. 项目名称识别(新增)\r
从对话内容中识别项目名称,常见特征:\r
- 明确提及:"关于你们的**CRM系统项目**"、"**数据中台建设**的进展"\r
- 项目代号:"**凤凰计划**"、"**星辰工程**"\r
- 产品/系统名:"**销售管理系统**"、"**客户数据平台**"\r
- 如无法识别,返回空字符串 ""(后端将使用 company_name 作为默认值)\r
\r
## 1. 销售阶段(基于 LTC 流程)\r
- 线索:初次接触,尚未明确需求\r
- 商机确认:需求明确,确认有采购意向\r
- 方案评估:客户对比多家方案,进入评估阶段\r
- 商务谈判:方案已认可,进入价格/条款谈判\r
- 赢单/关单:合同签订或项目关闭\r
根据沟通记录判断当前阶段,设置 active=true,progress_percent 对应 20/40/60/80/100。\r
\r
## 2. 跟进策略(4个维度,至少 3 条)\r
(1) 当前销售阶段 → 下一阶段的推进动作\r
(2) 既往/本次承诺事项 → 需要跟进的兑现动作\r
(3) 项目风险 → 需要化解风险的跟进动作\r
(4) 客户最新动态/意向 → 需要抓住机会的跟进动作\r
\r
## 3. 客户洞察(至少 3 条)\r
- 客户意向、项目新增线索、客户关注点、个人诉求\r
\r
## 4. 承诺事项(识别所有承诺)\r
- our_promise:我方承诺(如"下周提供方案"、"月底前给报价")\r
- customer_promise:客户承诺(如"下周内部讨论"、"月底给答复")\r
- customer_request:客户要求(如"希望增加某功能"、"要求降价")\r
\r
## 5. 风险预估\r
- 从沟通记录中识别所有潜在风险,不限于固定类别\r
- 每条风险标注等级:`high` / `medium` / `low`\r
- **由 AI 根据对话内容自行判断等级**,不要套用固定规则:\r
- 如果客户明确表示不合作、项目暂停、预算砍掉 → `high`\r
- 如果客户有顾虑、需要额外审批、竞品介入 → `medium`\r
- 如果只是常规提醒、轻微分歧 → `low`\r
- 没有明显风险时,输出空数组 `[]`\r
\r
# 重要规则\r
1. 所有分析必须基于实际沟通记录,绝不编造\r
2. 如果某维度无相关信息,输出空数组 []\r
3. description 和 content 要具体、可执行,不要泛泛而谈\r
4. 使用中文输出\r
```\r
\r
### 提取日期(用于 `visit_date`)\r
\r
```bash\r
# 优先从"录音名称"行提取 YYYY_MM_DD\r
visit_date=$(echo "$chat_content" | grep "录音名称" | grep -oE '[0-9]{4}_[0-9]{2}_[0-9]{2}' | tr '_' '-' | head -1)\r
\r
if [ -z "$visit_date" ]; then\r
# 备选:从"创建时间"行提取 ISO 格式\r
visit_date=$(echo "$chat_content" | grep "创建时间" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -1)\r
fi\r
\r
if [ -z "$visit_date" ]; then\r
# 兜底:文件修改时间\r
visit_date=$(date +%Y-%m-%d)\r
fi\r
```\r
\r
---\r
\r
## Step 6: 保存项目画像\r
\r
### 请求地址\r
\r
| 项 | 说明 |\r
|---|---|\r
| URL | `POST ${FASTAPI_BASE_URL}/project-portrait/ingest` |\r
| 认证 | `Authorization: Bearer ${API_TOKEN}`(员工 Token) |\r
| Content-Type | `application/json` |\r
\r
### 请求参数\r
\r
```json\r
{\r
"company_name": "string (必填,公司名优先;无公司名时传联系人)",\r
"project_name": "string (可选,项目名称;AI识别或用户指定,为空时后端使用 company_name)",\r
"contact_name": "string (可选,联系人姓名)",\r
"transcript_file_path": "string (可选,电话录音为真实文件路径;微信为虚拟路径 wechat://{联系人}/{日期})",\r
"visit_date": "string (YYYY-MM-DD)",\r
"data_source": "string (电话录音转录 | 微信聊天记录)",\r
"sales_stage": {\r
"steps": [{"label": "线索", "active": false}, ...],\r
"progress_percent": 60,\r
"current_stage": "方案评估",\r
"description": "..."\r
},\r
"follow_up_strategies": [\r
{"icon": "", "title": "", "description": "", "tag_icon": "", "tag_text": ""}\r
],\r
"customer_insights": [\r
{"title": "", "content": ""}\r
],\r
"commitments": [\r
{"type": "our_promise|customer_promise|customer_request", "type_label": "", "content": "", "meta": ""}\r
],\r
"risk_assessment": [\r
{"title": "", "content": "", "level": "low"}\r
],\r
"raw_analysis": "string (可选,AI 原始返回)",\r
"visit_summary": "string (可选,100字以内总结)"\r
}\r
```\r
\r
### 响应格式\r
\r
```json\r
{\r
"code": 0,\r
"data": {\r
"portrait_id": 123,\r
"ingested": true\r
},\r
"message": "ok"\r
}\r
```\r
\r
### 执行脚本\r
\r
```bash\r
FASTAPI_BASE_URL="http://47.116.49.218:8000/api/v1"\r
\r
INGEST_PAYLOAD=$(jq -n \\r
--arg company_name "$company_name" \\r
--arg project_name "${project_name:-}" \\r
--arg contact_name "${contact_name:-}" \\r
--arg transcript_file_path "$target_file" \\r
--arg visit_date "$visit_date" \\r
--arg data_source "电话录音转录" \\r
--argjson sales_stage "$(echo "$parsed_result" | jq '.sales_stage')" \\r
--argjson follow_up_strategies "$(echo "$parsed_result" | jq '.follow_up_strategies')" \\r
--argjson customer_insights "$(echo "$parsed_result" | jq '.customer_insights')" \\r
--argjson commitments "$(echo "$parsed_result" | jq '.commitments')" \\r
--argjson risk_assessment "$(echo "$parsed_result" | jq '.risk_assessment')" \\r
--arg raw_analysis "$(echo "$parsed_result" | jq -r '.' | head -c 10000)" \\r
--arg visit_summary "$(echo "$parsed_result" | jq -r '.visit_summary')" \\r
'{\r
company_name: $company_name,\r
project_name: $project_name,\r
contact_name: $contact_name,\r
transcript_file_path: $transcript_file_path,\r
visit_date: $visit_date,\r
data_source: $data_source,\r
sales_stage: $sales_stage,\r
follow_up_strategies: $follow_up_strategies,\r
customer_insights: $customer_insights,\r
commitments: $commitments,\r
risk_assessment: $risk_assessment,\r
raw_analysis: $raw_analysis,\r
visit_summary: $visit_summary\r
}')\r
\r
ingest_response=$(curl -s -X POST "${FASTAPI_BASE_URL}/project-portrait/ingest" \\r
-H "Content-Type: application/json" \\r
-H "Authorization: Bearer ${API_TOKEN}" \\r
-d "$INGEST_PAYLOAD" \\r
--max-time 30)\r
\r
ingest_code=$(echo "$ingest_response" | jq -r '.code')\r
\r
if [ "$ingest_code" = "0" ]; then\r
echo "✅ 项目画像已生成"\r
PORTRAIT_ID=$(echo "$ingest_response" | jq -r '.data.portrait_id')\r
else\r
echo "⚠️ 生成失败:$(echo "$ingest_response" | jq -r '.message')"\r
mkdir -p /tmp/sales-companion-fallback\r
echo "$INGEST_PAYLOAD" > "/tmp/sales-companion-fallback/portrait_$(date +%s).json"\r
exit 1\r
fi\r
```\r
\r
### 后端自动处理(无需 Skill 感知)\r
\r
请求被处理后,系统会自动完成:\r
\r
1. 自动关联或创建客户信息\r
2. 建立员工与客户的归属关系\r
3. 增量更新或首次创建项目画像\r
4. 返回画像 ID\r
\r
> **数据隔离**:`employee_code + company_name + project_name` 联合确定唯一画像。同一公司同一项目,员工 A 和员工 B 的画像互相隔离,互不可见。同一公司不同项目,画像也互相独立。\r
\r
---\r
\r
## Step 7: 输出摘要 + H5 链接\r
\r
### 生成 H5 链接(优先换码,兜底完整 Token)\r
\r
```bash\r
exchange_response=$(curl -s -X POST "${FASTAPI_BASE_URL}/auth/exchange-code" \\r
-H "Authorization: Bearer ${API_TOKEN}" \\r
-H "Content-Type: application/json" \\r
-d '{}' \\r
--max-time 10)\r
\r
exchange_code=$(echo "$exchange_response" | jq -r '.data.code // empty')\r
\r
H5_BASE_URL="http://47.116.49.218:5173"\r
\r
if [ -n "$exchange_code" ] && [ "$exchange_code" != "null" ]; then\r
h5_url="${H5_BASE_URL}/project-portrait-new/${PORTRAIT_ID}?code=${exchange_code}"\r
else\r
# 兜底:使用完整 Token(禁止截断、禁止带 ...)\r
h5_url="${H5_BASE_URL}/project-portrait-new/${PORTRAIT_ID}?token=${API_TOKEN}"\r
fi\r
```\r
\r
### 输出格式\r
\r
```markdown\r
📊 {company_name} · {project_name} · 项目画像分析\r
\r
📅 聊天日期:{visit_date}\r
📱 数据源:{data_source} ← "电话录音转录" 或 "微信聊天记录"\r
👤 联系人:{contact_name}\r
📍 当前阶段:{sales_stage.current_stage}(进度 {sales_stage.progress_percent}%)\r
\r
🎯 核心总结:{visit_summary}\r
\r
📈 [查看完整项目画像 →]({h5_url})\r
\r
────────────────────────────────────────\r
\r
💡 **下一步行动建议**\r
\r
分析完成后,建议将此项目同步到 CRM 管理系统,方便后续跟进和团队协作。\r
\r
🔄 [同步到 CRM 管理 →] 对我说"同步到CRM"\r
\r
────────────────────────────────────────\r
\r
🔒 数据隔离:此分析记录属于员工 {employee_code},项目 {project_name}\r
```\r
\r
---\r
\r
\r
\r
---\r
\r
## 数据库 Schema(参考)\r
\r
### project_portraits 表\r
\r
| 字段 | 类型 | 说明 |\r
|------|------|------|\r
| id | INTEGER PK | 画像 ID(URL 中使用) |\r
| employee_code | VARCHAR INDEX | 员工号(数据隔离键) |\r
| company_name | VARCHAR INDEX | 公司名称 |\r
| project_name | VARCHAR INDEX | 项目名称(数据隔离键:employee_code + company_name + project_name) |\r
| contact_name | VARCHAR | 联系人 |\r
| transcript_file_path | VARCHAR | 转录文件路径 |\r
| visit_date | VARCHAR | 聊天日期 |\r
| data_source | VARCHAR | 数据来源 |\r
| sales_stage | JSON TEXT | 销售阶段 |\r
| follow_up_strategies | JSON TEXT | 跟进策略(列表) |\r
| customer_insights | JSON TEXT | 客户洞察(列表) |\r
| commitments | JSON TEXT | 承诺事项(列表) |\r
| risk_assessment | JSON TEXT | 风险预估(列表) |\r
| raw_analysis | TEXT | AI 原始返回 |\r
| visit_summary | TEXT | 拜访总结 |\r
| created_at / updated_at | DATETIME | 时间戳 |\r
\r
### customers 表 & employee_customers 表\r
\r
`ingest` 接口会自动维护这两张表,Skill 无需感知。\r
\r
---\r
\r
## 错误处理\r
\r
| 错误场景 | 处理方式 |\r
|---------|---------|\r
| 无匹配客户 | 输出相似客户列表,让用户选择 |\r
| 无拜访记录 | 提示录入拜访沟通内容 |\r
| 拜访记录无沟通内容 | 提示补充对话记录 |\r
| AI 分析失败 | 提示"分析失败,请重试" |\r
| 保存失败 | 兜底写入 `/tmp/sales-companion-fallback/`,提示联系管理员 |\r
| Token 过期或失效 | 自动续期;续期失败则提示用户输入账号和密码重新登录 |\r
\r
---\r
\r
## 使用示例\r
\r
### 示例 1:电话录音分析\r
\r
```\r
用户:查看我和陌陌公司CRM项目的聊天分析\r
\r
输出:\r
📊 陌陌公司 · CRM项目 · 项目画像分析\r
\r
📅 聊天日期:2026-06-10\r
� 数据源:电话录音转录\r
� 联系人:李总\r
📍 当前阶段:方案评估(进度 60%)\r
\r
🎯 核心总结:客户已明确需求,正在对比 3 家方案,对我方技术方案认可度高,但价格敏感...\r
\r
📈 [查看完整项目画像 →]({h5_url})\r
\r
────────────────────────────────────────\r
\r
💡 **下一步行动建议**\r
\r
分析完成后,建议将此项目同步到 CRM 管理系统,方便后续跟进和团队协作。\r
\r
�🔄 [同步到 CRM 管理 →] 对我说"同步到CRM"\r
\r
────────────────────────────────────────\r
\r
🔒 数据隔离:此分析记录属于员工 E10001,项目 CRM项目\r
```\r
\r
### 示例 2:微信聊天分析\r
\r
```\r
用户:查看我和沈莹玉的微信聊天记录\r
\r
输出:\r
📊 沈莹玉 · Mochac 科技 · 项目画像分析\r
\r
📅 聊天日期:2026-06-09\r
📱 数据源:微信聊天记录\r
👤 联系人:沈莹玉\r
📍 当前阶段:商机确认(进度 40%)\r
\r
🎯 核心总结:客户对方案表现出兴趣,但提及晚上加班,时间紧张...\r
\r
📈 [查看完整项目画像 →]({h5_url})\r
\r
────────────────────────────────────────\r
\r
💡 **下一步行动建议**\r
\r
分析完成后,建议将此项目同步到 CRM 管理系统,方便后续跟进和团队协作。\r
\r
🔄 [同步到 CRM 管理 →] 对我说"同步到CRM"\r
\r
────────────────────────────────────────\r
```\r
\r
---\r
\r
## 与 employee-radar 的区别\r
\r
| 对比项 | employee-radar | visit-analyzer |\r
|--------|---------------|----------------|\r
| 数据来源 | 公域(官网/公众号/招投标/招聘) | 私域(拜访沟通记录) |\r
| 分析对象 | 企业整体情报 | 具体项目/拜访 |\r
| 输出内容 | 客户画像 + 企业动态 + AI商机 | 销售阶段 + 跟进策略 + 客户洞察 + 承诺事项 + 风险预估 |\r
| H5 路由 | `/intelligence-radar/{companyId}` | `/project-portrait-new/{portraitId}` |\r
| 触发场景 | "查询XX公司情报" | "查看我和XX的聊天分析" |\r
| 生成方式 | 查询缓存 → 触发采集 | 直接生成画像(自动创建客户) |\r
| 数据隔离 | 企业级 | **员工+项目级**(`employee_code + company_name + project_name`) |\r
\r
---\r
\r
## 变更记录\r
\r
| 版本 | 日期 | 变更 |\r
|------|------|------|\r
| v1.0 | 2026-06-10 | 新建 visit-analyzer Skill,实现拜访记录 AI 分析 |\r
| v1.1 | 2026-06-10 | 工作流从 10 步简化为 6 步,删除 Customer/Visit 手动创建逻辑 |\r
| v1.2 | 2026-06-10 | 彻底移除 visits 表相关描述;明确 visits 表已删除,改用 transcript_file_path 引用原始录音;明确 ingest 接口自动创建 Customer + EmployeeCustomer;补充数据隔离说明(employee_code + company_name);更新 API Schema 和 H5 链接格式 |\r
| v1.3 | 2026-06-10 | Step 3 文件匹配策略从"全量 grep"优化为"4 级分层过滤"(时间范围 find → 文件名匹配 → 小范围内容 grep → 取最新),避免在数千文件上全量扫描 |\r
| v1.4 | 2026-06-10 | 明确实际文件名格式 `{UUID}_{AI标题}.md`(无时间信息,标题为 AI 生成);Step 2 新增 `title_keywords` 主题关键词提取(CRM/方案/价格/谈判等);Step 3 第 2 级改为提取 UUID 后的标题部分匹配,按 company_name → contact_name → title_keywords 优先级尝试,提升命中率 |\r
| v1.5 | 2026-06-10 | **新增微信聊天记录数据源**:Step 2 新增 `source_type` 判断(wechat/phone/auto);Step 3 重构为多源分支(A 电话录音 / B 微信通知 JSON);微信源使用 `jq` 按 appName+title 过滤,聚合同联系人多消息为对话格式;`data_source` 支持"微信聊天记录",`transcript_file_path` 使用虚拟路径 `wechat://{联系人}/{日期}`;新增示例 4/5(微信场景和自动合并) |\r
| v1.6 | 2026-06-11 | **意图解析从规则引擎改为语义理解**:删除 Step 2 的正则伪代码和硬编码关键词表,改为 AI 自行判断;5 个详细示例精简为 2 个(电话录音 + 微信聊天),只展示输出格式不展示内部步骤流转;删除两个重复的"性能对比"表;输出格式新增"同步到 CRM"引导提示 |\r
| v1.7 | 2026-06-13 | **新增客户确认步骤(Step 3)**:工作流从 6 步升级为 7 步;当 Step 2 未明确识别 `company_name` 时,调用 `GET /project-portrait/customers` 查询员工已有客户列表,模糊匹配后展示给员工选择或输入新企业名;避免用错误名称创建新客户产生脏数据;客户标识优先级更新,仅 `contact_name`/`title_keywords` 时进入客户确认而非直接回退 |\r
安全使用建议
Install only if you trust the publisher and backend operator, understand that the skill may ask for employee passwords in chat, read local call transcripts and WeChat notification records, send derived analysis to a remote IP-based service, and cache or expose tokens for later access. A safer version would use platform-native authentication, explicit consent before reading each data source, narrower triggers, and selected conversation files rather than broad notification archives.
能力评估
Purpose & Capability
The visit-analysis workflow is coherent, but it expands into in-chat employee login, password change, token renewal, local private-message analysis, remote portrait creation, and H5 token/link generation.
Instruction Scope
Triggers include broad phrases such as sales stage and re-summarize, while the activated workflow can request credentials, read local transcripts or notification archives, and send analysis results to a remote backend.
Install Mechanism
The artifact is a single markdown skill requiring curl, jq, and python3; no executable installer or package payload was present, and static scan plus VirusTotal telemetry were clean.
Credentials
Reading recent phone transcripts and WeChat notification JSON can fit the stated analysis purpose, but scanning general notification files and aggregating private messages is broader than a narrowly selected conversation export.
Persistence & Privilege
The skill stores an API token under ~/.openclaw/workspace/scripts, can delete that cache on account switch, writes failed ingest payloads to /tmp, and may place a full API token in an H5 URL as fallback.
如何使用
- 确保已安装 OpenClaw(本地或 Docker 部署)
- 在对话框中输入安装命令:
/install visit-analyzer - 安装完成后,直接呼叫该 Skill 的名称或使用
/visit-analyzer触发 - 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
版本历史
v1.0.0
- Initial release of visit-analyzer, an AI-driven visit record analysis engine.
- Automatically analyzes employee visit communication records to assess sales stage, follow-up strategy, customer insights, commitments, and risk estimates.
- Generates client/project profiles and provides an H5 link for full data viewing.
- Supports multiple data sources: phone call transcripts and WeChat chat records, incrementally merging data for the same client.
- Fully automated token management with layered renewal; only asks for account credentials on first use or token expiration.
- Ensures all analysis is strictly based on real visit records, without fabricated content.
元数据
常见问题
visit-analyzer 是什么?
拜访记录分析引擎。根据员工的拜访沟通记录,AI 分析销售阶段、跟进策略、客户洞察、承诺事项和风险预估。当员工要求查看某个客户/公司的聊天分析时触发,自动生成项目画像并输出 H5 链接。 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 31 次。
如何安装 visit-analyzer?
在 OpenClaw 或 Claude Code 对话框中运行命令「/install visit-analyzer」即可一键安装,无需额外配置。
visit-analyzer 是免费的吗?
是的,visit-analyzer 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。
visit-analyzer 支持哪些平台?
visit-analyzer 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(linux, darwin)。
谁开发了 visit-analyzer?
由 vivalavida-say-hi(@vivalavida-say-hi)开发并维护,当前版本 v1.0.0。
推荐 Skills