| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- import { createServer, IncomingMessage, ServerResponse } from 'node:http';
- type JsonValue = Record<string, unknown> | Array<unknown>;
- interface Candidate {
- platform: string;
- name: string;
- fans: string;
- price: number;
- score: number;
- styleMatch: number;
- status: string;
- reason: string;
- risk: string;
- }
- const port = Number(process.env.TIHAO_AI_API_PORT ?? 4300);
- const demoRequirements = [
- { label: '客户/品牌', value: '客户A / 新锐护肤品牌', confidence: 96 },
- { label: '投放目标', value: '新品种草、真实测评、站内转化蓄水', confidence: 92 },
- { label: '平台需求', value: '小红书 20人、抖音 8人、B站 4人', confidence: 90 },
- { label: '内容风格', value: '真实体验、通勤生活、轻专业成分党', confidence: 88 }
- ];
- const demoRules = [
- '按 Brief 平台优先提号',
- '默认推荐数量不少于需求 3 倍',
- '风格匹配低于 80% 自动降级',
- '黑名单与低配合度剔除',
- '优先近期采集有效价格'
- ];
- const demoCandidates: Candidate[] = [
- {
- platform: '小红书',
- name: '梨涡测评室',
- fans: '18.6万',
- price: 6800,
- score: 94,
- styleMatch: 91,
- status: '强推荐',
- reason: '近10篇中 9 篇为真实体验或成分拆解,互动中位数高于同价位账号 28%。',
- risk: '视频内容偏少,可作为图文主推。'
- },
- {
- platform: '抖音',
- name: '林小林认真护肤',
- fans: '42.1万',
- price: 12800,
- score: 87,
- styleMatch: 82,
- status: '备选',
- reason: '爆文率和完播率表现稳定,预算接近上限,建议作为抖音头部备选。',
- risk: '单条报价较高,需确认档期。'
- }
- ];
- function sendJson(response: ServerResponse, statusCode: number, payload: JsonValue): void {
- const body = JSON.stringify(payload);
- response.writeHead(statusCode, {
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Headers': 'Content-Type',
- 'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
- 'Content-Type': 'application/json; charset=utf-8',
- 'Content-Length': Buffer.byteLength(body)
- });
- response.end(body);
- }
- function sendExcel(response: ServerResponse, filename: string, candidates: Candidate[]): void {
- const rows = candidates
- .map(
- (candidate) => `
- <tr>
- <td>${escapeHtml(candidate.platform)}</td>
- <td>${escapeHtml(candidate.name)}</td>
- <td>${escapeHtml(candidate.fans)}</td>
- <td>${candidate.price}</td>
- <td>${candidate.score}</td>
- <td>${candidate.styleMatch}%</td>
- <td>${escapeHtml(candidate.status)}</td>
- <td>${escapeHtml(candidate.reason)}</td>
- <td>${escapeHtml(candidate.risk)}</td>
- </tr>`
- )
- .join('');
- const body = `
- <html>
- <head>
- <meta charset="UTF-8">
- <style>
- table { border-collapse: collapse; font-family: "Microsoft YaHei", Arial, sans-serif; }
- th { background: #1f8a70; color: #fff; }
- th, td { border: 1px solid #cfd7d1; padding: 8px 10px; }
- td:nth-child(5), td:nth-child(6) { background: #eef7f3; font-weight: 700; }
- </style>
- </head>
- <body>
- <table>
- <tr>
- <th>平台</th><th>账号</th><th>粉丝量</th><th>报价</th><th>综合分</th><th>风格匹配</th><th>推荐状态</th><th>推荐理由</th><th>风险提示</th>
- </tr>
- ${rows}
- </table>
- </body>
- </html>`;
- response.writeHead(200, {
- 'Access-Control-Allow-Origin': '*',
- 'Content-Type': 'application/vnd.ms-excel; charset=utf-8',
- 'Content-Disposition': `attachment; filename="${encodeURIComponent(filename)}"`,
- 'Content-Length': Buffer.byteLength(body)
- });
- response.end(body);
- }
- function readBody(request: IncomingMessage): Promise<Record<string, unknown>> {
- return new Promise((resolve) => {
- let body = '';
- request.on('data', (chunk) => {
- body += chunk;
- });
- request.on('end', () => {
- if (!body) {
- resolve({});
- return;
- }
- try {
- resolve(JSON.parse(body));
- } catch {
- resolve({});
- }
- });
- });
- }
- function escapeHtml(value: string): string {
- return value
- .replaceAll('&', '&')
- .replaceAll('<', '<')
- .replaceAll('>', '>')
- .replaceAll('"', '"');
- }
- const server = createServer(async (request, response) => {
- const requestUrl = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);
- if (request.method === 'OPTIONS') {
- sendJson(response, 200, { ok: true });
- return;
- }
- if (request.method === 'GET' && requestUrl.pathname === '/api/health') {
- sendJson(response, 200, {
- ok: true,
- name: '提号AI Mock API',
- resourcesPath: 'D:\\公司优秀媒体资源库\\4月份资源库'
- });
- return;
- }
- if (request.method === 'POST' && requestUrl.pathname === '/api/analyze-brief') {
- sendJson(response, 200, {
- requirements: demoRequirements,
- rules: demoRules,
- message: 'Demo 已模拟解析 Brief,真实环境会接入 Excel/Docx 解析与 LLM 抽取。'
- });
- return;
- }
- if (request.method === 'POST' && requestUrl.pathname === '/api/recommend') {
- sendJson(response, 200, {
- candidates: demoCandidates,
- totalPool: 104,
- message: 'Demo 已模拟按规则生成推荐池。'
- });
- return;
- }
- if (request.method === 'POST' && requestUrl.pathname === '/api/rules/learn') {
- sendJson(response, 200, {
- suggestions: [
- {
- title: '小红书减少泛美妆号,优先生活方式和通勤场景',
- confidence: 93
- },
- {
- title: '抖音预算压低时保留爆文率高账号',
- confidence: 87
- }
- ]
- });
- return;
- }
- if (request.method === 'POST' && requestUrl.pathname === '/api/recommendations/export') {
- const body = await readBody(request);
- const candidates = Array.isArray(body['candidates'])
- ? (body['candidates'] as Candidate[])
- : demoCandidates;
- sendExcel(response, '客户A-新锐护肤品牌-需求推荐名单.xls', candidates);
- return;
- }
- sendJson(response, 404, { ok: false, message: 'Not found' });
- });
- server.listen(port, () => {
- console.log(`Tihao AI mock API listening on http://localhost:${port}`);
- });
|