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');
|
||||
}
|
||||
|
||||
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) {
|
||||
const { maxTokens = 600, temperature = 0.7, model = 'deepseek-v4-flash' } = opts || {};
|
||||
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' },
|
||||
});
|
||||
|
||||
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 --------------------------- */
|
||||
// 上传一份/多份PDF或图片形式的产品目录(可能包含多个型号),做OCR文字识别后交给
|
||||
// DeepSeek解析成结构化的产品字段数组,前端拿到后先给用户一个预览/确认页面,
|
||||
|
|
|
|||
Loading…
Reference in New Issue