Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
432 changes: 265 additions & 167 deletions backend/package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "papyrus-backend",
"version": "2.0.0-beta.16",
"version": "2.0.0",
"description": "Papyrus Desktop TypeScript backend",
"type": "module",
"main": "dist/api/server.js",
Expand All @@ -23,7 +23,7 @@
"gray-matter": "^4.0.3",
"markdown-it": "^14.1.0",
"openai": "^4.96.0",
"sharp": "^0.34.5",
"sharp": "^0.35.3",
"tar": "^7.5.15",
"undici": "^8.2.0",
"uuid": "^14.0.0",
Expand Down
9 changes: 6 additions & 3 deletions backend/src/ai/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1457,7 +1457,10 @@ Output only the translation, no explanations.`;
}

delete headers['content-length'];
return fetchWithProxy(reqUrl, { ...reqInit, headers } as unknown as RequestInit) as unknown as ReturnType<Fetch>;
// 本地 Provider(Ollama/LM Studio 等)必须允许回环地址,其余 Provider 一律走禁私网的默认 Agent。
return fetchWithProxy(reqUrl, { ...reqInit, headers } as unknown as RequestInit, {
allowLoopback: isLocalProvider,
}) as unknown as ReturnType<Fetch>;
}) as Fetch,
});

Expand Down Expand Up @@ -1589,7 +1592,7 @@ Output only the translation, no explanations.`;
? new PapyrusTools().getToolsForOpenAI(allowedToolNames)
: undefined;

const response = await fetch(`${baseUrl}/api/chat`, {
const response = await fetchWithProxy(`${baseUrl}/api/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
signal: signal ?? AbortSignal.timeout(60000),
Expand All @@ -1603,7 +1606,7 @@ Output only the translation, no explanations.`;
},
...(ollamaTools && ollamaTools.length > 0 ? { tools: ollamaTools } : {}),
}),
});
}, { allowLoopback: true });

if (!response.ok) {
throw new Error(`Ollama API 错误: ${response.status} ${response.statusText}`);
Expand Down
7 changes: 4 additions & 3 deletions backend/src/api/routes/ai-completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export default async function aiCompletionRoutes(fastify: FastifyInstance): Prom
return;
}

