feat(app): 移除内置agents并添加CORS支持和技能上传优化

移除了agents/registry.json中的所有内置agents配置,将agents数组清空。
为web应用添加了CORS中间件支持,允许指定的前端地址跨域访问。
重构了技能上传功能,增加了LLM重写机制,自动规范化上传的技能格式。
新增了工具名称提取逻辑,从技能正文中自动识别Required Tools段落。
更新了技能学习候选者和草稿的载荷结构,添加评估报告统计信息。
修改了意图路由技能的说明,改进任务状态管理逻辑。
This commit is contained in:
2026-06-12 13:25:20 +08:00
parent fc9fd93c36
commit 8aeb97a5fc
76 changed files with 3382 additions and 553 deletions

View File

@ -58,6 +58,7 @@ const WS_URL = process.env.NEXT_PUBLIC_WS_URL?.trim();
const DEFAULT_API_URL = 'http://127.0.0.1:18080';
const ACCESS_TOKEN_KEY = 'beaver_access_token';
const REFRESH_TOKEN_KEY = 'beaver_refresh_token';
export const AUTH_CLEARED_EVENT = 'beaver-auth-cleared';
const REQUEST_TIMEOUT_MS = 8000;
const OUTLOOK_REQUEST_TIMEOUT_MS = 45000;
const SKILL_LEARNING_REQUEST_TIMEOUT_MS = 120000;
@ -117,6 +118,34 @@ type FetchJsonOptions = RequestInit & {
timeoutMs?: number;
};
export class ApiError extends Error {
status: number;
detail: string;
constructor(message: string, options: { status: number; detail: string }) {
super(message);
this.name = 'ApiError';
this.status = options.status;
this.detail = options.detail;
}
}
export function isApiError(error: unknown, status?: number): error is ApiError {
return error instanceof ApiError && (status === undefined || error.status === status);
}
function parseErrorDetail(text: string): string {
try {
const parsed = JSON.parse(text);
if (parsed && typeof parsed.detail === 'string') {
return parsed.detail;
}
} catch {
// keep raw text
}
return text;
}
function withTimeout(
signal?: AbortSignal,
timeoutMs: number = REQUEST_TIMEOUT_MS
@ -163,6 +192,7 @@ export function clearTokens(): void {
if (!isBrowser()) return;
localStorage.removeItem(ACCESS_TOKEN_KEY);
localStorage.removeItem(REFRESH_TOKEN_KEY);
window.dispatchEvent(new CustomEvent(AUTH_CLEARED_EVENT));
}
export function isLoggedIn(): boolean {
@ -215,16 +245,11 @@ async function fetchJSON<T>(path: string, options?: FetchJsonOptions): Promise<T
if (res.status === 401) {
clearTokens();
}
let detail = text;
try {
const parsed = JSON.parse(text);
if (parsed && typeof parsed.detail === 'string') {
detail = parsed.detail;
}
} catch {
// keep raw text
}
throw new Error(`${pickAppText(locale, '接口错误', 'API error')} ${res.status}: ${detail}`);
const detail = parseErrorDetail(text);
throw new ApiError(`${pickAppText(locale, '接口错误', 'API error')} ${res.status}: ${detail}`, {
status: res.status,
detail,
});
}
return res.json();
}
@ -1216,7 +1241,7 @@ export async function uploadSkill(file: File): Promise<Skill> {
if (!res.ok) {
const text = await res.text();
throw new Error(`接口错误 ${res.status}: ${text}`);
throw new Error(`接口错误 ${res.status}: ${parseErrorDetail(text)}`);
}
return res.json();
}