Update server.js via upload script - 2026-08-13 13:53:19
This commit is contained in:
parent
9a66d7c333
commit
f3c8aecd25
66
server.js
66
server.js
|
|
@ -107,29 +107,6 @@ function saveAiConfig(apiKey) {
|
||||||
fs.writeFileSync(AI_CONFIG_FILE, JSON.stringify({ apiKeyEnc: encrypt(apiKey) }, null, 2), 'utf8');
|
fs.writeFileSync(AI_CONFIG_FILE, JSON.stringify({ apiKeyEnc: encrypt(apiKey) }, null, 2), 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
const REGION_LABELS = {
|
|
||||||
africa: 'Africa', middle_east: 'Middle East', australia: 'Australia/Oceania',
|
|
||||||
europe: 'Europe', sea: 'Southeast Asia', other: 'Other',
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildCustomerContext(c) {
|
|
||||||
const lines = [];
|
|
||||||
lines.push(`Company: ${c.company || '(unknown / not on file)'}`);
|
|
||||||
if (c.contact) lines.push(`Contact person: ${c.contact}`);
|
|
||||||
const place = [c.country, REGION_LABELS[c.region]].filter(Boolean).join(', ');
|
|
||||||
if (place) lines.push(`Location: ${place}`);
|
|
||||||
if (c.productLines && c.productLines.length) lines.push(`Product interest: ${c.productLines.join(', ')}`);
|
|
||||||
if (c.intendedModel || c.intendedQty) {
|
|
||||||
lines.push(`Specific model interest: ${c.intendedModel || '(model unspecified)'}${c.intendedQty ? `, quantity: ${c.intendedQty}` : ''}`);
|
|
||||||
}
|
|
||||||
if (c.lastContact) lines.push(`Last contact date: ${c.lastContact}`);
|
|
||||||
if (c.notes) lines.push(`Background research on this customer: ${c.notes}`);
|
|
||||||
if (c.tags) lines.push(`Additional remarks: ${c.tags}`);
|
|
||||||
if (c.isVip) lines.push('This is a VIP / priority customer — extra attentive tone.');
|
|
||||||
if (c.hasOrdered) lines.push('This customer has already placed an order before — this follow-up is with an existing buyer, not a cold lead.');
|
|
||||||
return lines.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function callDeepSeek(apiKey, messages, opts) {
|
async function callDeepSeek(apiKey, messages, opts) {
|
||||||
const { maxTokens = 600, temperature = 0.7, model = 'deepseek-v4-flash' } = opts || {};
|
const { maxTokens = 600, temperature = 0.7, model = 'deepseek-v4-flash' } = opts || {};
|
||||||
const resp = await fetch(`${DEEPSEEK_API_BASE}/chat/completions`, {
|
const resp = await fetch(`${DEEPSEEK_API_BASE}/chat/completions`, {
|
||||||
|
|
@ -384,49 +361,6 @@ const aiLimiter = rateLimit({
|
||||||
message: { error: 'too many AI requests, please slow down' },
|
message: { error: 'too many AI requests, please slow down' },
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/ai/draft', requireAuth, aiLimiter, async (req, res) => {
|
|
||||||
const body = req.body || {};
|
|
||||||
const customerId = body.customerId;
|
|
||||||
const channel = body.channel;
|
|
||||||
if (!customerId || !['email', 'whatsapp'].includes(channel)) {
|
|
||||||
return res.status(400).json({ error: 'invalid request' });
|
|
||||||
}
|
|
||||||
const cfg = loadAiConfig();
|
|
||||||
if (!cfg.apiKey) return res.status(400).json({ error: 'DeepSeek API key not configured' });
|
|
||||||
|
|
||||||
const data = readData();
|
|
||||||
let customers = [];
|
|
||||||
try { customers = JSON.parse(data['crm:customers'] || '[]'); } catch (e) {}
|
|
||||||
const customer = customers.find((c) => c.id === customerId);
|
|
||||||
if (!customer) return res.status(404).json({ error: 'customer not found' });
|
|
||||||
|
|
||||||
const context = buildCustomerContext(customer);
|
|
||||||
const systemPrompt = 'You are a sales assistant helping a B2B export salesperson at a Chinese power-equipment company (UPS systems, rectifiers, solar inverters, battery storage) write natural, professional follow-up outreach to overseas customers. Always write in English. Never sound like a generic template, reference specifics from the customer info given. Output only the requested content, with no meta-commentary, explanations, or markdown formatting.';
|
|
||||||
|
|
||||||
const userPrompt = channel === 'email'
|
|
||||||
? `Customer info:\n${context}\n\nWrite a short, warm but professional follow-up email to this customer. Respond in EXACTLY this format with no extra text:\nSUBJECT: <subject line>\nBODY:\n<email body, 3-5 short paragraphs max, sign off with "[Your name]">`
|
|
||||||
: `Customer info:\n${context}\n\nWrite a short, casual WhatsApp message to check in with this customer (2-4 sentences, friendly tone, no formal greeting like "Dear", a light emoji is fine if it feels natural). Respond with ONLY the message text, nothing else.`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const content = await callDeepSeek(cfg.apiKey, [
|
|
||||||
{ role: 'system', content: systemPrompt },
|
|
||||||
{ role: 'user', content: userPrompt },
|
|
||||||
]);
|
|
||||||
if (channel === 'email') {
|
|
||||||
const subjectMatch = content.match(/SUBJECT:\s*(.+)/i);
|
|
||||||
const bodyMatch = content.match(/BODY:\s*([\s\S]*)/i);
|
|
||||||
res.json({
|
|
||||||
subject: subjectMatch ? subjectMatch[1].trim() : 'Following up',
|
|
||||||
body: bodyMatch ? bodyMatch[1].trim() : content.trim(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.json({ body: content.trim() });
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
res.status(502).json({ error: e.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* --------------------------- ai catalog import --------------------------- */
|
/* --------------------------- ai catalog import --------------------------- */
|
||||||
// 上传一份/多份PDF或图片形式的产品目录(可能包含多个型号),做OCR文字识别后交给
|
// 上传一份/多份PDF或图片形式的产品目录(可能包含多个型号),做OCR文字识别后交给
|
||||||
// DeepSeek解析成结构化的产品字段数组,前端拿到后先给用户一个预览/确认页面,
|
// DeepSeek解析成结构化的产品字段数组,前端拿到后先给用户一个预览/确认页面,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue