| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146 |
- const http = require('http');
- const fs = require('fs');
- const path = require('path');
- const { URL } = require('url');
- const { outputsRoot, categoryDir } = require('../core/output-paths');
- const { resolveWorkspaceRoot, workspaceIdentity } = require('../core/runtime-context');
- const {
- qiweiSyncExternalGroups,
- qiweiListExternalGroups,
- qiweiAnalyzeGroupMembers,
- qiweiConfirmExternalGroup,
- qiweiAddExternalGroup,
- qiweiConfigureGroupKeywords,
- qiweiRejectExternalGroup,
- qiweiSyncGroupMessages
- } = require('../tools/qiwei-group-management-run');
- const {
- qiweiGroupOpsGetContext,
- qiweiGroupOpsGeneratePlan,
- qiweiGroupOpsBatchGeneratePlans,
- qiweiGroupOpsReviewMessages,
- qiweiGroupOpsManagePlaybook,
- qiweiGroupOpsExecuteItem,
- qiweiGroupOpsUpdateFinding,
- qiweiGroupOpsInsights,
- qiweiGroupOpsManageTask,
- qiweiGroupOpsAutomation
- } = require('../tools/qiwei-group-operations-run');
- const {
- qiweiLoginStatus,
- qiweiLoginStart,
- qiweiLoginCheck,
- qiweiLoginVerify
- } = require('../tools/qiwei-login-run');
- const { qiweiSubscriptionStatus } = require('../tools/qiwei-subscription-run');
- const { createAccountConnectionMonitor } = require('../core/account-connection-monitor');
- const {
- qiweiBatchAddFriends,
- qiweiCheckFriendStatus,
- qiweiGetCustomerProfile,
- qiweiAutoCreateGroup,
- qiweiUpdateCustomerInfo
- } = require('../tools/qiwei-customer-ops-run');
- const {
- qiweiUpdateCustomerPortrait,
- qiweiSaveCustomerPortrait,
- qiweiBatchUpdateCustomerPortrait,
- qiweiExportCustomerPortraits,
- qiweiListCustomerTags,
- qiweiListAllTags,
- qiweiAddCustomerTags,
- qiweiRemoveCustomerTags,
- qiweiSyncPersonalLabels,
- qiweiCreatePersonalLabel,
- qiweiUpdatePersonalLabel,
- qiweiDeletePersonalLabel,
- qiweiApplyPersonalLabels
- } = require('../tools/qiwei-portrait-tags-run');
- const {
- qiweiPreviewTransferPackage,
- qiweiExecuteTransfer
- } = require('../tools/qiwei-customer-transfer-run');
- const {
- getStateSection,
- rebuildStateFromOutputs
- } = require('../core/dashboard-state');
- const {
- switchActiveAccount,
- getAgentStatus,
- getAllowlistCandidates,
- updateAllowlist,
- getConversations,
- getResponseMonitor,
- getGroupAgents,
- changeGroupMode,
- generateGroupReply,
- approveGroupDraft,
- rejectGroupDraft,
- regenerateGroupDraft,
- syncConversations,
- changeGlobalMode,
- changeConversationMode,
- approveReply,
- approveDraft,
- rejectDraft,
- regenerateDraft,
- generateLatestDraft,
- manualSend,
- getVoiceStatus,
- enrollVoice,
- revokeVoiceProfile,
- sendClonedVoice,
- getSentVoiceAudio,
- updateCustomerTask,
- syncCustomerTaskToOfficialTodo,
- updateCustomerAlert,
- getAudit,
- startListener,
- stopListener
- } = require('./agent-service');
- const {
- listKnowledgeTree,
- readKnowledgeFile,
- listProperties,
- getProperty,
- listSkillRegistry,
- getSkillDetail
- } = require('./workspace-library-service');
- const {
- getMeetingKnowledgeHub,
- syncMeetingKnowledge,
- analyzeMeetingKnowledge,
- getMeetingKnowledge
- } = require('./meeting-knowledge-service');
- const {
- getDocumentKnowledgeHub,
- importDocumentKnowledge,
- createDocumentKnowledge,
- analyzeDocumentKnowledge,
- refreshDocumentKnowledge,
- getDocumentKnowledge,
- getTodoKnowledgeHub,
- searchTodoUsers,
- syncTodoKnowledge,
- refreshTodoKnowledgeDetails,
- createTodoKnowledge,
- completeTodoKnowledge
- } = require('./official-office-knowledge-service');
- const {
- getUnifiedTaskHub,
- createUnifiedLocalTask,
- createUnifiedDiagnosisCandidate,
- updateUnifiedDiagnosisOutcome,
- updateUnifiedTask
- } = require('./unified-task-service');
- const {
- getCustomerMasterHub,
- updateCustomerMaster,
- updateCustomerRecommendationFeedback
- } = require('./customer-master-service');
- const { qiweiBusinessDiagnosis } = require('../tools/qiwei-business-diagnosis-run');
- const DASHBOARD_PORT = process.env.QIWEI_DASHBOARD_PORT || 4320;
- const STATIC_DIR = path.join(__dirname);
- const TMP_DIR = path.join(outputsRoot(), 'tmp');
- const WORKSPACE_ID = workspaceIdentity(resolveWorkspaceRoot());
- const jobs = new Map();
- const accountConnectionMonitor = createAccountConnectionMonitor({
- checkStatus: () => qiweiLoginStatus({}),
- recoverLogin: () => qiweiLoginCheck({ manual: true, persistConfig: false }),
- // 后台每 15 秒主动探测一次;页面读取复用最近结果,避免再次阻塞远端登录状态接口。
- cacheTtlMs: 20000
- });
- accountConnectionMonitor.start();
- void accountConnectionMonitor.getStatus().catch(() => {});
- const SUBSCRIPTION_STATUS_CACHE_TTL_MS = 2 * 60 * 1000;
- let subscriptionStatusCache = null;
- let subscriptionStatusCachedAt = 0;
- let subscriptionStatusInFlight = null;
- async function getCachedSubscriptionStatus({ force = false } = {}) {
- const fresh = subscriptionStatusCache && Date.now() - subscriptionStatusCachedAt < SUBSCRIPTION_STATUS_CACHE_TTL_MS;
- if (!force && fresh) return subscriptionStatusCache;
- if (subscriptionStatusInFlight) return subscriptionStatusInFlight;
- subscriptionStatusInFlight = qiweiSubscriptionStatus({})
- .then(result => {
- subscriptionStatusCache = result;
- subscriptionStatusCachedAt = Date.now();
- return result;
- })
- .finally(() => { subscriptionStatusInFlight = null; });
- return subscriptionStatusInFlight;
- }
- // Dashboard 启动后立即预热较慢的远程订阅状态,用户进入账号页时通常可直接命中缓存。
- void getCachedSubscriptionStatus().catch(() => {});
- const CUSTOMER_DIRECTORY_CACHE_TTL_MS = 60 * 1000;
- let customerDirectoryCache = null;
- let customerDirectoryCachedAt = 0;
- let customerDirectoryInFlight = null;
- async function buildDashboardCustomerDirectory() {
- const merged = new Map();
- const mergeCustomer = customer => {
- const externalUserId = String(customer.externalUserId || customer.customerId || '').trim();
- if (!externalUserId) return;
- const current = merged.get(externalUserId) || {};
- merged.set(externalUserId, {
- ...current,
- ...customer,
- externalUserId,
- customerId: customer.customerId || current.customerId || externalUserId,
- name: customer.name || customer.displayName || current.name || '',
- phone: customer.phone || current.phone || '',
- tags: Array.isArray(customer.tags) ? customer.tags : (current.tags || []),
- hasPortrait: customer.hasPortrait !== undefined
- ? Boolean(customer.hasPortrait)
- : Boolean((customer.fields || current.fields || []).length || current.hasPortrait),
- });
- };
- try {
- const allowlist = await getAllowlistCandidates();
- for (const contact of allowlist.data?.contacts || []) {
- mergeCustomer({
- externalUserId: contact.id,
- name: contact.displayName,
- selected: contact.selected === true,
- friendRequestStatus: 'ACCEPTED',
- source: 'qiwei-contact',
- });
- }
- } catch {
- // Local and conversation-backed customers remain available during a remote outage.
- }
- const local = getStateSection('customers');
- for (const customer of Object.values(local.customers || {})) mergeCustomer(customer);
- const master = getCustomerMasterHub();
- for (const customer of master.data?.customers || []) {
- mergeCustomer({
- ...customer,
- name: customer.displayName,
- hasPortrait: Array.isArray(customer.fields) && customer.fields.length > 0,
- });
- }
- return Array.from(merged.values());
- }
- async function getDashboardCustomerDirectory({ force = false } = {}) {
- const fresh = customerDirectoryCache && Date.now() - customerDirectoryCachedAt < CUSTOMER_DIRECTORY_CACHE_TTL_MS;
- if (!force && fresh) return customerDirectoryCache;
- if (customerDirectoryInFlight) return customerDirectoryInFlight;
- customerDirectoryInFlight = buildDashboardCustomerDirectory()
- .then(customers => {
- customerDirectoryCache = customers;
- customerDirectoryCachedAt = Date.now();
- return customers;
- })
- .finally(() => { customerDirectoryInFlight = null; });
- return customerDirectoryInFlight;
- }
- // 预热共享客户目录,画像、交接和客户选择器进入时可直接复用。
- void getDashboardCustomerDirectory().catch(() => {});
- function ensureTmpDir() {
- fs.mkdirSync(TMP_DIR, { recursive: true });
- }
- function readBody(req, maxBytes = 30 * 1024 * 1024) {
- return new Promise((resolve, reject) => {
- const chunks = [];
- let size = 0;
- let exceeded = false;
- req.on('data', chunk => {
- if (exceeded) return;
- size += chunk.length;
- if (size > maxBytes) {
- exceeded = true;
- reject(new Error('请求内容过大'));
- return;
- }
- chunks.push(chunk);
- });
- req.on('end', () => {
- if (exceeded) return;
- try {
- resolve(JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}'));
- } catch {
- resolve({});
- }
- });
- req.on('error', reject);
- });
- }
- function json(res, status, body) {
- res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
- res.end(JSON.stringify(body));
- }
- function createJob(runFn) {
- const id = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
- const job = { id, status: 'running', progress: 0, result: null, error: null, startedAt: Date.now() };
- jobs.set(id, job);
- runFn()
- .then(r => { job.status = 'done'; job.progress = 100; job.result = r; })
- .catch(e => { job.status = 'error'; job.error = e.message || String(e); });
- return id;
- }
- function getJob(id) {
- const job = jobs.get(id);
- if (!job) return null;
- if (job.status === 'running') {
- const elapsed = Date.now() - job.startedAt;
- job.progress = Math.min(95, Math.floor((elapsed / 30000) * 100));
- }
- return job;
- }
- function serveStatic(req, res, filePath, contentType) {
- fs.readFile(filePath, (err, data) => {
- if (err) {
- res.writeHead(404);
- res.end('not found');
- return;
- }
- res.writeHead(200, {
- 'Content-Type': contentType,
- 'Cache-Control': 'no-store'
- });
- res.end(data);
- });
- }
- function saveUploadBody(body) {
- const base64 = String(body.data || '');
- const requestedName = path.basename(String(body.name || `upload-${Date.now()}`)).replace(/[^A-Za-z0-9._\-\u4e00-\u9fa5]/g, '_');
- const name = !requestedName || requestedName === '.' || requestedName === '..' ? `upload-${Date.now()}` : requestedName;
- if (!base64) throw new Error('缺少文件数据');
- const maxBytes = 20 * 1024 * 1024;
- if (base64.length > Math.ceil(maxBytes / 3) * 4 + 16) throw new Error('上传文件不能超过 20MB');
- ensureTmpDir();
- const buffer = Buffer.from(base64, 'base64');
- if (buffer.length > maxBytes) throw new Error('上传文件不能超过 20MB');
- const filePath = path.join(TMP_DIR, name);
- fs.writeFileSync(filePath, buffer);
- return { path: filePath };
- }
- async function handleUpload(req) {
- return saveUploadBody(await readBody(req));
- }
- function handleOutputsDownload(req, res, query) {
- const requested = String(query.path || '');
- if (!requested) {
- json(res, 400, { status: 'error', message: '缺少 path 参数' });
- return;
- }
- const resolved = path.resolve(requested);
- const root = path.resolve(outputsRoot());
- if (!resolved.startsWith(root)) {
- json(res, 403, { status: 'error', message: '禁止访问 outputs 目录之外的文件' });
- return;
- }
- if (!fs.existsSync(resolved)) {
- json(res, 404, { status: 'error', message: '文件不存在' });
- return;
- }
- const data = fs.readFileSync(resolved);
- const ext = path.extname(resolved).toLowerCase();
- const contentType = {
- '.json': 'application/json',
- '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
- '.xls': 'application/vnd.ms-excel',
- '.png': 'image/png',
- '.jpg': 'image/jpeg',
- '.jpeg': 'image/jpeg',
- '.mp3': 'audio/mpeg',
- '.mp4': 'video/mp4'
- }[ext] || 'application/octet-stream';
- res.writeHead(200, {
- 'Content-Type': contentType,
- 'Content-Disposition': `attachment; filename="${path.basename(resolved)}"`
- });
- res.end(data);
- }
- function serveSentVoiceAudio(req, res, requested) {
- const resolved = path.resolve(String(requested || ''));
- const root = path.resolve(categoryDir('voice'));
- const relative = path.relative(root, resolved);
- if (!relative || relative.startsWith('..') || path.isAbsolute(relative)
- || path.basename(resolved).toLowerCase() !== 'speech.wav'
- || !relative.split(path.sep).some(part => /^\d{6}-clone-[a-z0-9-]+$/i.test(part))
- || !fs.existsSync(resolved)) {
- json(res, 404, { status: 'error', message: '已发送语音文件不存在' });
- return;
- }
- const size = fs.statSync(resolved).size;
- const range = String(req.headers.range || '').match(/^bytes=(\d*)-(\d*)$/);
- let start = 0;
- let end = size - 1;
- if (range) {
- if (range[1]) start = Number(range[1]);
- if (range[2]) end = Number(range[2]);
- if (!range[1] && range[2]) start = Math.max(0, size - Number(range[2]));
- if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end < start || start >= size) {
- res.writeHead(416, { 'Content-Range': `bytes */${size}` });
- res.end();
- return;
- }
- end = Math.min(end, size - 1);
- }
- const headers = {
- 'Content-Type': 'audio/wav',
- 'Content-Length': end - start + 1,
- 'Accept-Ranges': 'bytes',
- 'Cache-Control': 'private, no-store',
- 'Content-Disposition': 'inline; filename="speech.wav"',
- };
- if (range) headers['Content-Range'] = `bytes ${start}-${end}/${size}`;
- res.writeHead(range ? 206 : 200, headers);
- fs.createReadStream(resolved, { start, end }).pipe(res);
- }
- function getContentType(filePath) {
- const ext = path.extname(filePath).toLowerCase();
- const map = {
- '.html': 'text/html; charset=utf-8',
- '.css': 'text/css; charset=utf-8',
- '.js': 'application/javascript; charset=utf-8',
- '.png': 'image/png',
- '.jpg': 'image/jpeg',
- '.jpeg': 'image/jpeg',
- '.svg': 'image/svg+xml'
- };
- return map[ext] || 'application/octet-stream';
- }
- async function combinedStatus({ fast = false } = {}) {
- const statusPromise = Promise.all([
- accountConnectionMonitor.getStatus(),
- getCachedSubscriptionStatus()
- ]);
- let login;
- let subscription;
- if (fast) {
- const timeout = Symbol('status-timeout');
- const result = await Promise.race([
- statusPromise,
- new Promise(resolve => setTimeout(() => resolve(timeout), 180))
- ]);
- if (result === timeout) {
- login = accountConnectionMonitor.state.lastOutput || { status: 'pending', summary: {}, data: {} };
- subscription = subscriptionStatusCache || { status: 'pending', summary: {}, data: {} };
- } else {
- [login, subscription] = result;
- }
- } else {
- [login, subscription] = await statusPromise;
- }
- const loginPending = login?.status === 'pending';
- const subscriptionPending = subscription?.status === 'pending';
- return {
- status: 'ok',
- summary: {
- authConfigured: loginPending ? null : login.status !== 'needs_auth',
- online: loginPending ? null : !!login.summary?.online,
- subscribed: subscriptionPending ? null : !!subscription.summary?.subscribed,
- loginPending,
- subscriptionPending
- },
- data: { login, subscription }
- };
- }
- async function handleRequest(req, res) {
- const url = new URL(req.url, 'http://localhost');
- const pathname = url.pathname;
- try {
- if (pathname === '/' || pathname === '/index.html') {
- serveStatic(req, res, path.join(STATIC_DIR, 'index.html'), 'text/html; charset=utf-8');
- return;
- }
- if (pathname.startsWith('/dashboard/')) {
- const fileName = pathname.slice('/dashboard/'.length).replace(/\.{2,}/g, '');
- const filePath = path.join(STATIC_DIR, fileName);
- if (!filePath.startsWith(STATIC_DIR)) {
- json(res, 403, { status: 'error', message: '禁止访问' });
- return;
- }
- serveStatic(req, res, filePath, getContentType(filePath));
- return;
- }
- if (pathname === '/api/health') {
- json(res, 200, { status: 'ok', data: { workspaceId: WORKSPACE_ID, port: Number(DASHBOARD_PORT) } });
- return;
- }
- if (pathname === '/api/status' && req.method === 'GET') {
- json(res, 200, await combinedStatus({ fast: url.searchParams.get('fast') === 'true' }));
- return;
- }
- if (pathname === '/api/skills' && req.method === 'GET') {
- json(res, 200, listSkillRegistry());
- return;
- }
- if (pathname === '/api/knowledge/tree' && req.method === 'GET') {
- json(res, 200, listKnowledgeTree());
- return;
- }
- if (pathname === '/api/knowledge/file' && req.method === 'GET') {
- json(res, 200, readKnowledgeFile(url.searchParams.get('id')));
- return;
- }
- if (pathname === '/api/knowledge/meetings' && req.method === 'GET') {
- json(res, 200, await getMeetingKnowledgeHub());
- return;
- }
- if (pathname === '/api/knowledge/meetings/sync' && req.method === 'POST') {
- json(res, 200, await syncMeetingKnowledge(await readBody(req)));
- return;
- }
- const meetingAnalyzeRoute = pathname.match(/^\/api\/knowledge\/meetings\/([^/]+)\/analyze$/);
- if (meetingAnalyzeRoute && req.method === 'POST') {
- json(res, 200, await analyzeMeetingKnowledge(decodeURIComponent(meetingAnalyzeRoute[1])));
- return;
- }
- const meetingDetailRoute = pathname.match(/^\/api\/knowledge\/meetings\/([^/]+)$/);
- if (meetingDetailRoute && req.method === 'GET') {
- json(res, 200, getMeetingKnowledge(decodeURIComponent(meetingDetailRoute[1])));
- return;
- }
- if (pathname === '/api/knowledge/docs' && req.method === 'GET') {
- json(res, 200, await getDocumentKnowledgeHub());
- return;
- }
- if (pathname === '/api/knowledge/docs/import' && req.method === 'POST') {
- json(res, 200, await importDocumentKnowledge(await readBody(req)));
- return;
- }
- if (pathname === '/api/knowledge/docs/create' && req.method === 'POST') {
- json(res, 200, await createDocumentKnowledge(await readBody(req)));
- return;
- }
- const documentAnalyzeRoute = pathname.match(/^\/api\/knowledge\/docs\/([^/]+)\/analyze$/);
- if (documentAnalyzeRoute && req.method === 'POST') {
- json(res, 200, await analyzeDocumentKnowledge(decodeURIComponent(documentAnalyzeRoute[1])));
- return;
- }
- const documentRefreshRoute = pathname.match(/^\/api\/knowledge\/docs\/([^/]+)\/refresh$/);
- if (documentRefreshRoute && req.method === 'POST') {
- json(res, 200, await refreshDocumentKnowledge(decodeURIComponent(documentRefreshRoute[1])));
- return;
- }
- const documentDetailRoute = pathname.match(/^\/api\/knowledge\/docs\/([^/]+)$/);
- if (documentDetailRoute && req.method === 'GET') {
- json(res, 200, getDocumentKnowledge(decodeURIComponent(documentDetailRoute[1])));
- return;
- }
- if (pathname === '/api/knowledge/todos' && req.method === 'GET') {
- json(res, 200, await getTodoKnowledgeHub());
- return;
- }
- if (pathname === '/api/knowledge/tasks' && req.method === 'GET') {
- json(res, 200, getUnifiedTaskHub());
- return;
- }
- if (pathname === '/api/knowledge/tasks/create' && req.method === 'POST') {
- json(res, 200, createUnifiedLocalTask(await readBody(req)));
- return;
- }
- if (pathname === '/api/knowledge/tasks/diagnosis-candidate' && req.method === 'POST') {
- json(res, 200, createUnifiedDiagnosisCandidate(await readBody(req)));
- return;
- }
- if (pathname === '/api/knowledge/tasks/diagnosis-feedback' && req.method === 'POST') {
- json(res, 200, updateUnifiedDiagnosisOutcome(await readBody(req)));
- return;
- }
- if (pathname === '/api/knowledge/tasks/update' && req.method === 'POST') {
- json(res, 200, await updateUnifiedTask(await readBody(req)));
- return;
- }
- if (pathname === '/api/knowledge/todos/search-users' && req.method === 'POST') {
- json(res, 200, await searchTodoUsers(await readBody(req)));
- return;
- }
- if (pathname === '/api/knowledge/todos/sync' && req.method === 'POST') {
- json(res, 200, await syncTodoKnowledge(await readBody(req)));
- return;
- }
- if (pathname === '/api/knowledge/todos/details' && req.method === 'POST') {
- json(res, 200, await refreshTodoKnowledgeDetails(await readBody(req)));
- return;
- }
- if (pathname === '/api/knowledge/todos/create' && req.method === 'POST') {
- json(res, 200, await createTodoKnowledge(await readBody(req)));
- return;
- }
- const todoCompleteRoute = pathname.match(/^\/api\/knowledge\/todos\/([^/]+)\/complete$/);
- if (todoCompleteRoute && req.method === 'POST') {
- json(res, 200, await completeTodoKnowledge(decodeURIComponent(todoCompleteRoute[1])));
- return;
- }
- if (pathname === '/api/knowledge/properties' && req.method === 'GET') {
- json(res, 200, listProperties(Object.fromEntries(url.searchParams.entries())));
- return;
- }
- const propertyRoute = pathname.match(/^\/api\/knowledge\/properties\/([^/]+)$/);
- if (propertyRoute && req.method === 'GET') {
- json(res, 200, getProperty(decodeURIComponent(propertyRoute[1])));
- return;
- }
- const skillRoute = pathname.match(/^\/api\/skills\/(.+)$/);
- if (skillRoute && req.method === 'GET') {
- json(res, 200, getSkillDetail(decodeURIComponent(skillRoute[1])));
- return;
- }
- if (pathname === '/api/agent/status' && req.method === 'GET') {
- json(res, 200, await getAgentStatus());
- return;
- }
- if (pathname === '/api/agent/voice/status' && req.method === 'GET') {
- json(res, 200, getVoiceStatus());
- return;
- }
- if (pathname === '/api/agent/voice/profile' && req.method === 'POST') {
- const body = await readBody(req);
- const upload = saveUploadBody(body);
- try {
- json(res, 200, await enrollVoice({
- filePath: upload.path,
- originalName: body.name,
- mime: body.type,
- }));
- } finally {
- fs.rmSync(upload.path, { force: true });
- }
- return;
- }
- if (pathname === '/api/agent/voice/profile' && req.method === 'DELETE') {
- json(res, 200, revokeVoiceProfile());
- return;
- }
- if (pathname === '/api/agent/allowlist' && req.method === 'GET') {
- json(res, 200, await getAllowlistCandidates());
- return;
- }
- if (pathname === '/api/agent/allowlist' && req.method === 'POST') {
- json(res, 200, updateAllowlist(await readBody(req)));
- return;
- }
- if (pathname === '/api/accounts/switch' && req.method === 'POST') {
- json(res, 200, await switchActiveAccount(await readBody(req)));
- return;
- }
- if (pathname === '/api/agent/conversations' && req.method === 'GET') {
- json(res, 200, getConversations());
- return;
- }
- if (pathname === '/api/agent/response-monitor' && req.method === 'GET') {
- json(res, 200, getResponseMonitor());
- return;
- }
- if (pathname === '/api/agent/groups' && req.method === 'GET') {
- json(res, 200, getGroupAgents());
- return;
- }
- const groupAgentModeRoute = pathname.match(/^\/api\/agent\/groups\/([^/]+)\/mode$/);
- if (groupAgentModeRoute && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, changeGroupMode(decodeURIComponent(groupAgentModeRoute[1]), body.mode, body.confirmation));
- return;
- }
- const groupAgentGenerateRoute = pathname.match(/^\/api\/agent\/groups\/([^/]+)\/generate$/);
- if (groupAgentGenerateRoute && req.method === 'POST') {
- json(res, 200, await generateGroupReply(decodeURIComponent(groupAgentGenerateRoute[1]), await readBody(req)));
- return;
- }
- const groupAgentDraftRoute = pathname.match(/^\/api\/agent\/groups\/([^/]+)\/drafts\/([^/]+)\/(approve|reject|regenerate)$/);
- if (groupAgentDraftRoute && req.method === 'POST') {
- const [, roomId, draftId, action] = groupAgentDraftRoute;
- const body = await readBody(req);
- if (action === 'approve') json(res, 200, await approveGroupDraft(decodeURIComponent(roomId), decodeURIComponent(draftId), body.content));
- else if (action === 'reject') json(res, 200, rejectGroupDraft(decodeURIComponent(roomId), decodeURIComponent(draftId), body.reason));
- else json(res, 200, await regenerateGroupDraft(decodeURIComponent(roomId), decodeURIComponent(draftId)));
- return;
- }
- if (pathname === '/api/agent/conversations/sync' && req.method === 'POST') {
- json(res, 200, await syncConversations());
- return;
- }
- if (pathname === '/api/agent/audit' && req.method === 'GET') {
- json(res, 200, getAudit(url.searchParams.get('limit')));
- return;
- }
- if (pathname === '/api/agent/mode' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, changeGlobalMode(body.mode));
- return;
- }
- if (pathname === '/api/agent/listener/start' && req.method === 'POST') {
- json(res, 200, await startListener());
- return;
- }
- if (pathname === '/api/agent/listener/stop' && req.method === 'POST') {
- json(res, 200, stopListener());
- return;
- }
- const customerTaskSyncRoute = pathname.match(/^\/api\/agent\/tasks\/([^/]+)\/sync-official$/);
- if (customerTaskSyncRoute && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await syncCustomerTaskToOfficialTodo(customerTaskSyncRoute[1], body));
- return;
- }
- const customerTaskRoute = pathname.match(/^\/api\/agent\/tasks\/([^/]+)$/);
- if (customerTaskRoute && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await updateCustomerTask(customerTaskRoute[1], body));
- return;
- }
- const customerAlertRoute = pathname.match(/^\/api\/agent\/alerts\/([^/]+)$/);
- if (customerAlertRoute && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, updateCustomerAlert(customerAlertRoute[1], body));
- return;
- }
- const agentConversationRoute = pathname.match(/^\/api\/agent\/conversations\/([^/]+)\/(takeover|resume|pause|auto|approve-reply|generate|manual-send)$/);
- if (agentConversationRoute && req.method === 'POST') {
- const [, conversationId, action] = agentConversationRoute;
- const body = await readBody(req);
- if (action === 'takeover') json(res, 200, changeConversationMode(conversationId, 'human'));
- else if (action === 'resume') json(res, 200, changeConversationMode(conversationId, 'review'));
- else if (action === 'pause') json(res, 200, changeConversationMode(conversationId, 'paused'));
- else if (action === 'auto') json(res, 200, changeConversationMode(conversationId, 'auto'));
- else if (action === 'generate') json(res, 200, await generateLatestDraft(conversationId));
- else if (action === 'manual-send') json(res, 200, await manualSend(conversationId, body.content));
- else json(res, 200, await approveReply(conversationId, body.content));
- return;
- }
- const voiceConversationRoute = pathname.match(/^\/api\/agent\/conversations\/([^/]+)\/voice-send$/);
- if (voiceConversationRoute && req.method === 'POST') {
- const [, conversationId] = voiceConversationRoute;
- const body = await readBody(req);
- json(res, 200, await sendClonedVoice(conversationId, body));
- return;
- }
- const voiceAudioRoute = pathname.match(/^\/api\/agent\/messages\/([^/]+)\/voice-audio$/);
- if (voiceAudioRoute && req.method === 'GET') {
- const audio = getSentVoiceAudio(decodeURIComponent(voiceAudioRoute[1]));
- serveSentVoiceAudio(req, res, audio.filePath);
- return;
- }
- const agentDraftRoute = pathname.match(/^\/api\/agent\/drafts\/([^/]+)\/(approve|reject|regenerate)$/);
- if (agentDraftRoute && req.method === 'POST') {
- const [, draftId, action] = agentDraftRoute;
- const body = await readBody(req);
- if (action === 'approve') json(res, 200, await approveDraft(draftId, body.content));
- else if (action === 'reject') json(res, 200, rejectDraft(draftId, body.reason));
- else json(res, 200, await regenerateDraft(draftId));
- return;
- }
- if (pathname === '/api/dashboard/summary' && req.method === 'GET') {
- json(res, 200, { status: 'ok', data: getStateSection('summary') });
- return;
- }
- if (pathname === '/api/business-diagnosis' && req.method === 'POST') {
- json(res, 200, await qiweiBusinessDiagnosis(await readBody(req)));
- return;
- }
- if (pathname === '/api/dashboard/customers/search' && req.method === 'GET') {
- const keyword = String(url.searchParams.get('keyword') || '').trim().toLowerCase();
- const hasPortrait = url.searchParams.has('hasPortrait') ? url.searchParams.get('hasPortrait') === 'true' : undefined;
- const scope = String(url.searchParams.get('scope') || '').trim();
- const forceRefresh = url.searchParams.get('refresh') === '1';
- let customers = (await getDashboardCustomerDirectory({ force: forceRefresh })).filter(customer => {
- if (scope === 'portrait' && customer.selected !== true && customer.discoveredFromGroups !== true && customer.groupStatus !== 'IN_GROUP') return false;
- if (keyword) {
- const text = `${customer.name || ''} ${customer.phone || ''} ${customer.externalUserId || ''}`.toLowerCase();
- if (!text.includes(keyword)) return false;
- }
- if (hasPortrait !== undefined && Boolean(customer.hasPortrait) !== hasPortrait) return false;
- return true;
- });
- const sortBy = url.searchParams.get('sortBy') || 'default';
- if (sortBy === 'lastActive') {
- customers.sort((a, b) => String(b.lastSeenInGroupAt || b.updatedAt || '').localeCompare(String(a.lastSeenInGroupAt || a.updatedAt || '')));
- } else if (sortBy === 'portraitDesc') {
- customers.sort((a, b) => Number(Boolean(b.hasPortrait)) - Number(Boolean(a.hasPortrait)));
- } else if (sortBy === 'portraitAsc') {
- customers.sort((a, b) => Number(Boolean(a.hasPortrait)) - Number(Boolean(b.hasPortrait)));
- } else if (sortBy === 'nameAsc') {
- customers.sort((a, b) => String(a.name || '').localeCompare(String(b.name || ''), 'zh-CN'));
- }
- const total = customers.length;
- const limit = Math.min(100, Math.max(1, Number(url.searchParams.get('limit')) || 20));
- const offset = Math.max(0, Number(url.searchParams.get('offset')) || 0);
- customers = customers.slice(offset, offset + limit);
- json(res, 200, { status: 'ok', data: { customers, total, limit, offset } });
- return;
- }
- if (pathname === '/api/dashboard/customers' && req.method === 'GET') {
- const filter = {
- keyword: url.searchParams.get('keyword') || '',
- hasPortrait: url.searchParams.has('hasPortrait') ? url.searchParams.get('hasPortrait') === 'true' : undefined,
- friendRequestStatus: url.searchParams.get('friendRequestStatus') || '',
- tag: url.searchParams.get('tag') || ''
- };
- json(res, 200, { status: 'ok', data: getStateSection('customers', { filter }) });
- return;
- }
- if (pathname === '/api/dashboard/portraits' && req.method === 'GET') {
- json(res, 200, { status: 'ok', data: getStateSection('portraits') });
- return;
- }
- if (pathname === '/api/dashboard/tags' && req.method === 'GET') {
- json(res, 200, { status: 'ok', data: getStateSection('tags') });
- return;
- }
- if (pathname === '/api/dashboard/transfers' && req.method === 'GET') {
- json(res, 200, { status: 'ok', data: getStateSection('transfers', { limit: url.searchParams.get('limit') }) });
- return;
- }
- if (pathname === '/api/dashboard/groups' && req.method === 'GET') {
- json(res, 200, { status: 'ok', data: getStateSection('groups') });
- return;
- }
- if (pathname === '/api/dashboard/operations' && req.method === 'GET') {
- json(res, 200, { status: 'ok', data: getStateSection('operations', { limit: url.searchParams.get('limit') }) });
- return;
- }
- if (pathname === '/api/dashboard/rebuild' && req.method === 'POST') {
- const state = rebuildStateFromOutputs();
- json(res, 200, { status: 'ok', data: { message: '索引已重建', summary: getStateSection('summary'), updatedAt: state.updatedAt } });
- return;
- }
- if (pathname === '/api/login/start' && req.method === 'POST') {
- const body = await readBody(req);
- const result = await qiweiLoginStart({
- flowUi: false,
- openBrowser: false,
- ...body
- });
- json(res, 200, result);
- return;
- }
- if (pathname === '/api/login/check' && req.method === 'POST') {
- const body = await readBody(req);
- const result = await qiweiLoginCheck(body);
- json(res, 200, result);
- return;
- }
- if (pathname === '/api/login/verify' && req.method === 'POST') {
- const body = await readBody(req);
- const result = await qiweiLoginVerify(body);
- json(res, 200, result);
- return;
- }
- if (pathname === '/api/groups/sync' && req.method === 'POST') {
- const body = await readBody(req);
- const id = createJob(() => qiweiSyncExternalGroups(body));
- json(res, 200, { status: 'ok', data: { jobId: id } });
- return;
- }
- if (pathname === '/api/groups/list' && req.method === 'GET') {
- const params = {};
- if (url.searchParams.has('keyword')) params.keyword = url.searchParams.get('keyword');
- if (url.searchParams.has('status')) params.status = url.searchParams.get('status');
- if (url.searchParams.has('source')) params.source = url.searchParams.get('source');
- if (url.searchParams.has('includeRejected')) params.includeRejected = url.searchParams.get('includeRejected') === 'true';
- json(res, 200, await qiweiListExternalGroups(params));
- return;
- }
- if (pathname === '/api/groups/analyze' && req.method === 'POST') {
- const body = await readBody(req);
- const id = createJob(() => qiweiAnalyzeGroupMembers(body));
- json(res, 200, { status: 'ok', data: { jobId: id } });
- return;
- }
- if (pathname === '/api/groups/confirm' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiConfirmExternalGroup(body));
- return;
- }
- if (pathname === '/api/groups/add' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiAddExternalGroup(body));
- return;
- }
- if (pathname === '/api/groups/reject' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiRejectExternalGroup(body));
- return;
- }
- if (pathname === '/api/groups/keywords' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiConfigureGroupKeywords(body));
- return;
- }
- if (pathname === '/api/groups/messages/sync' && req.method === 'POST') {
- const body = await readBody(req);
- const id = createJob(() => qiweiSyncGroupMessages(body));
- json(res, 200, { status: 'ok', data: { jobId: id } });
- return;
- }
- // Group Operations
- if (pathname === '/api/group-ops/overview' && req.method === 'GET') {
- json(res, 200, await qiweiGroupOpsGetContext({
- accountKey: url.searchParams.get('accountKey') || undefined,
- date: url.searchParams.get('date') || undefined
- }));
- return;
- }
- if (pathname === '/api/group-ops/playbooks' && req.method === 'GET') {
- json(res, 200, await qiweiGroupOpsManagePlaybook({ accountKey: url.searchParams.get('accountKey') || undefined, action: 'list' }));
- return;
- }
- if (pathname === '/api/group-ops/playbooks' && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsManagePlaybook({ ...(await readBody(req)), action: 'create' }));
- return;
- }
- const groupOpsPreviewRoute = pathname.match(/^\/api\/group-ops\/playbooks\/([^/]+)\/(preview|versions)$/);
- if (groupOpsPreviewRoute && req.method === 'GET') {
- json(res, 200, await qiweiGroupOpsManagePlaybook({ accountKey: url.searchParams.get('accountKey') || undefined, playbookId: groupOpsPreviewRoute[1], version: url.searchParams.get('version') ? Number(url.searchParams.get('version')) : undefined, action: groupOpsPreviewRoute[2] }));
- return;
- }
- const groupOpsVersionRoute = pathname.match(/^\/api\/group-ops\/playbooks\/([^/]+)\/versions$/);
- if (groupOpsVersionRoute && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsManagePlaybook({ ...(await readBody(req)), playbookId: groupOpsVersionRoute[1], action: 'new_version' }));
- return;
- }
- const groupOpsPublishRoute = pathname.match(/^\/api\/group-ops\/playbooks\/([^/]+)\/(publish|rollback)$/);
- if (groupOpsPublishRoute && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsManagePlaybook({ ...(await readBody(req)), playbookId: groupOpsPublishRoute[1], action: groupOpsPublishRoute[2] }));
- return;
- }
- const groupOpsPlanRoute = pathname.match(/^\/api\/group-ops\/groups\/([^/]+)\/generate-plan$/);
- if (groupOpsPlanRoute && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsGeneratePlan({ ...(await readBody(req)), roomId: decodeURIComponent(groupOpsPlanRoute[1]) }));
- return;
- }
- if (pathname === '/api/group-ops/plans/batch' && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsBatchGeneratePlans(await readBody(req)));
- return;
- }
- const groupOpsReviewRoute = pathname.match(/^\/api\/group-ops\/groups\/([^/]+)\/review$/);
- if (groupOpsReviewRoute && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsReviewMessages({ ...(await readBody(req)), roomId: decodeURIComponent(groupOpsReviewRoute[1]) }));
- return;
- }
- const groupOpsItemRoute = pathname.match(/^\/api\/group-ops\/plan-items\/([^/]+)\/(approve|reject|skip|mark-sent)$/);
- if (groupOpsItemRoute && req.method === 'POST') {
- const action = groupOpsItemRoute[2] === 'mark-sent' ? 'mark_sent' : groupOpsItemRoute[2];
- json(res, 200, await qiweiGroupOpsExecuteItem({ ...(await readBody(req)), itemId: groupOpsItemRoute[1], action }));
- return;
- }
- const groupOpsFindingRoute = pathname.match(/^\/api\/group-ops\/findings\/([^/]+)$/);
- if (groupOpsFindingRoute && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsUpdateFinding({ ...(await readBody(req)), findingId: groupOpsFindingRoute[1] }));
- return;
- }
- if (pathname === '/api/group-ops/quality-settings' && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsInsights({ ...(await readBody(req)), action: 'update_settings' }));
- return;
- }
- if (pathname === '/api/group-ops/tasks' && req.method === 'GET') {
- json(res, 200, await qiweiGroupOpsManageTask({ accountKey: url.searchParams.get('accountKey') || undefined, status: url.searchParams.get('status') || undefined, action: 'list' }));
- return;
- }
- if (pathname === '/api/group-ops/tasks' && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsManageTask({ ...(await readBody(req)), action: 'create' }));
- return;
- }
- const groupOpsTaskRoute = pathname.match(/^\/api\/group-ops\/tasks\/([^/]+)\/(update|sync-official)$/);
- if (groupOpsTaskRoute && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsManageTask({ ...(await readBody(req)), taskId: groupOpsTaskRoute[1], action: groupOpsTaskRoute[2] === 'sync-official' ? 'sync_official' : 'update' }));
- return;
- }
- if (pathname === '/api/group-ops/automation' && req.method === 'POST') {
- json(res, 200, await qiweiGroupOpsAutomation(await readBody(req)));
- return;
- }
- // Customer Operations
- if (pathname === '/api/customers' && req.method === 'GET') {
- json(res, 200, getCustomerMasterHub());
- return;
- }
- const customerMasterRoute = pathname.match(/^\/api\/customers\/([^/]+)\/profile$/);
- if (customerMasterRoute && req.method === 'POST') {
- json(res, 200, updateCustomerMaster(decodeURIComponent(customerMasterRoute[1]), await readBody(req)));
- return;
- }
- const customerRecommendationRoute = pathname.match(/^\/api\/customers\/([^/]+)\/recommendations\/([^/]+)$/);
- if (customerRecommendationRoute && req.method === 'POST') {
- json(res, 200, updateCustomerRecommendationFeedback(decodeURIComponent(customerRecommendationRoute[1]), decodeURIComponent(customerRecommendationRoute[2]), await readBody(req)));
- return;
- }
- if (pathname === '/api/customer-ops/batch-add-friends' && req.method === 'POST') {
- const body = await readBody(req);
- const id = createJob(() => qiweiBatchAddFriends(body));
- json(res, 200, { status: 'ok', data: { jobId: id } });
- return;
- }
- if (pathname === '/api/customer-ops/check-friend-status' && req.method === 'POST') {
- const body = await readBody(req);
- const id = createJob(() => qiweiCheckFriendStatus(body));
- json(res, 200, { status: 'ok', data: { jobId: id } });
- return;
- }
- if (pathname === '/api/customer-ops/customer-profile' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiGetCustomerProfile(body));
- return;
- }
- if (pathname === '/api/customer-ops/update' && req.method === 'POST') {
- json(res, 200, await qiweiUpdateCustomerInfo(await readBody(req)));
- return;
- }
- if (pathname === '/api/customer-ops/auto-create-group' && req.method === 'POST') {
- const body = await readBody(req);
- const id = createJob(() => qiweiAutoCreateGroup(body));
- json(res, 200, { status: 'ok', data: { jobId: id } });
- return;
- }
- // Portraits & Tags
- if (pathname === '/api/portraits/update' && req.method === 'POST') {
- const body = await readBody(req);
- const id = createJob(() => qiweiUpdateCustomerPortrait(body));
- json(res, 200, { status: 'ok', data: { jobId: id } });
- return;
- }
- if (pathname === '/api/portraits/save' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiSaveCustomerPortrait(body));
- return;
- }
- if (pathname === '/api/portraits/batch' && req.method === 'POST') {
- const body = await readBody(req);
- const id = createJob(() => qiweiBatchUpdateCustomerPortrait(body));
- json(res, 200, { status: 'ok', data: { jobId: id } });
- return;
- }
- if (pathname === '/api/portraits/export' && req.method === 'POST') {
- const body = await readBody(req);
- const id = createJob(() => qiweiExportCustomerPortraits(body));
- json(res, 200, { status: 'ok', data: { jobId: id } });
- return;
- }
- if (pathname === '/api/tags/list' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiListCustomerTags(body));
- return;
- }
- if (pathname === '/api/tags/all' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiListAllTags(body));
- return;
- }
- if (pathname === '/api/tags/add' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiAddCustomerTags(body));
- return;
- }
- if (pathname === '/api/tags/remove' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiRemoveCustomerTags(body));
- return;
- }
- if (pathname === '/api/personal-labels/sync' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiSyncPersonalLabels(body));
- return;
- }
- if (pathname === '/api/personal-labels/create' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiCreatePersonalLabel(body));
- return;
- }
- if (pathname === '/api/personal-labels/update' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiUpdatePersonalLabel(body));
- return;
- }
- if (pathname === '/api/personal-labels/delete' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiDeletePersonalLabel(body));
- return;
- }
- if (pathname === '/api/personal-labels/apply' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiApplyPersonalLabels(body));
- return;
- }
- // Customer Transfers
- if (pathname === '/api/transfers/preview' && req.method === 'POST') {
- const body = await readBody(req);
- json(res, 200, await qiweiPreviewTransferPackage(body));
- return;
- }
- if (pathname === '/api/transfers/execute' && req.method === 'POST') {
- const body = await readBody(req);
- const id = createJob(() => qiweiExecuteTransfer(body));
- json(res, 200, { status: 'ok', data: { jobId: id } });
- return;
- }
- if (pathname === '/api/upload' && req.method === 'POST') {
- const result = await handleUpload(req);
- json(res, 200, { status: 'ok', data: result });
- return;
- }
- if (pathname === '/api/outputs' && req.method === 'GET') {
- handleOutputsDownload(req, res, url.searchParams);
- return;
- }
- if (pathname.startsWith('/api/jobs/') && req.method === 'GET') {
- const id = pathname.slice('/api/jobs/'.length);
- const job = getJob(id);
- if (!job) {
- json(res, 404, { status: 'error', message: '任务不存在' });
- return;
- }
- json(res, 200, { status: 'ok', data: job });
- return;
- }
- json(res, 404, { status: 'error', message: 'not found' });
- } catch (error) {
- json(res, 500, { status: 'error', message: error.message || String(error) });
- }
- }
- function startServer(port = DASHBOARD_PORT) {
- const server = http.createServer(handleRequest);
- return new Promise((resolve, reject) => {
- server.once('error', reject);
- server.listen(port, '127.0.0.1', () => {
- console.log(`Qiwei Dashboard 已启动:http://127.0.0.1:${port}/`);
- resolve({ server, url: `http://127.0.0.1:${port}/`, port });
- });
- });
- }
- if (require.main === module) {
- startServer().catch(error => {
- console.error('Dashboard 启动失败:', error);
- process.exit(1);
- });
- }
- module.exports = { startServer };
|