|
@@ -7,10 +7,35 @@ import {
|
|
} from '@angular/core';
|
|
} from '@angular/core';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { CommonModule } from '@angular/common';
|
|
import { CommonModule } from '@angular/common';
|
|
|
|
+import { TestCompletion, TestMessage,extractJSON} from '../../../../lib/cloud/comletion';
|
|
|
|
|
|
type SceneType = 'first-contact' | 'deep-talk';
|
|
type SceneType = 'first-contact' | 'deep-talk';
|
|
type MessageRole = 'customer' | 'assistant';
|
|
type MessageRole = 'customer' | 'assistant';
|
|
|
|
|
|
|
|
+interface StrategyAnalysis {
|
|
|
|
+ aggressive: {
|
|
|
|
+ title: string;
|
|
|
|
+ highlight: string;
|
|
|
|
+ applicable: string;
|
|
|
|
+ advantage: string;
|
|
|
|
+ successRate: string;
|
|
|
|
+ };
|
|
|
|
+ neutral: {
|
|
|
|
+ title: string;
|
|
|
|
+ highlight: string;
|
|
|
|
+ applicable: string;
|
|
|
|
+ advantage: string;
|
|
|
|
+ successRate: string;
|
|
|
|
+ };
|
|
|
|
+ conservative: {
|
|
|
|
+ title: string;
|
|
|
|
+ highlight: string;
|
|
|
|
+ applicable: string;
|
|
|
|
+ advantage: string;
|
|
|
|
+ successRate: string;
|
|
|
|
+ };
|
|
|
|
+}
|
|
|
|
+
|
|
interface Message {
|
|
interface Message {
|
|
role: MessageRole;
|
|
role: MessageRole;
|
|
content: string;
|
|
content: string;
|
|
@@ -36,6 +61,16 @@ interface Strategy {
|
|
advantage: string;
|
|
advantage: string;
|
|
successRate: string;
|
|
successRate: string;
|
|
rating: string;
|
|
rating: string;
|
|
|
|
+ isDefault: boolean; // 新增:标记是否为默认策略
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+interface ChatRecord {
|
|
|
|
+ id: string;
|
|
|
|
+ scene: SceneType;
|
|
|
|
+ messages: Message[];
|
|
|
|
+ tags: Tag[];
|
|
|
|
+ strategies: Strategy[];
|
|
|
|
+ timestamp: number;
|
|
}
|
|
}
|
|
|
|
|
|
@Component({
|
|
@Component({
|
|
@@ -47,19 +82,34 @@ interface Strategy {
|
|
})
|
|
})
|
|
export class PageCrmDecision implements OnInit, AfterViewChecked {
|
|
export class PageCrmDecision implements OnInit, AfterViewChecked {
|
|
@ViewChild('chatContainer') chatContainer!: ElementRef;
|
|
@ViewChild('chatContainer') chatContainer!: ElementRef;
|
|
-
|
|
|
|
-
|
|
|
|
|
|
+ @ViewChild('fileInput') fileInput!: ElementRef;
|
|
|
|
+
|
|
|
|
+ private aiAssistant!: TestCompletion;
|
|
|
|
+ private scenePrompts = {
|
|
|
|
+ 'first-contact': `你是一名专业的酒店销售顾问,正在与一位潜在客户进行首次接触。
|
|
|
|
+客户可能对酒店服务、房型、价格等方面有疑问。你的任务是:
|
|
|
|
+1. 建立良好的沟通氛围
|
|
|
|
+2. 了解客户基本需求
|
|
|
|
+3. 介绍酒店核心优势
|
|
|
|
+4. 避免过于强硬的推销语气
|
|
|
|
+5. 为后续沟通留下空间`,
|
|
|
|
+ 'deep-talk': `你是一名经验丰富的酒店销售顾问,正在与一位对酒店有一定兴趣的客户进行深度交流。
|
|
|
|
+客户可能已经了解了基本信息,正在比较选择或寻求更详细的信息。你的任务是:
|
|
|
|
+1. 深入挖掘客户具体需求和关注点
|
|
|
|
+2. 提供针对性的解决方案和建议
|
|
|
|
+3. 有效处理客户提出的异议和问题
|
|
|
|
+4. 展示酒店差异化竞争优势
|
|
|
|
+5. 推动客户向决策阶段发展`
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ openHistory() {
|
|
|
|
+ this.showHistoryModal = true;
|
|
|
|
+ }
|
|
|
|
+
|
|
selectedScene: SceneType = 'first-contact';
|
|
selectedScene: SceneType = 'first-contact';
|
|
sceneTitle = '首次接触';
|
|
sceneTitle = '首次接触';
|
|
|
|
|
|
- messages: Message[] = [
|
|
|
|
- { role: 'customer', content: '您好,我对贵酒店的豪华套房很感兴趣,但觉得价格偏高,能否提供一些优惠?', time: '10:25 AM' },
|
|
|
|
- { role: 'assistant', content: '感谢您对我们豪华套房的关注!我们的套房拥有全景海景和私人管家服务,目前预订可享8折优惠,您觉得如何?', time: '10:26 AM' },
|
|
|
|
- { role: 'customer', content: '8折还是有点高,我看到其他酒店类似房型有更优惠的价格。', time: '10:27 AM' },
|
|
|
|
- { role: 'assistant', content: '我理解您的考虑。我们的套房包含双人早餐和免费SPA体验,这些增值服务能让您的住宿体验更加完美。', time: '10:28 AM' },
|
|
|
|
- { role: 'customer', content: '这个听起来不错,我考虑一下。', time: '10:29 AM' },
|
|
|
|
- { role: 'assistant', content: '好的,如果您今天预订,我们还可以额外赠送您一次下午茶体验。', time: '10:30 AM' }
|
|
|
|
- ];
|
|
|
|
|
|
+ messages: Message[] = [];
|
|
|
|
|
|
tags: Tag[] = [
|
|
tags: Tag[] = [
|
|
{ text: 'VIP客户', icon: 'fas fa-user', background: 'rgba(100, 160, 255, 0.15)', color: '#64a0ff' },
|
|
{ text: 'VIP客户', icon: 'fas fa-user', background: 'rgba(100, 160, 255, 0.15)', color: '#64a0ff' },
|
|
@@ -77,12 +127,13 @@ export class PageCrmDecision implements OnInit, AfterViewChecked {
|
|
icon: 'fas fa-bolt',
|
|
icon: 'fas fa-bolt',
|
|
iconBg: 'var(--blue-light)',
|
|
iconBg: 'var(--blue-light)',
|
|
iconColor: 'var(--blue)',
|
|
iconColor: 'var(--blue)',
|
|
- description: '直接强调酒店独特价值,限量优惠制造紧迫感:',
|
|
|
|
|
|
+ description: '直接强调酒店独特价值,制造紧迫感:',
|
|
highlight: '',
|
|
highlight: '',
|
|
applicable: '客户表现出明确兴趣,决策周期短',
|
|
applicable: '客户表现出明确兴趣,决策周期短',
|
|
advantage: '快速成交,提升单笔订单价值',
|
|
advantage: '快速成交,提升单笔订单价值',
|
|
successRate: '68%',
|
|
successRate: '68%',
|
|
- rating: '⭐⭐⭐⭐'
|
|
|
|
|
|
+ rating: '⭐⭐⭐⭐',
|
|
|
|
+ isDefault: true
|
|
},
|
|
},
|
|
{
|
|
{
|
|
type: 'neutral',
|
|
type: 'neutral',
|
|
@@ -95,7 +146,8 @@ export class PageCrmDecision implements OnInit, AfterViewChecked {
|
|
applicable: '客户在多个选项间犹豫',
|
|
applicable: '客户在多个选项间犹豫',
|
|
advantage: '平衡双方利益,建立长期关系',
|
|
advantage: '平衡双方利益,建立长期关系',
|
|
successRate: '82%',
|
|
successRate: '82%',
|
|
- rating: '⭐⭐⭐⭐⭐'
|
|
|
|
|
|
+ rating: '⭐⭐⭐⭐⭐',
|
|
|
|
+ isDefault: true
|
|
},
|
|
},
|
|
{
|
|
{
|
|
type: 'conservative',
|
|
type: 'conservative',
|
|
@@ -108,11 +160,15 @@ export class PageCrmDecision implements OnInit, AfterViewChecked {
|
|
applicable: '客户犹豫不决,决策周期长',
|
|
applicable: '客户犹豫不决,决策周期长',
|
|
advantage: '降低客户风险感知,提高转化率',
|
|
advantage: '降低客户风险感知,提高转化率',
|
|
successRate: '75%',
|
|
successRate: '75%',
|
|
- rating: '⭐⭐⭐⭐'
|
|
|
|
|
|
+ rating: '⭐⭐⭐⭐',
|
|
|
|
+ isDefault: true
|
|
}
|
|
}
|
|
];
|
|
];
|
|
|
|
|
|
activeCardIndex = 0;
|
|
activeCardIndex = 0;
|
|
|
|
+ isProcessing = false;
|
|
|
|
+ showHistoryModal = false;
|
|
|
|
+ chatRecords: ChatRecord[] = [];
|
|
|
|
|
|
private strategiesContent: Record<SceneType, { aggressive: string; neutral: string; conservative: string }> = {
|
|
private strategiesContent: Record<SceneType, { aggressive: string; neutral: string; conservative: string }> = {
|
|
"first-contact": {
|
|
"first-contact": {
|
|
@@ -128,7 +184,8 @@ export class PageCrmDecision implements OnInit, AfterViewChecked {
|
|
};
|
|
};
|
|
|
|
|
|
ngOnInit() {
|
|
ngOnInit() {
|
|
- this.updateStrategies();
|
|
|
|
|
|
+ this.loadRecords();
|
|
|
|
+ this.selectScene('first-contact');
|
|
}
|
|
}
|
|
|
|
|
|
ngAfterViewChecked() {
|
|
ngAfterViewChecked() {
|
|
@@ -137,18 +194,14 @@ export class PageCrmDecision implements OnInit, AfterViewChecked {
|
|
|
|
|
|
scrollToBottom(): void {
|
|
scrollToBottom(): void {
|
|
try {
|
|
try {
|
|
- this.chatContainer.nativeElement.scrollTop = this.chatContainer.nativeElement.scrollHeight;
|
|
|
|
|
|
+ if (this.chatContainer) {
|
|
|
|
+ this.chatContainer.nativeElement.scrollTop = this.chatContainer.nativeElement.scrollHeight;
|
|
|
|
+ }
|
|
} catch (err) {
|
|
} catch (err) {
|
|
console.error(err);
|
|
console.error(err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- handleKeydown(event: KeyboardEvent): void {
|
|
|
|
- if (event.key === 'Enter' && !event.shiftKey) {
|
|
|
|
- event.preventDefault();
|
|
|
|
- this.sendMessage();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
onTextareaKeydown(event: KeyboardEvent): void {
|
|
onTextareaKeydown(event: KeyboardEvent): void {
|
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
event.preventDefault();
|
|
event.preventDefault();
|
|
@@ -156,17 +209,83 @@ export class PageCrmDecision implements OnInit, AfterViewChecked {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- updateStrategies(): void {
|
|
|
|
|
|
+ // 只在初始化时设置默认策略
|
|
|
|
+ initializeDefaultStrategies(): void {
|
|
const sceneStrategies = this.strategiesContent[this.selectedScene];
|
|
const sceneStrategies = this.strategiesContent[this.selectedScene];
|
|
- this.strategies[0].highlight = sceneStrategies.aggressive;
|
|
|
|
- this.strategies[1].highlight = sceneStrategies.neutral;
|
|
|
|
- this.strategies[2].highlight = sceneStrategies.conservative;
|
|
|
|
|
|
+
|
|
|
|
+ this.strategies[0] = {
|
|
|
|
+ ...this.strategies[0],
|
|
|
|
+ highlight: sceneStrategies.aggressive,
|
|
|
|
+ isDefault: true
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ this.strategies[1] = {
|
|
|
|
+ ...this.strategies[1],
|
|
|
|
+ highlight: sceneStrategies.neutral,
|
|
|
|
+ isDefault: true
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ this.strategies[2] = {
|
|
|
|
+ ...this.strategies[2],
|
|
|
|
+ highlight: sceneStrategies.conservative,
|
|
|
|
+ isDefault: true
|
|
|
|
+ };
|
|
}
|
|
}
|
|
|
|
|
|
selectScene(scene: SceneType): void {
|
|
selectScene(scene: SceneType): void {
|
|
this.selectedScene = scene;
|
|
this.selectedScene = scene;
|
|
this.sceneTitle = scene === 'first-contact' ? '首次接触' : '深度交流';
|
|
this.sceneTitle = scene === 'first-contact' ? '首次接触' : '深度交流';
|
|
- this.updateStrategies();
|
|
|
|
|
|
+
|
|
|
|
+ // 初始化AI助手
|
|
|
|
+ this.messages = [];
|
|
|
|
+ const systemPrompt = this.scenePrompts[scene];
|
|
|
|
+ this.aiAssistant = new TestCompletion([
|
|
|
|
+ { role: "system", content: systemPrompt },
|
|
|
|
+ { role: "assistant", content: scene === 'first-contact' ?
|
|
|
|
+ "您好!我是九州宴会的销售顾问,很高兴认识您。请问您今天想了解哪方面的信息呢?" :
|
|
|
|
+ "感谢您继续深入交流。基于我们之前的沟通,我整理了几个可能适合您的方案,您想先了解哪个方面?"
|
|
|
|
+ }
|
|
|
|
+ ]);
|
|
|
|
+
|
|
|
|
+ // 添加初始问候语
|
|
|
|
+ this.addMessage('assistant', scene === 'first-contact' ?
|
|
|
|
+ "您好!我是九州宴会的销售顾问,很高兴认识您。请问您今天想了解哪方面的信息呢?" :
|
|
|
|
+ "感谢您继续深入交流。基于我们之前的沟通,我整理了几个可能适合您的方案,您想先了解哪个方面?"
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // 重置并初始化策略
|
|
|
|
+ this.resetStrategies();
|
|
|
|
+ this.initializeDefaultStrategies();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 重置策略内容(切换场景时调用)
|
|
|
|
+ private resetStrategies() {
|
|
|
|
+ this.strategies = [
|
|
|
|
+ {
|
|
|
|
+ ...this.strategies[0],
|
|
|
|
+ highlight: "",
|
|
|
|
+ applicable: "客户表现出明确兴趣,决策周期短",
|
|
|
|
+ advantage: "快速成交,提升单笔订单价值",
|
|
|
|
+ successRate: "68%",
|
|
|
|
+ isDefault: true
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ ...this.strategies[1],
|
|
|
|
+ highlight: "",
|
|
|
|
+ applicable: "客户在多个选项间犹豫",
|
|
|
|
+ advantage: "平衡双方利益,建立长期关系",
|
|
|
|
+ successRate: "82%",
|
|
|
|
+ isDefault: true
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ ...this.strategies[2],
|
|
|
|
+ highlight: "",
|
|
|
|
+ applicable: "客户犹豫不决,决策周期长",
|
|
|
|
+ advantage: "降低客户风险感知,提高转化率",
|
|
|
|
+ successRate: "75%",
|
|
|
|
+ isDefault: true
|
|
|
|
+ }
|
|
|
|
+ ];
|
|
}
|
|
}
|
|
|
|
|
|
setRole(role: MessageRole): void {
|
|
setRole(role: MessageRole): void {
|
|
@@ -188,47 +307,154 @@ export class PageCrmDecision implements OnInit, AfterViewChecked {
|
|
this.tags = this.tags.filter(t => t !== tag);
|
|
this.tags = this.tags.filter(t => t !== tag);
|
|
}
|
|
}
|
|
|
|
|
|
- sendMessage(): void {
|
|
|
|
- if (this.newMessage.trim()) {
|
|
|
|
- const now = new Date();
|
|
|
|
- const timeString = `${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`;
|
|
|
|
|
|
+ async sendMessage() {
|
|
|
|
+ if (!this.newMessage.trim() || !this.selectedScene) return;
|
|
|
|
+
|
|
|
|
+ this.isProcessing = true;
|
|
|
|
+
|
|
|
|
+ // 添加用户消息
|
|
|
|
+ const now = new Date();
|
|
|
|
+ this.addMessage(this.currentRole, this.newMessage.trim());
|
|
|
|
+ this.newMessage = '';
|
|
|
|
+
|
|
|
|
+ // 如果是客户消息,更新策略分析
|
|
|
|
+ if (this.currentRole === 'customer') {
|
|
|
|
+ try {
|
|
|
|
+ // 显示加载状态
|
|
|
|
+ this.strategies.forEach(strategy => {
|
|
|
|
+ strategy.highlight = "分析中...";
|
|
|
|
+ strategy.applicable = "生成最佳策略...";
|
|
|
|
+ strategy.successRate = "--";
|
|
|
|
+ strategy.isDefault = false;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 更新策略分析(基于最新客户消息)
|
|
|
|
+ await this.updateStrategiesBasedOnMessage(this.messages[this.messages.length-1].content);
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('更新策略失败:', error);
|
|
|
|
+ // 失败时使用关键词匹配生成动态内容
|
|
|
|
+ const fallbackStrategies = this.generateDynamicStrategies(this.messages[this.messages.length-1].content);
|
|
|
|
+ this.updateStrategiesWithFallback(fallbackStrategies);
|
|
|
|
+ } finally {
|
|
|
|
+ this.isProcessing = false;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ this.isProcessing = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private addMessage(role: MessageRole, content: string): void {
|
|
|
|
+ const now = new Date();
|
|
|
|
+ this.messages.push({
|
|
|
|
+ role,
|
|
|
|
+ content,
|
|
|
|
+ time: `${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`
|
|
|
|
+ });
|
|
|
|
+ this.scrollToBottom();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 根据客户消息动态更新策略卡片内容
|
|
|
|
+ private async updateStrategiesBasedOnMessage(customerMessage: string) {
|
|
|
|
+ try {
|
|
|
|
+ // 调用AI分析客户消息,生成策略建议
|
|
|
|
+ const analysis = await this.analyzeCustomerMessage(customerMessage);
|
|
|
|
|
|
- this.messages.push({
|
|
|
|
- role: this.currentRole,
|
|
|
|
- content: this.newMessage.trim(),
|
|
|
|
- time: timeString
|
|
|
|
- });
|
|
|
|
|
|
+ // 更新策略卡片内容
|
|
|
|
+ this.strategies[0] = {
|
|
|
|
+ ...this.strategies[0],
|
|
|
|
+ highlight: analysis.aggressive.highlight,
|
|
|
|
+ applicable: analysis.aggressive.applicable,
|
|
|
|
+ advantage: analysis.aggressive.advantage,
|
|
|
|
+ successRate: analysis.aggressive.successRate,
|
|
|
|
+ isDefault: false
|
|
|
|
+ };
|
|
|
|
|
|
- this.newMessage = '';
|
|
|
|
|
|
+ this.strategies[1] = {
|
|
|
|
+ ...this.strategies[1],
|
|
|
|
+ highlight: analysis.neutral.highlight,
|
|
|
|
+ applicable: analysis.neutral.applicable,
|
|
|
|
+ advantage: analysis.neutral.advantage,
|
|
|
|
+ successRate: analysis.neutral.successRate,
|
|
|
|
+ isDefault: false
|
|
|
|
+ };
|
|
|
|
|
|
- if (this.currentRole === 'assistant') {
|
|
|
|
- setTimeout(() => {
|
|
|
|
- this.simulateCustomerReply(now);
|
|
|
|
- }, 1500);
|
|
|
|
- }
|
|
|
|
|
|
+ this.strategies[2] = {
|
|
|
|
+ ...this.strategies[2],
|
|
|
|
+ highlight: analysis.conservative.highlight,
|
|
|
|
+ applicable: analysis.conservative.applicable,
|
|
|
|
+ advantage: analysis.conservative.advantage,
|
|
|
|
+ successRate: analysis.conservative.successRate,
|
|
|
|
+ isDefault: false
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 根据消息内容选择最合适的策略
|
|
|
|
+ this.selectBestStrategy(customerMessage);
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('更新策略失败:', error);
|
|
|
|
+ throw error; // 让调用者处理错误
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- simulateCustomerReply(prevTime: Date): void {
|
|
|
|
- const replies = [
|
|
|
|
- "这个优惠包含早餐吗?",
|
|
|
|
- "我需要和家人商量一下",
|
|
|
|
- "能否提供免费接机服务?",
|
|
|
|
- "价格还是有点高,能再优惠些吗?",
|
|
|
|
- "你们的取消政策是怎样的?",
|
|
|
|
- "如果我今天预订,还有额外优惠吗?",
|
|
|
|
- "套餐包含哪些服务?",
|
|
|
|
- "有更经济的房型推荐吗?"
|
|
|
|
- ];
|
|
|
|
|
|
+ // 使用备用策略更新(AI调用失败时)
|
|
|
|
+ private updateStrategiesWithFallback(strategies: ReturnType<typeof this.generateDynamicStrategies>) {
|
|
|
|
+ this.strategies[0] = {
|
|
|
|
+ ...this.strategies[0],
|
|
|
|
+ highlight: strategies.aggressive.highlight,
|
|
|
|
+ applicable: "客户表现出明确兴趣,决策周期短",
|
|
|
|
+ successRate: "68%",
|
|
|
|
+ isDefault: false
|
|
|
|
+ };
|
|
|
|
|
|
- const randomReply = replies[Math.floor(Math.random() * replies.length)];
|
|
|
|
- const time = new Date(prevTime.getTime() + 60000); // 加1分钟
|
|
|
|
|
|
+ this.strategies[1] = {
|
|
|
|
+ ...this.strategies[1],
|
|
|
|
+ highlight: strategies.neutral.highlight,
|
|
|
|
+ applicable: "客户在多个选项间犹豫",
|
|
|
|
+ successRate: "82%",
|
|
|
|
+ isDefault: false
|
|
|
|
+ };
|
|
|
|
|
|
- this.messages.push({
|
|
|
|
- role: 'customer',
|
|
|
|
- content: randomReply,
|
|
|
|
- time: `${time.getHours()}:${time.getMinutes().toString().padStart(2, '0')}`
|
|
|
|
- });
|
|
|
|
|
|
+ this.strategies[2] = {
|
|
|
|
+ ...this.strategies[2],
|
|
|
|
+ highlight: strategies.conservative.highlight,
|
|
|
|
+ applicable: "客户犹豫不决,决策周期长",
|
|
|
|
+ successRate: "75%",
|
|
|
|
+ isDefault: false
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 仍然选择最佳策略
|
|
|
|
+ this.selectBestStrategy(this.messages[this.messages.length-1].content);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 生成动态策略内容(关键词匹配)
|
|
|
|
+ private generateDynamicStrategies(customerMessage: string) {
|
|
|
|
+ // 关键词匹配,确保内容随客户消息变化
|
|
|
|
+ let focus = "酒店服务";
|
|
|
|
+ if (customerMessage.toLowerCase().includes("价格")) focus = "价格优势";
|
|
|
|
+ if (customerMessage.toLowerCase().includes("位置")) focus = "地理位置";
|
|
|
|
+ if (customerMessage.toLowerCase().includes("设施")) focus = "设施特色";
|
|
|
|
+ if (customerMessage.toLowerCase().includes("日期")) focus = "日期灵活性";
|
|
|
|
+ if (customerMessage.toLowerCase().includes("容量")) focus = "场地容量";
|
|
|
|
+
|
|
|
|
+ return {
|
|
|
|
+ aggressive: {
|
|
|
|
+ highlight: `立即强调${focus},提供专属限时优惠`,
|
|
|
|
+ applicable: "客户表现出明确兴趣",
|
|
|
|
+ advantage: "快速促成交易",
|
|
|
|
+ successRate: "68%"
|
|
|
|
+ },
|
|
|
|
+ neutral: {
|
|
|
|
+ highlight: `提供${focus}的3种选择方案,满足不同需求`,
|
|
|
|
+ applicable: "客户需要比较选择",
|
|
|
|
+ advantage: "提高选择满意度",
|
|
|
|
+ successRate: "82%"
|
|
|
|
+ },
|
|
|
|
+ conservative: {
|
|
|
|
+ highlight: `详细说明${focus}的优势,提供案例参考`,
|
|
|
|
+ applicable: "客户需要更多决策信息",
|
|
|
|
+ advantage: "降低决策阻力",
|
|
|
|
+ successRate: "75%"
|
|
|
|
+ }
|
|
|
|
+ };
|
|
}
|
|
}
|
|
|
|
|
|
activateCard(index: number): void {
|
|
activateCard(index: number): void {
|
|
@@ -255,13 +481,289 @@ export class PageCrmDecision implements OnInit, AfterViewChecked {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- saveRecord(): void {
|
|
|
|
- console.log('记录已保存');
|
|
|
|
- // 这里可以添加实际的保存逻辑
|
|
|
|
|
|
+ // 文件上传处理
|
|
|
|
+ onFileSelected(event: Event) {
|
|
|
|
+ const input = event.target as HTMLInputElement;
|
|
|
|
+ if (!input.files || input.files.length === 0) return;
|
|
|
|
+
|
|
|
|
+ const file = input.files[0];
|
|
|
|
+ if (file.type !== 'application/json' && !file.name.endsWith('.json')) {
|
|
|
|
+ alert('请上传JSON格式的文件');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const reader = new FileReader();
|
|
|
|
+ reader.onload = (e: ProgressEvent<FileReader>) => {
|
|
|
|
+ try {
|
|
|
|
+ const content = e.target?.result as string;
|
|
|
|
+ const data = JSON.parse(content);
|
|
|
|
+
|
|
|
|
+ if (Array.isArray(data) && data.every(item => item.role && item.content && item.time)) {
|
|
|
|
+ this.messages = data as Message[];
|
|
|
|
+ this.updateStrategiesBasedOnMessages();
|
|
|
|
+ this.fileInput.nativeElement.value = ''; // 清空文件选择
|
|
|
|
+ } else {
|
|
|
|
+ alert('文件格式不正确,请上传有效的聊天记录JSON文件');
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('文件解析错误:', error);
|
|
|
|
+ alert('解析文件时出错');
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ reader.readAsText(file);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 根据完整对话更新策略
|
|
|
|
+ private updateStrategiesBasedOnMessages() {
|
|
|
|
+ // 从对话中提取最后一条客户消息
|
|
|
|
+ const lastCustomerMessage = this.messages
|
|
|
|
+ .filter(msg => msg.role === 'customer')
|
|
|
|
+ .pop();
|
|
|
|
+
|
|
|
|
+ if (lastCustomerMessage) {
|
|
|
|
+ this.updateStrategiesBasedOnMessage(lastCustomerMessage.content);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 保存记录
|
|
|
|
+ saveRecord() {
|
|
|
|
+ if (!this.messages.length) {
|
|
|
|
+ alert('没有可保存的对话记录');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const record: ChatRecord = {
|
|
|
|
+ id: Date.now().toString(),
|
|
|
|
+ scene: this.selectedScene,
|
|
|
|
+ messages: [...this.messages],
|
|
|
|
+ tags: [...this.tags],
|
|
|
|
+ strategies: [...this.strategies],
|
|
|
|
+ timestamp: Date.now()
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ this.chatRecords.unshift(record); // 添加到开头
|
|
|
|
+ this.saveRecords();
|
|
|
|
+ alert('记录已保存');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 加载记录
|
|
|
|
+ loadRecords() {
|
|
|
|
+ const records = localStorage.getItem('chatRecords');
|
|
|
|
+ if (records) {
|
|
|
|
+ this.chatRecords = JSON.parse(records);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 保存记录到本地存储
|
|
|
|
+ saveRecords() {
|
|
|
|
+ localStorage.setItem('chatRecords', JSON.stringify(this.chatRecords));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 查看历史
|
|
|
|
+ viewHistory() {
|
|
|
|
+ this.showHistoryModal = true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 选择历史记录
|
|
|
|
+ selectRecord(record: ChatRecord) {
|
|
|
|
+ this.selectedScene = record.scene;
|
|
|
|
+ this.sceneTitle = record.scene === 'first-contact' ? '首次接触' : '深度交流';
|
|
|
|
+ this.messages = [...record.messages];
|
|
|
|
+ this.tags = [...record.tags];
|
|
|
|
+ this.strategies = [...record.strategies];
|
|
|
|
+
|
|
|
|
+ // 初始化AI助手
|
|
|
|
+ const systemPrompt = this.scenePrompts[record.scene];
|
|
|
|
+ this.aiAssistant = new TestCompletion([
|
|
|
|
+ { role: "system", content: systemPrompt },
|
|
|
|
+ ...record.messages.map(msg => ({
|
|
|
|
+ role: msg.role === 'customer' ? 'user' : msg.role,
|
|
|
|
+ content: msg.content
|
|
|
|
+ }))
|
|
|
|
+ ]);
|
|
|
|
+
|
|
|
|
+ this.showHistoryModal = false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 删除历史记录
|
|
|
|
+ deleteRecord(record: ChatRecord) {
|
|
|
|
+ if (confirm('确定要删除这条记录吗?')) {
|
|
|
|
+ this.chatRecords = this.chatRecords.filter(r => r.id !== record.id);
|
|
|
|
+ this.saveRecords();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- viewHistory(): void {
|
|
|
|
- console.log('查看历史记录');
|
|
|
|
- // 这里可以添加历史查看逻辑
|
|
|
|
|
|
+ // 格式化日期
|
|
|
|
+ formatDate(timestamp: number) {
|
|
|
|
+ const date = new Date(timestamp);
|
|
|
|
+ return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 导出对话记录为JSON文件
|
|
|
|
+ exportChat() {
|
|
|
|
+ if (!this.messages.length) {
|
|
|
|
+ alert('没有可导出的对话记录');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const jsonContent = JSON.stringify(this.messages, null, 2);
|
|
|
|
+ const blob = new Blob([jsonContent], { type: 'application/json' });
|
|
|
|
+ const url = URL.createObjectURL(blob);
|
|
|
|
+
|
|
|
|
+ const a = document.createElement('a');
|
|
|
|
+ a.href = url;
|
|
|
|
+ a.download = `chat-record-${new Date().toISOString().slice(0,10)}.json`;
|
|
|
|
+ document.body.appendChild(a);
|
|
|
|
+ a.click();
|
|
|
|
+ document.body.removeChild(a);
|
|
|
|
+ URL.revokeObjectURL(url);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 生成AI回复
|
|
|
|
+ async generateAIResponse() {
|
|
|
|
+ if (!this.messages.length || this.messages[this.messages.length - 1].role === 'assistant') {
|
|
|
|
+ alert('请先发送客户消息');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.isProcessing = true;
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ const aiReply = await this.aiAssistant.sendMessage(
|
|
|
|
+ [...this.messages.map(msg => ({
|
|
|
|
+ role: msg.role === 'customer' ? 'user' : msg.role,
|
|
|
|
+ content: msg.content
|
|
|
|
+ }))],
|
|
|
|
+ (partialContent) => {
|
|
|
|
+ const lastMsg = this.messages[this.messages.length - 1];
|
|
|
|
+ if (lastMsg.role === 'assistant') {
|
|
|
|
+ lastMsg.content = partialContent;
|
|
|
|
+ } else {
|
|
|
|
+ this.addMessage('assistant', partialContent);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ if (typeof aiReply === 'string') {
|
|
|
|
+ this.addMessage('assistant', aiReply);
|
|
|
|
+ // 基于最新客户消息更新策略
|
|
|
|
+ const lastCustomerMsg = this.messages
|
|
|
|
+ .filter(msg => msg.role === 'customer')
|
|
|
|
+ .pop();
|
|
|
|
+
|
|
|
|
+ if (lastCustomerMsg) {
|
|
|
|
+ this.updateStrategiesBasedOnMessage(lastCustomerMsg.content);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('AI交互错误:', error);
|
|
|
|
+ this.addMessage('assistant', '抱歉,处理您的请求时出现了问题。');
|
|
|
|
+ } finally {
|
|
|
|
+ this.isProcessing = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 调用AI分析客户消息并生成策略建议
|
|
|
|
+ private async analyzeCustomerMessage(customerMessage: string): Promise<StrategyAnalysis> {
|
|
|
|
+ // 构建提示词,引导AI生成三种策略
|
|
|
|
+ const prompt = `
|
|
|
|
+ 客户消息: "${customerMessage}"
|
|
|
|
+
|
|
|
|
+ 请针对此客户消息,为酒店销售顾问生成三种应对策略:
|
|
|
|
+ 1. 主动型策略: 直接、果断的销售方法
|
|
|
|
+ 2. 平衡型策略: 中等强度的销售方法
|
|
|
|
+ 3. 保守型策略: 温和、信息丰富的销售方法
|
|
|
|
+
|
|
|
|
+ 请以JSON格式返回,包含以下字段:
|
|
|
|
+ {
|
|
|
|
+ "aggressive": {
|
|
|
|
+ "title": "主动型策略标题",
|
|
|
|
+ "highlight": "核心建议",
|
|
|
|
+ "applicable": "适用场景",
|
|
|
|
+ "advantage": "主要优势",
|
|
|
|
+ "successRate": "成功率百分比"
|
|
|
|
+ },
|
|
|
|
+ "neutral": {
|
|
|
|
+ "title": "平衡型策略标题",
|
|
|
|
+ "highlight": "核心建议",
|
|
|
|
+ "applicable": "适用场景",
|
|
|
|
+ "advantage": "主要优势",
|
|
|
|
+ "successRate": "成功率百分比"
|
|
|
|
+ },
|
|
|
|
+ "conservative": {
|
|
|
|
+ "title": "保守型策略标题",
|
|
|
|
+ "highlight": "核心建议",
|
|
|
|
+ "applicable": "适用场景",
|
|
|
|
+ "advantage": "主要优势",
|
|
|
|
+ "successRate": "成功率百分比"
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ `;
|
|
|
|
+
|
|
|
|
+ // 使用TestCompletion调用AI
|
|
|
|
+ const strategyAnalyzer = new TestCompletion([
|
|
|
|
+ { role: "system", content: "你是一位专业的酒店销售策略顾问,擅长根据客户消息生成针对性的销售策略。" },
|
|
|
|
+ { role: "user", content: prompt }
|
|
|
|
+ ]);
|
|
|
|
+
|
|
|
|
+ // 获取AI响应
|
|
|
|
+ const response = await strategyAnalyzer.sendMessage([
|
|
|
|
+ { role: "user", content: prompt }
|
|
|
|
+ ],(content)=>{
|
|
|
|
+ this.aicontent = content
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 解析JSON响应
|
|
|
|
+ try {
|
|
|
|
+ let json = extractJSON(response)
|
|
|
|
+ return json;
|
|
|
|
+ } catch (parseError) {
|
|
|
|
+ console.error('解析策略分析失败:', parseError);
|
|
|
|
+ // 返回默认策略
|
|
|
|
+ return this.getDefaultStrategies();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ aicontent:string = ""
|
|
|
|
+ // 根据客户消息选择最合适的策略
|
|
|
|
+ private selectBestStrategy(customerMessage: string) {
|
|
|
|
+ const lowerCaseMsg = customerMessage.toLowerCase();
|
|
|
|
+
|
|
|
|
+ if (lowerCaseMsg.includes('价格') || lowerCaseMsg.includes('优惠')) {
|
|
|
|
+ this.activeCardIndex = 1; // 平衡型策略通常最适合价格讨论
|
|
|
|
+ } else if (lowerCaseMsg.includes('兴趣') || lowerCaseMsg.includes('考虑')) {
|
|
|
|
+ this.activeCardIndex = 0; // 主动型策略适合有兴趣的客户
|
|
|
|
+ } else if (lowerCaseMsg.includes('犹豫') || lowerCaseMsg.includes('比较')) {
|
|
|
|
+ this.activeCardIndex = 2; // 保守型策略适合犹豫的客户
|
|
|
|
+ } else {
|
|
|
|
+ // 默认选择平衡型策略
|
|
|
|
+ this.activeCardIndex = 1;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 返回默认策略(当AI分析失败时使用)
|
|
|
|
+ private getDefaultStrategies(): StrategyAnalysis {
|
|
|
|
+ return {
|
|
|
|
+ aggressive: {
|
|
|
|
+ title: "主动型策略",
|
|
|
|
+ highlight: "直接介绍酒店特色和限时优惠",
|
|
|
|
+ applicable: "客户表现出明确兴趣,决策周期短",
|
|
|
|
+ advantage: "快速成交,提升单笔订单价值",
|
|
|
|
+ successRate: "68%"
|
|
|
|
+ },
|
|
|
|
+ neutral: {
|
|
|
|
+ title: "平衡型策略",
|
|
|
|
+ highlight: "提供套餐选择,引导客户了解不同房型",
|
|
|
|
+ applicable: "客户在多个选项间犹豫",
|
|
|
|
+ advantage: "平衡双方利益,建立长期关系",
|
|
|
|
+ successRate: "82%"
|
|
|
|
+ },
|
|
|
|
+ conservative: {
|
|
|
|
+ title: "保守型策略",
|
|
|
|
+ highlight: "邀请客户参观虚拟酒店,提供详细资料",
|
|
|
|
+ applicable: "客户犹豫不决,决策周期长",
|
|
|
|
+ advantage: "降低客户风险感知,提高转化率",
|
|
|
|
+ successRate: "75%"
|
|
|
|
+ }
|
|
|
|
+ };
|
|
}
|
|
}
|
|
}
|
|
}
|