文件
OmniCard/mpuniapp/pages/portal/useAiChat.uts
wikiboss 4859110114 refactor(mpuniapp): 全局规范所有用词并去除AI口吻与浮夸文案
- 规范7国语言全局词典中AI咨询为在线咨询,去除内部黑话与营销说教
- 在线咨询去除机械人套话与写死的大号AI文本标签
- 交换名片剔除留资行话与写死AI,规范为名片交换与纸质名片识别
- 工作台去除战报、闪电出示、高光足迹、破冰等中二词汇,回归严谨商务数据
- 产品案例与专栏文章去除夸大修饰词与冗余操作说明
- 潜客跟进去除写死销售套话,规范为得体自然的跟进建议
- 硬件安全与场景模式去除秒级一键、芯片防盗等噱头词
- 导航栏标题统一规范为在线咨询、名片编辑、展会数据
- 新增专项测试并通过全量62项回归测试
2026-09-21 16:04:38 +08:00

591 行
14 KiB
Plaintext

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

/**
* OmniCard AI (CRMmp) - 门户 AI 顾问对话业务驱动层
* 调用已有 askAiChat,空文本不发,cardId 必填
*/
import {
AiChatMessage,
AiChatReply,
AiChatRequest,
ChatMessage,
ChatMessageType,
ChatLeadPayload,
SubmitLeadParams,
} from '../../common/types.uts';
import { askAiChat, submitLead } from '../../common/api.uts';
import {
OmniLocale,
getCurrentLocale,
getOmniText,
resolveDeviceLocale,
setCurrentLocale,
} from '../../common/locale.uts';
export {
ChatMessage,
ChatMessageType,
ChatLeadPayload,
AiChatMessage,
AiChatReply,
AiChatRequest,
};
export type AiChatCopyKey =
| 'title'
| 'send'
| 'sending'
| 'placeholder'
| 'empty'
| 'leadHint'
| 'missingCard'
| 'inputLabel'
| 'nameLabel'
| 'namePlaceholder'
| 'contactLabel'
| 'contactPlaceholder'
| 'submitLead'
| 'submitting'
| 'submittedSuccess'
| 'leadCardTitle'
| 'leadCardDesc'
| 'submitFailed'
| 'error'
| 'statusOnline'
| 'advisorSubtitle'
| 'quickPricing'
| 'quickCases'
| 'quickServices';
export type SendChatInput = {
cardId: string | number;
locale?: string;
messages: ChatMessage[];
text: string;
};
export type AskAiChatFn = (req: AiChatRequest) => Promise<AiChatReply>;
export type SendChatApi = {
askAiChat: AskAiChatFn;
};
export type SendChatResult = {
sent: boolean;
reply: AiChatReply | null;
messages: ChatMessage[];
shouldCollectLead: boolean;
leadHint: string;
};
export type AiChatState = {
cardId: string;
locale: OmniLocale;
draft: string;
sending: boolean;
messages: ChatMessage[];
showLeadHint: boolean;
leadHint: string;
errorMessage: string;
};
export type AiChatPageOptions = {
cardId?: string;
id?: string;
lang?: string;
locale?: string;
};
function isZhLocale(): boolean {
return getCurrentLocale() === 'zh-CN';
}
function cloneMessages(messages: ChatMessage[]): ChatMessage[] {
const next: ChatMessage[] = [];
if (messages == null) {
return next;
}
for (let i = 0; i < messages.length; i++) {
const item = messages[i];
const cloned: ChatMessage = {
role: item.role,
content: item.content,
type: item.type != null ? item.type : 'text',
};
if (item.cardPayload != null) {
cloned.cardPayload = {
title: item.cardPayload.title,
description: item.cardPayload.description,
name: item.cardPayload.name,
contact: item.cardPayload.contact,
submitted: item.cardPayload.submitted,
submitting: item.cardPayload.submitting,
successMessage: item.cardPayload.successMessage,
errorMessage: item.cardPayload.errorMessage,
};
}
next.push(cloned);
}
return next;
}
function resolveCardId(cardId: string | number): string {
if (cardId == null) {
return '';
}
return String(cardId).trim();
}
function requestCardId(cardId: string | number): string | number {
if (typeof cardId === 'number') {
return cardId;
}
return resolveCardId(cardId);
}
function emptySendResult(messages: ChatMessage[]): SendChatResult {
return {
sent: false,
reply: null,
messages,
shouldCollectLead: false,
leadHint: '',
};
}
/**
* 页面文案:标题走 card.ai_chat,其余本地词典
*/
export function resolveAiChatText(key: AiChatCopyKey): string {
const locale = getCurrentLocale();
if (key === 'title') {
return getOmniText(locale, 'card.ai_chat');
}
if (key === 'error') {
return getOmniText(locale, 'common.error');
}
const zh = isZhLocale();
if (key === 'send') {
return zh ? '发送' : 'Send';
}
if (key === 'sending') {
return zh ? '发送中' : 'Sending';
}
if (key === 'placeholder') {
return zh ? '输入你想了解的内容' : 'Ask about this card';
}
if (key === 'empty') {
return zh ? '您好,欢迎咨询。您可以了解业务介绍、服务案例或提交合作需求。' : 'Welcome! Feel free to inquire about our services, cases, or partnership.';
}
if (key === 'leadHint') {
return zh
? '留下您的联系方式,方便进一步沟通。'
: 'Leave your contact details so we can follow up with you.';
}
if (key === 'missingCard') {
return zh ? '缺少名片标识,暂时无法对话。' : 'A card ID is required to start this chat.';
}
if (key === 'inputLabel') {
return zh ? '消息' : 'Message';
}
if (key === 'nameLabel') {
return zh ? '您的称呼' : 'Your Name';
}
if (key === 'namePlaceholder') {
return zh ? '例如:张经理 / Alex' : 'e.g. Alex Smith';
}
if (key === 'contactLabel') {
return zh ? '联系方式' : 'Contact (WhatsApp / Email)';
}
if (key === 'contactPlaceholder') {
return zh ? 'WhatsApp 或 电子邮箱' : 'WhatsApp or Email';
}
if (key === 'submitLead') {
return zh ? '提交需求' : 'Submit Request';
}
if (key === 'submitting') {
return zh ? '提交中' : 'Submitting';
}
if (key === 'submittedSuccess') {
return zh
? '信息已提交,我们将尽快与您联系。'
: 'Information received. We will get in touch shortly.';
}
if (key === 'leadCardTitle') {
return zh ? '获取方案与报价' : 'Proposal & Pricing';
}
if (key === 'leadCardDesc') {
return zh
? '请留下您的联系方式,商务人员将尽快与您对接。'
: 'Leave your contact info and our team will get in touch.';
}
if (key === 'submitFailed') {
return zh ? '提交失败,请重试' : 'Submission failed, please retry';
}
if (key === 'statusOnline') {
return zh ? '在线' : 'Online';
}
if (key === 'advisorSubtitle') {
return zh ? '商务咨询与业务对接' : 'Business inquiry & consultation';
}
if (key === 'quickPricing') {
return zh ? '产品方案与报价' : 'Pricing & Plans';
}
if (key === 'quickCases') {
return zh ? '客户案例' : 'Client Cases';
}
if (key === 'quickServices') {
return zh ? '核心服务能力' : 'Core Capabilities';
}
return '';
}
export function createAiChatState(): AiChatState {
return {
cardId: '',
locale: getCurrentLocale(),
draft: '',
sending: false,
messages: [],
showLeadHint: false,
leadHint: '',
errorMessage: '',
};
}
/**
* 从页面 query 写入 cardId / lang
*/
export function applyAiChatOptions(
state: AiChatState,
options: AiChatPageOptions | null
): boolean {
let cardIdRaw = options != null ? options.cardId || options.id || '' : '';
if (String(cardIdRaw).trim() === '') {
try {
const stored = uni.getStorageSync('omnicard_active_card_id');
if (stored != null && String(stored).trim() !== '') {
cardIdRaw = String(stored).trim();
} else {
cardIdRaw = '1001';
}
} catch (_e) {
cardIdRaw = '1001';
}
}
const langRaw = options != null ? options.lang || options.locale || '' : '';
state.cardId = String(cardIdRaw).trim();
if (langRaw.trim() !== '') {
const locale = resolveDeviceLocale(langRaw);
setCurrentLocale(locale);
state.locale = locale;
} else {
state.locale = getCurrentLocale();
}
return state.cardId !== '';
}
export function buildUserMessage(text: string): ChatMessage {
const content = text != null ? text.trim() : '';
return {
role: 'user',
content,
type: 'text',
};
}
/**
* 追加一轮 user + assistant,返回新数组
*/
export function appendTurn(
messages: ChatMessage[],
userText: string,
reply: AiChatReply
): ChatMessage[] {
const next = cloneMessages(messages);
next.push(buildUserMessage(userText));
const replyText = reply != null && reply.reply != null ? reply.reply : '';
next.push({
role: 'assistant',
content: replyText,
type: 'text',
});
return next;
}
/**
* 发送顾问对话。空文本不发,cardId 必填。可注入 askAiChat
*/
export async function sendChat(
input: SendChatInput,
api?: SendChatApi
): Promise<SendChatResult> {
const text = input != null && input.text != null ? input.text.trim() : '';
const cardId = input != null ? resolveCardId(input.cardId) : '';
const existing = input != null && input.messages != null ? input.messages : [];
if (text === '' || cardId === '') {
return emptySendResult(existing);
}
const outgoing = cloneMessages(existing);
outgoing.push(buildUserMessage(text));
const req: AiChatRequest = {
cardId: requestCardId(input.cardId),
messages: outgoing,
};
const locale = input.locale != null ? input.locale.trim() : '';
if (locale !== '') {
req.locale = locale;
}
const ask = api != null ? api.askAiChat : askAiChat;
const reply = await ask(req);
const shouldCollectLead = reply != null && reply.shouldCollectLead === true;
return {
sent: true,
reply,
messages: appendTurn(existing, text, reply),
shouldCollectLead,
leadHint: shouldCollectLead ? resolveAiChatText('leadHint') : '',
};
}
/**
* 页面发送:写入会话并给出留资引导标记
*/
export async function sendDraft(
state: AiChatState,
api?: SendChatApi
): Promise<SendChatResult> {
if (state.sending) {
return emptySendResult(state.messages);
}
state.sending = true;
state.errorMessage = '';
try {
const result = await sendChat(
{
cardId: state.cardId,
locale: state.locale,
messages: state.messages,
text: state.draft,
},
api
);
if (result.sent) {
state.draft = '';
state.messages = result.messages;
state.showLeadHint = result.shouldCollectLead;
state.leadHint = result.leadHint;
}
state.sending = false;
return result;
} catch (_e) {
state.sending = false;
state.errorMessage = resolveAiChatText('error');
return emptySendResult(state.messages);
}
}
/**
* 意向词检测算法(如“价格”, "报价", "合作", "buy", "quote", "price")
*/
export function hasLeadIntent(text: string): boolean {
if (text == null || text.trim() === '') {
return false;
}
const pattern = /价格|报价|多少钱|合作|联系|购买|buy|quote|price|pricing|cost|partner|cooperat|contact|order|purchase/i;
return pattern.test(text);
}
/**
* 模拟流式推送
*/
export async function sendStreamMessage(
prompt: string,
onChunk: (chunk: string) => void,
onComplete: () => void,
intervalMs: number = 20
): Promise<void> {
if (prompt == null || prompt.trim() === '') {
return;
}
const trimmed = prompt.trim();
const isIntent = hasLeadIntent(trimmed);
const zh = isZhLocale();
let fullReply = '';
if (isIntent) {
fullReply = zh
? '关于方案与合作细节,已为您准备相关资料。请在下方填写您的联系方式,我们将尽快与您对接。'
: 'For pricing and partnership details, we have tailored options. Please leave your details below to receive our pricing sheet.';
} else {
fullReply = zh
? '您好,欢迎咨询。您可以了解业务介绍、服务案例或提交合作需求,我们将尽快与您联系。'
: 'Welcome! I can introduce the profile, business blocks, and our capabilities. Feel free to ask.';
}
const step = 3;
for (let i = 0; i < fullReply.length; i += step) {
const chunk = fullReply.slice(i, i + step);
if (intervalMs > 0) {
await new Promise<void>((resolve) => setTimeout(resolve, intervalMs));
}
onChunk(chunk);
}
onComplete();
}
/**
* 留资卡片负载生成
*/
export function createLeadCardPayload(locale?: string): ChatLeadPayload {
const loc = locale != null && locale !== '' ? locale : getCurrentLocale();
const zh = loc.startsWith('zh');
return {
title: zh ? '获取方案与报价' : 'Proposal & Pricing',
description: zh
? '请留下您的联系方式,商务人员将尽快与您对接。'
: 'Leave your contact info and our team will get in touch.',
name: '',
contact: '',
submitted: false,
submitting: false,
errorMessage: '',
successMessage: '',
};
}
/**
* 创建留资卡片消息
*/
export function createLeadCardMessage(locale?: string): ChatMessage {
return {
role: 'assistant',
content: '',
type: 'lead_card',
cardPayload: createLeadCardPayload(locale),
};
}
/**
* 模拟流式对话发送
*/
export async function sendStreamChat(
state: AiChatState,
text?: string,
options?: { intervalMs?: number }
): Promise<boolean> {
if (state.sending) {
return false;
}
const rawText = text != null ? text : state.draft;
const prompt = rawText != null ? rawText.trim() : '';
if (prompt === '' || state.cardId === '') {
return false;
}
state.sending = true;
state.draft = '';
state.errorMessage = '';
// 1. 用户消息
const userMsg: ChatMessage = {
role: 'user',
content: prompt,
type: 'text',
};
state.messages.push(userMsg);
// 2. 意向检测
const isIntent = hasLeadIntent(prompt);
// 3. 初始 assistant 消息
const assistantMsg: ChatMessage = {
role: 'assistant',
content: '',
type: 'text',
};
state.messages.push(assistantMsg);
// 4. 流式推送
const intervalMs = options != null && options.intervalMs != null ? options.intervalMs : 20;
await sendStreamMessage(
prompt,
(chunk: string) => {
assistantMsg.content += chunk;
},
() => {},
intervalMs
);
// 5. 命中意向词时自动追加留资卡片
if (isIntent) {
state.messages.push(createLeadCardMessage(state.locale));
state.showLeadHint = true;
state.leadHint = resolveAiChatText('leadHint');
}
state.sending = false;
return true;
}
export type SubmitLeadApi = {
submitLead: (params: SubmitLeadParams) => Promise<unknown>;
};
/**
* 提交留资卡片线索
*/
export async function submitLeadCard(
cardId: string | number,
payload: ChatLeadPayload,
api?: SubmitLeadApi
): Promise<boolean> {
if (payload == null) {
return false;
}
const name = payload.name != null ? payload.name.trim() : '';
const contact = payload.contact != null ? payload.contact.trim() : '';
if (name === '' || contact === '') {
return false;
}
payload.submitting = true;
payload.errorMessage = '';
const submitFn = api != null ? api.submitLead : submitLead;
try {
const isEmail = contact.includes('@');
const params: SubmitLeadParams = {
cardId,
name,
source: 'ai_chat',
notes: 'AI顾问留资卡片线索',
};
if (isEmail) {
params.email = contact;
} else {
params.phone = contact;
}
await submitFn(params);
payload.submitted = true;
payload.submitting = false;
payload.successMessage = resolveAiChatText('submittedSuccess');
return true;
} catch (_e) {
payload.submitting = false;
payload.errorMessage = resolveAiChatText('submitFailed');
return false;
}
}