const resp = await fetch(`${baseUrl}/api/chat`, {
const resp = await fetchWithProxy(`${baseUrl}/api/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
signal: AbortSignal.timeout(60000),
Expand All @@ -155,7 +155,7 @@ export default async function aiCompletionRoutes(fastify: FastifyInstance): Prom
stream: true,
options: { temperature: 0.7 },
}),
});
}, { allowLoopback: true });

if (!resp.ok || !resp.body) {
reply.raw.write(`data: {"error":"Ollama API 错误: ${resp.status}"}\n\n`);
Expand Down Expand Up @@ -231,12 +231,13 @@ export default async function aiCompletionRoutes(fastify: FastifyInstance): Prom
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;

// keyless 本地 Provider(LM Studio 等)的 base URL 是 localhost,需要回环放行;其余走默认禁私网 Agent。
const resp = await fetchWithProxy(endpoint, {
method: 'POST',
headers,
signal: AbortSignal.timeout(60000),
body: JSON.stringify(reqBody),
});
}, { allowLoopback: isKeylessProvider(providerName) });

if (!resp.ok || !resp.body) {
reply.raw.write(`data: {"error":"API 错误: ${resp.status}"}\n\n`);
Expand Down
9 changes: 5 additions & 4 deletions backend/src/api/routes/ai-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,11 @@ export default async function aiConfigRoutes(fastify: FastifyInstance): Promise<
try {
// 对于 keyless providers,尝试连接 base URL
// 不同的 provider 可能有不同的健康检查端点,这里我们做一个简单的 GET 请求
const resp = await fetch(baseUrl, {
method: 'GET',
signal: AbortSignal.timeout(5000)
});
// keyless Provider 只允许 localhost/127.0.0.1(validateProviderBaseUrl 已保证),故放行回环。
const resp = await fetchWithProxy(baseUrl, {
method: 'GET',
signal: AbortSignal.timeout(5000)
}, { allowLoopback: true });
if (resp.ok || resp.status === 404 || resp.status === 401) {
// 即使返回 404 或 401,也说明服务器是可访问的
reply.send({ success: true, message: `${providerName} 连接成功` });
Expand Down
21 changes: 16 additions & 5 deletions backend/src/api/routes/cli.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify';
import { defaultCliManager } from '#/cli/cli-manager.js';
import { routeErrorMessage } from '../../utils/route-error.js';

interface CliRunPayload {
args?: unknown;
Expand All @@ -22,7 +23,7 @@ export default async function cliRoutes(fastify: FastifyInstance): Promise<void>
} catch (err) {
const message = err instanceof Error ? err.message : 'CLI 状态检查失败';
request.log.error({ err }, message);
reply.status(500).send({ success: false, error: message });
reply.status(500).send({ success: false, error: routeErrorMessage(err, 'CLI 状态检查失败') });
}
});

Expand All @@ -32,7 +33,7 @@ export default async function cliRoutes(fastify: FastifyInstance): Promise<void>
} catch (err) {
const message = err instanceof Error ? err.message : 'CLI 安装失败';
request.log.error({ err }, message);
reply.status(500).send({ success: false, error: message });
reply.status(500).send({ success: false, error: routeErrorMessage(err, 'CLI 安装失败') });
}
});

Expand All @@ -42,18 +43,28 @@ export default async function cliRoutes(fastify: FastifyInstance): Promise<void>
} catch (err) {
const message = err instanceof Error ? err.message : 'CLI 更新失败';
request.log.error({ err }, message);
reply.status(500).send({ success: false, error: message });
reply.status(500).send({ success: false, error: routeErrorMessage(err, 'CLI 更新失败') });
}
});

fastify.post('/run', async (request, reply) => {
let args: string[];
try {
args = readCliArgs(request.body as CliRunPayload | undefined);
} catch (e) {
// 入参校验错误面向调用方(描述请求体问题),保留原文便于修正请求;
// 与内部执行错误区分开,后者才需要 debug 门控消毒。
const message = e instanceof Error ? e.message : 'CLI 运行失败';
request.log.warn({ err: e }, message);
reply.status(400).send({ success: false, error: message });
return;
}
try {
const args = readCliArgs(request.body as CliRunPayload | undefined);
reply.send(await defaultCliManager.run(args));
} catch (err) {
const message = err instanceof Error ? err.message : 'CLI 运行失败';
request.log.warn({ err }, message);
reply.status(400).send({ success: false, error: message });
reply.status(400).send({ success: false, error: routeErrorMessage(err, 'CLI 运行失败') });
}
});
}
13 changes: 8 additions & 5 deletions backend/src/api/routes/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { v4 as uuidv4 } from 'uuid';
import { paths } from '../../utils/paths.js';
import { routeErrorMessage } from '../../utils/route-error.js';
import {
loadAllCards, insertCard, checkpointDb, runInTransaction,
loadAllNotes, insertNote,
Expand All @@ -26,19 +27,21 @@ export default async function dataRoutes(fastify: FastifyInstance): Promise<void
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
reply.status(500).send({ success: false, error: message });
reply.status(500).send({ success: false, error: routeErrorMessage(err, '创建备份失败') });
}
});

fastify.post('/data/reset', async (_request, reply) => {
fastify.post('/data/reset', async (request, reply) => {
try {
clearAllData();
checkpointDb();
reply.send({ success: true });
} catch (err) {
// 重置是破坏性操作,失败原因完整记录在服务端日志;响应不回显内部细节。
request.log.error({ err }, '数据重置失败');
reply.status(500).send({
success: false,
error: err instanceof Error ? err.message : 'Failed to reset data',
error: routeErrorMessage(err, '数据重置失败'),
});
}
});
Expand All @@ -56,7 +59,7 @@ export default async function dataRoutes(fastify: FastifyInstance): Promise<void
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
reply.status(500).send({ success: false, error: message });
reply.status(500).send({ success: false, error: routeErrorMessage(err, '导出数据失败') });
}
});

Expand Down Expand Up @@ -110,7 +113,7 @@ export default async function dataRoutes(fastify: FastifyInstance): Promise<void
}
});
} catch (e) {
reply.status(400).send({ success: false, error: e instanceof Error ? e.message : String(e) });
reply.status(400).send({ success: false, error: routeErrorMessage(e, '导入失败,数据格式可能有误') });
return;
}

Expand Down
17 changes: 9 additions & 8 deletions backend/src/api/routes/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { parseExtensionManifestFromZip } from '#/core/extension-package.js';
import { addExtensionEventClient } from '#/core/extension-events.js';
import { routeErrorMessage } from '../../utils/route-error.js';
import {
loadAllExtensions,
getExtensionById,
Expand Down Expand Up @@ -111,7 +112,7 @@ export default async function extensionsRoutes(fastify: FastifyInstance): Promis
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
return reply.status(500).send({ success: false, error: message });
return reply.status(500).send({ success: false, error: routeErrorMessage(err, '扩展操作失败') });
}
});

Expand All @@ -129,7 +130,7 @@ export default async function extensionsRoutes(fastify: FastifyInstance): Promis
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
return reply.status(500).send({ success: false, error: message });
return reply.status(500).send({ success: false, error: routeErrorMessage(err, '扩展操作失败') });
}
});

Expand All @@ -156,7 +157,7 @@ export default async function extensionsRoutes(fastify: FastifyInstance): Promis
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
return reply.status(500).send({ success: false, error: message });
return reply.status(500).send({ success: false, error: routeErrorMessage(err, '扩展操作失败') });
}
});

Expand Down Expand Up @@ -198,7 +199,7 @@ export default async function extensionsRoutes(fastify: FastifyInstance): Promis
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
console.log('[extensions] local install failed:', message);
return reply.status(400).send({ success: false, error: message });
return reply.status(400).send({ success: false, error: routeErrorMessage(err, '本地扩展安装失败') });
}
});

Expand All @@ -220,7 +221,7 @@ export default async function extensionsRoutes(fastify: FastifyInstance): Promis
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
return reply.status(500).send({ success: false, error: message });
return reply.status(500).send({ success: false, error: routeErrorMessage(err, '扩展操作失败') });
}
});

Expand Down Expand Up @@ -248,7 +249,7 @@ export default async function extensionsRoutes(fastify: FastifyInstance): Promis
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
return reply.status(500).send({ success: false, error: message });
return reply.status(500).send({ success: false, error: routeErrorMessage(err, '扩展操作失败') });
}
});

Expand All @@ -267,7 +268,7 @@ export default async function extensionsRoutes(fastify: FastifyInstance): Promis
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
return reply.status(500).send({ success: false, error: message });
return reply.status(500).send({ success: false, error: routeErrorMessage(err, '扩展操作失败') });
}
});

Expand All @@ -292,7 +293,7 @@ export default async function extensionsRoutes(fastify: FastifyInstance): Promis
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
return reply.status(500).send({ success: false, error: message });
return reply.status(500).send({ success: false, error: routeErrorMessage(err, '扩展操作失败') });
}
});
}
9 changes: 5 additions & 4 deletions backend/src/api/routes/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { FastifyInstance } from 'fastify';
import fs from 'node:fs';
import sharp from 'sharp';
import { listFiles, createFolder, saveFile, deleteFileItem, getFileById, isSafeFileStoragePath } from '../../core/files.js';
import { routeErrorMessage } from '../../utils/route-error.js';

const MAX_PREVIEW_SIZE = 10 * 1024 * 1024;
const THUMBNAIL_SIZE = 128;
Expand Down Expand Up @@ -50,7 +51,7 @@ export default async function filesRoutes(fastify: FastifyInstance): Promise<voi
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
reply.status(500).send({ success: false, error: message });
reply.status(500).send({ success: false, error: routeErrorMessage(err, '创建文件夹失败') });
}
});

Expand Down Expand Up @@ -78,8 +79,8 @@ export default async function filesRoutes(fastify: FastifyInstance): Promise<voi
const record = saveFile(f.name, f.content, f.mimeType, body.parentId);
saved.push({ id: record.id, name: record.name, size: record.size });
} catch (saveErr) {
const msg = saveErr instanceof Error ? saveErr.message : '保存失败';
errors.push({ name: f.name, error: msg });
// 逐文件错误同样可能包含磁盘/数据库内部细节,统一走 debug 门控消毒。
errors.push({ name: f.name, error: routeErrorMessage(saveErr, '保存失败') });
}
}

Expand All @@ -92,7 +93,7 @@ export default async function filesRoutes(fastify: FastifyInstance): Promise<voi
} catch (err) {
const message = err instanceof Error ? err.message : '服务器内部错误';
request.log.error({ err }, message);
reply.status(500).send({ success: false, error: message });
reply.status(500).send({ success: false, error: routeErrorMessage(err, '上传文件失败') });
}
});

Expand Down
Loading
Loading