mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-17 03:04:20 +00:00
418 lines
10 KiB
TypeScript
418 lines
10 KiB
TypeScript
import { Page, Route } from '@playwright/test';
|
|
|
|
type JsonRecord = Record<string, unknown>;
|
|
|
|
interface SkillMock {
|
|
name: string;
|
|
display_name: string;
|
|
description: string;
|
|
instructions: string;
|
|
package_root: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
interface LangBotApiMockState {
|
|
skills: SkillMock[];
|
|
}
|
|
|
|
function ok(data: unknown) {
|
|
return {
|
|
code: 0,
|
|
message: 'ok',
|
|
data,
|
|
timestamp: Date.now(),
|
|
};
|
|
}
|
|
|
|
async function fulfillJson(route: Route, data: unknown) {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(ok(data)),
|
|
});
|
|
}
|
|
|
|
function routePath(route: Route) {
|
|
return new URL(route.request().url()).pathname;
|
|
}
|
|
|
|
function emptyMonitoringData() {
|
|
return {
|
|
overview: {
|
|
total_messages: 0,
|
|
llm_calls: 0,
|
|
embedding_calls: 0,
|
|
model_calls: 0,
|
|
success_rate: 0,
|
|
active_sessions: 0,
|
|
},
|
|
messages: [],
|
|
llmCalls: [],
|
|
embeddingCalls: [],
|
|
sessions: [],
|
|
errors: [],
|
|
totalCount: {
|
|
messages: 0,
|
|
llmCalls: 0,
|
|
embeddingCalls: 0,
|
|
sessions: 0,
|
|
errors: 0,
|
|
},
|
|
};
|
|
}
|
|
|
|
function emptyTokenStatistics() {
|
|
return {
|
|
summary: {
|
|
total_calls: 0,
|
|
success_calls: 0,
|
|
error_calls: 0,
|
|
total_input_tokens: 0,
|
|
total_output_tokens: 0,
|
|
total_tokens: 0,
|
|
total_cost: 0,
|
|
avg_tokens_per_call: 0,
|
|
avg_duration_ms: 0,
|
|
avg_tokens_per_second: 0,
|
|
zero_token_success_calls: 0,
|
|
},
|
|
by_model: [],
|
|
timeseries: [],
|
|
bucket: 'day',
|
|
};
|
|
}
|
|
|
|
function makeSkill(data: JsonRecord): SkillMock {
|
|
return {
|
|
name: String(data.name || ''),
|
|
display_name: String(data.display_name || ''),
|
|
description: String(data.description || ''),
|
|
instructions: String(data.instructions || ''),
|
|
package_root: String(data.package_root || ''),
|
|
updated_at: new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
async function handleBackendApi(route: Route, state: LangBotApiMockState) {
|
|
const request = route.request();
|
|
const url = new URL(request.url());
|
|
const path = url.pathname;
|
|
const method = request.method();
|
|
|
|
if (path === '/api/v1/system/info') {
|
|
return fulfillJson(route, {
|
|
debug: false,
|
|
version: 'frontend-smoke',
|
|
edition: 'community',
|
|
cloud_service_url: 'https://space.langbot.app',
|
|
enable_marketplace: true,
|
|
allow_modify_login_info: true,
|
|
disable_models_service: false,
|
|
limitation: {
|
|
max_bots: -1,
|
|
max_pipelines: -1,
|
|
max_extensions: -1,
|
|
},
|
|
outbound_ips: [],
|
|
wizard_status: 'completed',
|
|
wizard_progress: null,
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/user/account-info') {
|
|
return fulfillJson(route, {
|
|
initialized: true,
|
|
account_type: 'local',
|
|
has_password: true,
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/user/check-token') {
|
|
return fulfillJson(route, { token: '' });
|
|
}
|
|
|
|
if (path === '/api/v1/user/auth') {
|
|
return fulfillJson(route, { token: 'playwright-token' });
|
|
}
|
|
|
|
if (path === '/api/v1/user/info') {
|
|
return fulfillJson(route, {
|
|
user: 'admin@example.com',
|
|
account_type: 'local',
|
|
has_password: true,
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/user/space-credits') {
|
|
return fulfillJson(route, { credits: null });
|
|
}
|
|
|
|
if (path === '/api/v1/platform/bots') {
|
|
return fulfillJson(route, { bots: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/pipelines') {
|
|
return fulfillJson(route, { pipelines: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/knowledge/bases') {
|
|
return fulfillJson(route, { bases: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/knowledge/migration/status') {
|
|
return fulfillJson(route, {
|
|
needed: false,
|
|
internal_kb_count: 0,
|
|
external_kb_count: 0,
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/plugins') {
|
|
return fulfillJson(route, { plugins: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/extensions') {
|
|
return fulfillJson(route, { extensions: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/mcp/servers') {
|
|
return fulfillJson(route, { servers: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/skills') {
|
|
if (method === 'POST') {
|
|
const skill = makeSkill(
|
|
JSON.parse(request.postData() || '{}') as JsonRecord,
|
|
);
|
|
state.skills = [
|
|
...state.skills.filter((item) => item.name !== skill.name),
|
|
skill,
|
|
];
|
|
return fulfillJson(route, { skill });
|
|
}
|
|
|
|
return fulfillJson(route, { skills: state.skills });
|
|
}
|
|
|
|
const skillFileMatch = path.match(
|
|
/^\/api\/v1\/skills\/([^/]+)\/files\/(.+)$/,
|
|
);
|
|
if (skillFileMatch) {
|
|
const skillName = decodeURIComponent(skillFileMatch[1]);
|
|
const filePath = decodeURIComponent(skillFileMatch[2]);
|
|
const skill = state.skills.find((item) => item.name === skillName);
|
|
return fulfillJson(route, {
|
|
skill: { name: skillName },
|
|
path: filePath,
|
|
content: skill?.instructions || '',
|
|
});
|
|
}
|
|
|
|
const skillFilesMatch = path.match(/^\/api\/v1\/skills\/([^/]+)\/files$/);
|
|
if (skillFilesMatch) {
|
|
const skillName = decodeURIComponent(skillFilesMatch[1]);
|
|
return fulfillJson(route, {
|
|
skill: { name: skillName },
|
|
base_path: '.',
|
|
entries: [
|
|
{
|
|
path: 'SKILL.md',
|
|
name: 'SKILL.md',
|
|
is_dir: false,
|
|
size: null,
|
|
},
|
|
],
|
|
truncated: false,
|
|
});
|
|
}
|
|
|
|
const skillMatch = path.match(/^\/api\/v1\/skills\/([^/]+)$/);
|
|
if (skillMatch) {
|
|
const skillName = decodeURIComponent(skillMatch[1]);
|
|
const skill = state.skills.find((item) => item.name === skillName) || {
|
|
name: skillName,
|
|
display_name: '',
|
|
description: '',
|
|
instructions: '',
|
|
package_root: '',
|
|
updated_at: new Date().toISOString(),
|
|
};
|
|
return fulfillJson(route, { skill });
|
|
}
|
|
|
|
if (path === '/api/v1/system/status/plugin-system') {
|
|
return fulfillJson(route, {
|
|
is_enable: true,
|
|
is_connected: true,
|
|
plugin_connector_error: '',
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/plugins/debug-info') {
|
|
return fulfillJson(route, {
|
|
debug_url: 'ws://127.0.0.1:5300/plugin/debug',
|
|
plugin_debug_key: 'test-debug-key',
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/box/status') {
|
|
return fulfillJson(route, {
|
|
available: true,
|
|
enabled: true,
|
|
profile: 'playwright',
|
|
recent_error_count: 0,
|
|
active_sessions: 0,
|
|
managed_processes: 0,
|
|
session_ttl_sec: 3600,
|
|
backend: {
|
|
name: 'playwright',
|
|
available: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/box/sessions') {
|
|
return fulfillJson(route, []);
|
|
}
|
|
|
|
if (path === '/api/v1/monitoring/data') {
|
|
return fulfillJson(route, emptyMonitoringData());
|
|
}
|
|
|
|
if (path === '/api/v1/monitoring/overview') {
|
|
return fulfillJson(route, emptyMonitoringData().overview);
|
|
}
|
|
|
|
if (path === '/api/v1/monitoring/token-statistics') {
|
|
return fulfillJson(route, emptyTokenStatistics());
|
|
}
|
|
|
|
if (path === '/api/v1/monitoring/feedback/stats') {
|
|
return fulfillJson(route, {
|
|
total_feedback: 0,
|
|
total_likes: 0,
|
|
total_dislikes: 0,
|
|
satisfaction_rate: 0,
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/monitoring/feedback') {
|
|
return fulfillJson(route, { feedback: [], total: 0 });
|
|
}
|
|
|
|
if (path === '/api/v1/survey/pending') {
|
|
return fulfillJson(route, { survey: null });
|
|
}
|
|
|
|
if (path === '/api/v1/system/tasks') {
|
|
return fulfillJson(route, { tasks: [] });
|
|
}
|
|
|
|
if (
|
|
path === '/api/v1/marketplace/plugins' ||
|
|
path === '/api/v1/marketplace/plugins/search' ||
|
|
path === '/api/v1/marketplace/extensions/search' ||
|
|
path === '/api/v1/marketplace/mcps/search' ||
|
|
path === '/api/v1/marketplace/skills/search'
|
|
) {
|
|
return fulfillJson(route, { plugins: [], total: 0 });
|
|
}
|
|
|
|
if (path === '/api/v1/marketplace/tags') {
|
|
return fulfillJson(route, { tags: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/marketplace/recommendation-lists') {
|
|
return fulfillJson(route, { lists: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/dist/info/releases') {
|
|
return fulfillJson(route, []);
|
|
}
|
|
|
|
if (path === '/api/v1/dist/info/repo') {
|
|
return fulfillJson(route, {
|
|
repo: {
|
|
stargazers_count: 0,
|
|
forks_count: 0,
|
|
open_issues_count: 0,
|
|
},
|
|
contributors: [],
|
|
});
|
|
}
|
|
|
|
await fulfillJson(route, {});
|
|
}
|
|
|
|
async function handleCloudApi(route: Route) {
|
|
const path = routePath(route);
|
|
|
|
if (
|
|
path === '/api/v1/marketplace/plugins' ||
|
|
path === '/api/v1/marketplace/plugins/search' ||
|
|
path === '/api/v1/marketplace/extensions/search' ||
|
|
path === '/api/v1/marketplace/mcps/search' ||
|
|
path === '/api/v1/marketplace/skills/search'
|
|
) {
|
|
return fulfillJson(route, { plugins: [], total: 0 });
|
|
}
|
|
|
|
if (path === '/api/v1/marketplace/tags') {
|
|
return fulfillJson(route, { tags: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/marketplace/recommendation-lists') {
|
|
return fulfillJson(route, { lists: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/dist/info/releases') {
|
|
return fulfillJson(route, []);
|
|
}
|
|
|
|
if (path === '/api/v1/dist/info/repo') {
|
|
return fulfillJson(route, {
|
|
repo: {
|
|
stargazers_count: 0,
|
|
forks_count: 0,
|
|
open_issues_count: 0,
|
|
},
|
|
contributors: [],
|
|
});
|
|
}
|
|
|
|
await fulfillJson(route, {});
|
|
}
|
|
|
|
export async function installLangBotApiMocks(
|
|
page: Page,
|
|
options: { authenticated?: boolean; storage?: JsonRecord } = {},
|
|
) {
|
|
const { authenticated = false, storage = {} } = options;
|
|
const state: LangBotApiMockState = {
|
|
skills: [],
|
|
};
|
|
|
|
await page.addInitScript(
|
|
({ authenticated, storage }) => {
|
|
localStorage.setItem('langbot_language', 'en-US');
|
|
localStorage.setItem('extensions_group_by_type', 'false');
|
|
|
|
if (authenticated) {
|
|
localStorage.setItem('token', 'playwright-token');
|
|
localStorage.setItem('userEmail', 'admin@example.com');
|
|
} else {
|
|
localStorage.removeItem('token');
|
|
localStorage.removeItem('userEmail');
|
|
}
|
|
|
|
for (const [key, value] of Object.entries(storage)) {
|
|
localStorage.setItem(key, String(value));
|
|
}
|
|
},
|
|
{ authenticated, storage },
|
|
);
|
|
|
|
await page.route('**/api/v1/**', (route) => handleBackendApi(route, state));
|
|
await page.route('https://space.langbot.app/**', handleCloudApi);
|
|
}
|