deploy: 2026-07-15 14:57 部署更新
This commit is contained in:
+40
-13
@@ -910,19 +910,47 @@ def generate_paradigm(
|
|||||||
{dialog_text}
|
{dialog_text}
|
||||||
|
|
||||||
## 输出要求
|
## 输出要求
|
||||||
请基于上述产品详情(定位、卖点、目标人群)和真实对话,提炼并生成一套**标准范式对话模板**,即针对此类客户的最优销售话术流程。话术要自然融入产品的核心卖点和定位,不要生硬推销。输出严格 JSON:
|
请基于上述产品详情(定位、卖点、目标人群)和真实对话,提炼并生成一套**标准范式对话模板**,即针对此类客户的最优销售话术流程。话术要自然融入产品的核心卖点和定位,不要生硬推销。
|
||||||
- customer_type: 客户类型描述(如"湿疹宝妈-价格敏感型")
|
|
||||||
- product_positioning: 对该客户的产品推荐理由(结合产品定位和客户需求,1-2句话)
|
|
||||||
- stages: 按销售阶段排列的对话步骤数组,每个元素包含:
|
|
||||||
- stage: 阶段名称(建立联系/需求发现/报价/异议处理/成交)
|
|
||||||
- goal: 该阶段目标
|
|
||||||
- sales_script: 销售话术(1-3句,具体可执行,自然融入产品卖点)
|
|
||||||
- expected_response: 预期客户回应
|
|
||||||
- tips: 该阶段技巧提示
|
|
||||||
- key_techniques: 核心销售技巧总结(数组,每条一句话)
|
|
||||||
- improvement_points: 真实对话中可改进的点(数组,每条一句话)
|
|
||||||
|
|
||||||
只输出 JSON,不要其他文字。"""
|
请用 Markdown 格式输出,结构如下:
|
||||||
|
|
||||||
|
## 客户类型
|
||||||
|
(描述客户类型,如"湿疹宝妈-价格敏感型")
|
||||||
|
|
||||||
|
## 产品推荐理由
|
||||||
|
(结合产品定位和客户需求,1-2句话说明为什么推荐这个产品)
|
||||||
|
|
||||||
|
## 标准销售流程
|
||||||
|
|
||||||
|
### 1. 建立联系
|
||||||
|
- **目标**:xxx
|
||||||
|
- **销售话术**:xxx
|
||||||
|
- **预期回应**:xxx
|
||||||
|
- **技巧提示**:xxx
|
||||||
|
|
||||||
|
### 2. 需求发现
|
||||||
|
(同上格式)
|
||||||
|
|
||||||
|
### 3. 报价
|
||||||
|
(同上格式)
|
||||||
|
|
||||||
|
### 4. 异议处理
|
||||||
|
(同上格式)
|
||||||
|
|
||||||
|
### 5. 成交
|
||||||
|
(同上格式)
|
||||||
|
|
||||||
|
## 核心销售技巧
|
||||||
|
- 技巧1
|
||||||
|
- 技巧2
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## 真实对话可改进的点
|
||||||
|
- 改进点1
|
||||||
|
- 改进点2
|
||||||
|
- ...
|
||||||
|
|
||||||
|
直接输出 Markdown 内容,不要输出 JSON。"""
|
||||||
|
|
||||||
body = json.dumps({
|
body = json.dumps({
|
||||||
"model": "qwen-plus",
|
"model": "qwen-plus",
|
||||||
@@ -930,7 +958,6 @@ def generate_paradigm(
|
|||||||
{"role": "system", "content": "你是微信销售对话分析专家,擅长从真实对话中提炼标准范式。"},
|
{"role": "system", "content": "你是微信销售对话分析专家,擅长从真实对话中提炼标准范式。"},
|
||||||
{"role": "user", "content": prompt},
|
{"role": "user", "content": prompt},
|
||||||
],
|
],
|
||||||
"response_format": {"type": "json_object"},
|
|
||||||
"temperature": 0.4,
|
"temperature": 0.4,
|
||||||
"stream": True,
|
"stream": True,
|
||||||
"stream_options": {"include_usage": False},
|
"stream_options": {"include_usage": False},
|
||||||
|
|||||||
+72
-18
@@ -1138,10 +1138,10 @@ async function generateParadigm() {
|
|||||||
<svg class="animate-spin w-5 h-5 text-green-600" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
<svg class="animate-spin w-5 h-5 text-green-600" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
||||||
<span class="text-sm text-gray-500">正在调用千问 LLM 生成标准范式对话...</span>
|
<span class="text-sm text-gray-500">正在调用千问 LLM 生成标准范式对话...</span>
|
||||||
</div>
|
</div>
|
||||||
<pre id="paradigm-stream-text" class="text-xs text-gray-600 bg-gray-50 rounded p-3 max-h-96 overflow-auto whitespace-pre-wrap break-all font-mono"></pre>
|
<div id="paradigm-md" class="text-sm leading-relaxed"></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
const streamText = document.getElementById('paradigm-stream-text');
|
const mdContainer = document.getElementById('paradigm-md');
|
||||||
let fullContent = '';
|
let fullContent = '';
|
||||||
try {
|
try {
|
||||||
const productId = document.getElementById('paradigm-product-select').value;
|
const productId = document.getElementById('paradigm-product-select').value;
|
||||||
@@ -1152,6 +1152,15 @@ async function generateParadigm() {
|
|||||||
const reader = resp.body.getReader();
|
const reader = resp.body.getReader();
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
let buffer = '';
|
let buffer = '';
|
||||||
|
let renderRaf = null;
|
||||||
|
function scheduleRender() {
|
||||||
|
if (renderRaf) return;
|
||||||
|
renderRaf = requestAnimationFrame(() => {
|
||||||
|
renderRaf = null;
|
||||||
|
mdContainer.innerHTML = renderMarkdown(fullContent);
|
||||||
|
mdContainer.scrollTop = mdContainer.scrollHeight;
|
||||||
|
});
|
||||||
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
@@ -1167,28 +1176,19 @@ async function generateParadigm() {
|
|||||||
if (obj.error) throw new Error(obj.error);
|
if (obj.error) throw new Error(obj.error);
|
||||||
if (obj.content) {
|
if (obj.content) {
|
||||||
fullContent += obj.content;
|
fullContent += obj.content;
|
||||||
streamText.textContent = fullContent;
|
scheduleRender();
|
||||||
streamText.scrollTop = streamText.scrollHeight;
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 忽略解析错误的行
|
// 忽略解析错误的行
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 尝试解析完整 JSON 并渲染结构化结果
|
// 最终渲染
|
||||||
try {
|
if (renderRaf) cancelAnimationFrame(renderRaf);
|
||||||
const data = JSON.parse(fullContent);
|
mdContainer.innerHTML = renderMarkdown(fullContent);
|
||||||
renderParadigm(data);
|
// 移除加载动画
|
||||||
} catch (e) {
|
const loader = result.querySelector('.animate-spin');
|
||||||
// JSON 解析失败,保留原始流式文本
|
if (loader) loader.parentElement.remove();
|
||||||
result.innerHTML = `
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<h3 class="text-lg font-bold mb-2">标准范式对话模板</h3>
|
|
||||||
<p class="text-sm text-orange-500 mb-3">LLM 返回内容无法解析为 JSON,以下为原始输出:</p>
|
|
||||||
<pre class="text-xs text-gray-600 bg-gray-50 rounded p-3 max-h-96 overflow-auto whitespace-pre-wrap break-all">${escapeHtml(fullContent)}</pre>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
result.innerHTML = `<p class="text-sm text-red-500">生成失败: ${e.message}</p>`;
|
result.innerHTML = `<p class="text-sm text-red-500">生成失败: ${e.message}</p>`;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1197,6 +1197,60 @@ async function generateParadigm() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 轻量 Markdown 转 HTML 渲染器 */
|
||||||
|
function renderMarkdown(md) {
|
||||||
|
if (!md) return '';
|
||||||
|
const lines = md.split('\n');
|
||||||
|
let html = '';
|
||||||
|
let inList = false;
|
||||||
|
let inOrderedList = false;
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
let line = lines[i];
|
||||||
|
// 标题
|
||||||
|
if (/^### /.test(line)) {
|
||||||
|
if (inList) { html += '</ul>'; inList = false; }
|
||||||
|
if (inOrderedList) { html += '</ol>'; inOrderedList = false; }
|
||||||
|
html += `<h4 class="font-semibold text-base mt-4 mb-2 text-gray-800">${inlineMd(line.slice(4))}</h4>`;
|
||||||
|
} else if (/^## /.test(line)) {
|
||||||
|
if (inList) { html += '</ul>'; inList = false; }
|
||||||
|
if (inOrderedList) { html += '</ol>'; inOrderedList = false; }
|
||||||
|
html += `<h3 class="font-bold text-lg mt-5 mb-2 text-gray-900 border-b pb-1">${inlineMd(line.slice(3))}</h3>`;
|
||||||
|
} else if (/^# /.test(line)) {
|
||||||
|
if (inList) { html += '</ul>'; inList = false; }
|
||||||
|
if (inOrderedList) { html += '</ol>'; inOrderedList = false; }
|
||||||
|
html += `<h2 class="font-bold text-xl mt-5 mb-3 text-gray-900">${inlineMd(line.slice(2))}</h2>`;
|
||||||
|
} else if (/^[-*] /.test(line)) {
|
||||||
|
if (!inList) { html += '<ul class="space-y-1 ml-4 list-disc">'; inList = true; }
|
||||||
|
html += `<li>${inlineMd(line.slice(2))}</li>`;
|
||||||
|
} else if (/^\d+\. /.test(line)) {
|
||||||
|
if (!inOrderedList) { html += '<ol class="space-y-1 ml-5 list-decimal">'; inOrderedList = true; }
|
||||||
|
html += `<li>${inlineMd(line.replace(/^\d+\. /, ''))}</li>`;
|
||||||
|
} else if (line.trim() === '') {
|
||||||
|
if (inList) { html += '</ul>'; inList = false; }
|
||||||
|
if (inOrderedList) { html += '</ol>'; inOrderedList = false; }
|
||||||
|
html += '';
|
||||||
|
} else {
|
||||||
|
if (inList) { html += '</ul>'; inList = false; }
|
||||||
|
if (inOrderedList) { html += '</ol>'; inOrderedList = false; }
|
||||||
|
html += `<p class="mb-2">${inlineMd(line)}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inList) html += '</ul>';
|
||||||
|
if (inOrderedList) html += '</ol>';
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 行内 Markdown:粗体、行内代码、链接 */
|
||||||
|
function inlineMd(text) {
|
||||||
|
// 转义 HTML
|
||||||
|
text = escapeHtml(text);
|
||||||
|
// 粗体 **text**
|
||||||
|
text = text.replace(/\*\*(.+?)\*\*/g, '<strong class="font-semibold text-gray-900">$1</strong>');
|
||||||
|
// 行内代码 `code`
|
||||||
|
text = text.replace(/`(.+?)`/g, '<code class="bg-gray-100 px-1 rounded text-xs">$1</code>');
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
function renderParadigm(data) {
|
function renderParadigm(data) {
|
||||||
const result = document.getElementById('paradigm-result');
|
const result = document.getElementById('paradigm-result');
|
||||||
const stageColors = {
|
const stageColors = {
|
||||||
|
|||||||
Reference in New Issue
Block a user