mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-16 18:56:02 +00:00
737 lines
18 KiB
TypeScript
737 lines
18 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 PipelineMock {
|
|
uuid: string;
|
|
name: string;
|
|
description: string;
|
|
config: JsonRecord;
|
|
emoji: string;
|
|
is_default: boolean;
|
|
updated_at: string;
|
|
}
|
|
|
|
interface KnowledgeBaseMock {
|
|
uuid: string;
|
|
name: string;
|
|
description: string;
|
|
emoji: string;
|
|
knowledge_engine_plugin_id: string;
|
|
creation_settings: JsonRecord;
|
|
retrieval_settings: JsonRecord;
|
|
knowledge_engine: {
|
|
plugin_id: string;
|
|
name: {
|
|
en_US: string;
|
|
zh_Hans: string;
|
|
};
|
|
capabilities: string[];
|
|
};
|
|
updated_at: string;
|
|
}
|
|
|
|
interface MCPServerMock {
|
|
name: string;
|
|
mode: 'sse' | 'stdio' | 'http';
|
|
enable: boolean;
|
|
extra_args: JsonRecord;
|
|
runtime_info: {
|
|
status: 'connected';
|
|
tool_count: number;
|
|
tools: unknown[];
|
|
};
|
|
readme: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
interface LangBotApiMockState {
|
|
counters: Record<string, number>;
|
|
knowledgeBases: KnowledgeBaseMock[];
|
|
mcpServers: MCPServerMock[];
|
|
pipelines: PipelineMock[];
|
|
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 parseJsonBody(route: Route): JsonRecord {
|
|
return JSON.parse(route.request().postData() || '{}') as JsonRecord;
|
|
}
|
|
|
|
function now() {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
function nextId(state: LangBotApiMockState, prefix: string) {
|
|
state.counters[prefix] = (state.counters[prefix] || 0) + 1;
|
|
return `${prefix}-${state.counters[prefix]}`;
|
|
}
|
|
|
|
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(),
|
|
};
|
|
}
|
|
|
|
function makePipeline(
|
|
state: LangBotApiMockState,
|
|
data: JsonRecord,
|
|
uuid = nextId(state, 'pipeline'),
|
|
): PipelineMock {
|
|
return {
|
|
uuid,
|
|
name: String(data.name || ''),
|
|
description: String(data.description || ''),
|
|
config: (data.config as JsonRecord | undefined) || {
|
|
ai: {},
|
|
trigger: {},
|
|
safety: {},
|
|
output: {},
|
|
},
|
|
emoji: String(data.emoji || '⚙️'),
|
|
is_default: false,
|
|
updated_at: now(),
|
|
};
|
|
}
|
|
|
|
function knowledgeEngine() {
|
|
return {
|
|
plugin_id: 'builtin/minimal-knowledge',
|
|
name: {
|
|
en_US: 'Minimal Knowledge Engine',
|
|
zh_Hans: '最小知识库引擎',
|
|
},
|
|
description: {
|
|
en_US: 'Minimal mocked engine for frontend smoke tests.',
|
|
zh_Hans: '用于前端冒烟测试的最小模拟引擎。',
|
|
},
|
|
capabilities: ['text_retrieval'],
|
|
creation_schema: [],
|
|
retrieval_schema: [],
|
|
};
|
|
}
|
|
|
|
function makeKnowledgeBase(
|
|
state: LangBotApiMockState,
|
|
data: JsonRecord,
|
|
uuid = nextId(state, 'knowledge'),
|
|
): KnowledgeBaseMock {
|
|
const engine = knowledgeEngine();
|
|
return {
|
|
uuid,
|
|
name: String(data.name || ''),
|
|
description: String(data.description || ''),
|
|
emoji: String(data.emoji || '📚'),
|
|
knowledge_engine_plugin_id: String(
|
|
data.knowledge_engine_plugin_id || engine.plugin_id,
|
|
),
|
|
creation_settings: (data.creation_settings as JsonRecord | undefined) || {},
|
|
retrieval_settings:
|
|
(data.retrieval_settings as JsonRecord | undefined) || {},
|
|
knowledge_engine: {
|
|
plugin_id: engine.plugin_id,
|
|
name: engine.name,
|
|
capabilities: engine.capabilities,
|
|
},
|
|
updated_at: now(),
|
|
};
|
|
}
|
|
|
|
function makeMCPServer(data: JsonRecord): MCPServerMock {
|
|
return {
|
|
name: String(data.name || ''),
|
|
mode: (data.mode as MCPServerMock['mode']) || 'sse',
|
|
enable: data.enable !== false,
|
|
extra_args: (data.extra_args as JsonRecord | undefined) || {},
|
|
runtime_info: {
|
|
status: 'connected',
|
|
tool_count: 0,
|
|
tools: [],
|
|
},
|
|
readme: '',
|
|
updated_at: now(),
|
|
};
|
|
}
|
|
|
|
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/_/metadata') {
|
|
return fulfillJson(route, { configs: [] });
|
|
}
|
|
|
|
if (path === '/api/v1/pipelines') {
|
|
if (method === 'POST') {
|
|
const pipeline = makePipeline(state, parseJsonBody(route));
|
|
state.pipelines = [
|
|
...state.pipelines.filter((item) => item.uuid !== pipeline.uuid),
|
|
pipeline,
|
|
];
|
|
return fulfillJson(route, { uuid: pipeline.uuid });
|
|
}
|
|
|
|
return fulfillJson(route, { pipelines: state.pipelines });
|
|
}
|
|
|
|
const pipelineMatch = path.match(/^\/api\/v1\/pipelines\/([^/]+)$/);
|
|
if (pipelineMatch) {
|
|
const pipelineId = decodeURIComponent(pipelineMatch[1]);
|
|
|
|
if (method === 'PUT') {
|
|
const pipeline = makePipeline(state, parseJsonBody(route), pipelineId);
|
|
state.pipelines = [
|
|
...state.pipelines.filter((item) => item.uuid !== pipelineId),
|
|
pipeline,
|
|
];
|
|
return fulfillJson(route, {});
|
|
}
|
|
|
|
if (method === 'DELETE') {
|
|
state.pipelines = state.pipelines.filter(
|
|
(item) => item.uuid !== pipelineId,
|
|
);
|
|
return fulfillJson(route, {});
|
|
}
|
|
|
|
const pipeline = state.pipelines.find((item) => item.uuid === pipelineId);
|
|
return fulfillJson(route, {
|
|
pipeline:
|
|
pipeline || makePipeline(state, { name: pipelineId }, pipelineId),
|
|
});
|
|
}
|
|
|
|
const pipelineExtensionsMatch = path.match(
|
|
/^\/api\/v1\/pipelines\/([^/]+)\/extensions$/,
|
|
);
|
|
if (pipelineExtensionsMatch) {
|
|
return fulfillJson(route, {
|
|
enable_all_plugins: true,
|
|
enable_all_mcp_servers: true,
|
|
enable_all_skills: true,
|
|
bound_plugins: [],
|
|
available_plugins: [],
|
|
bound_mcp_servers: [],
|
|
available_mcp_servers: state.mcpServers,
|
|
bound_skills: [],
|
|
available_skills: state.skills,
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/knowledge/bases') {
|
|
if (method === 'POST') {
|
|
const base = makeKnowledgeBase(state, parseJsonBody(route));
|
|
state.knowledgeBases = [
|
|
...state.knowledgeBases.filter((item) => item.uuid !== base.uuid),
|
|
base,
|
|
];
|
|
return fulfillJson(route, { uuid: base.uuid });
|
|
}
|
|
|
|
return fulfillJson(route, { bases: state.knowledgeBases });
|
|
}
|
|
|
|
const knowledgeBaseFilesMatch = path.match(
|
|
/^\/api\/v1\/knowledge\/bases\/([^/]+)\/files$/,
|
|
);
|
|
if (knowledgeBaseFilesMatch) {
|
|
return fulfillJson(route, { files: [] });
|
|
}
|
|
|
|
const knowledgeBaseMatch = path.match(
|
|
/^\/api\/v1\/knowledge\/bases\/([^/]+)$/,
|
|
);
|
|
if (knowledgeBaseMatch) {
|
|
const baseId = decodeURIComponent(knowledgeBaseMatch[1]);
|
|
|
|
if (method === 'PUT') {
|
|
const base = makeKnowledgeBase(state, parseJsonBody(route), baseId);
|
|
state.knowledgeBases = [
|
|
...state.knowledgeBases.filter((item) => item.uuid !== baseId),
|
|
base,
|
|
];
|
|
return fulfillJson(route, { uuid: base.uuid });
|
|
}
|
|
|
|
if (method === 'DELETE') {
|
|
state.knowledgeBases = state.knowledgeBases.filter(
|
|
(item) => item.uuid !== baseId,
|
|
);
|
|
return fulfillJson(route, {});
|
|
}
|
|
|
|
const base = state.knowledgeBases.find((item) => item.uuid === baseId);
|
|
return fulfillJson(route, {
|
|
base: base || makeKnowledgeBase(state, { name: baseId }, baseId),
|
|
});
|
|
}
|
|
|
|
if (path === '/api/v1/knowledge/engines') {
|
|
return fulfillJson(route, { engines: [knowledgeEngine()] });
|
|
}
|
|
|
|
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') {
|
|
if (method === 'POST') {
|
|
const server = makeMCPServer(parseJsonBody(route));
|
|
state.mcpServers = [
|
|
...state.mcpServers.filter((item) => item.name !== server.name),
|
|
server,
|
|
];
|
|
return fulfillJson(route, { task_id: nextId(state, 'task') });
|
|
}
|
|
|
|
return fulfillJson(route, { servers: state.mcpServers });
|
|
}
|
|
|
|
const mcpTestMatch = path.match(/^\/api\/v1\/mcp\/servers\/([^/]+)\/test$/);
|
|
if (mcpTestMatch) {
|
|
return fulfillJson(route, {
|
|
runtime_info: {
|
|
status: 'connected',
|
|
tool_count: 0,
|
|
tools: [],
|
|
},
|
|
});
|
|
}
|
|
|
|
const mcpServerMatch = path.match(/^\/api\/v1\/mcp\/servers\/([^/]+)$/);
|
|
if (mcpServerMatch) {
|
|
const serverName = decodeURIComponent(mcpServerMatch[1]);
|
|
|
|
if (method === 'PUT') {
|
|
const existing = state.mcpServers.find(
|
|
(item) => item.name === serverName,
|
|
);
|
|
const server = makeMCPServer({
|
|
...(existing || {}),
|
|
...parseJsonBody(route),
|
|
name: serverName,
|
|
});
|
|
state.mcpServers = [
|
|
...state.mcpServers.filter((item) => item.name !== serverName),
|
|
server,
|
|
];
|
|
return fulfillJson(route, { task_id: nextId(state, 'task') });
|
|
}
|
|
|
|
if (method === 'DELETE') {
|
|
state.mcpServers = state.mcpServers.filter(
|
|
(item) => item.name !== serverName,
|
|
);
|
|
return fulfillJson(route, { task_id: nextId(state, 'task') });
|
|
}
|
|
|
|
const server = state.mcpServers.find((item) => item.name === serverName);
|
|
return fulfillJson(route, {
|
|
server: server || makeMCPServer({ name: serverName }),
|
|
});
|
|
}
|
|
|
|
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]);
|
|
if (method === 'PUT') {
|
|
const skill = makeSkill({
|
|
...parseJsonBody(route),
|
|
name: skillName,
|
|
});
|
|
state.skills = [
|
|
...state.skills.filter((item) => item.name !== skillName),
|
|
skill,
|
|
];
|
|
return fulfillJson(route, { skill });
|
|
}
|
|
|
|
if (method === 'DELETE') {
|
|
state.skills = state.skills.filter((item) => item.name !== skillName);
|
|
return fulfillJson(route, {});
|
|
}
|
|
|
|
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 = {
|
|
counters: {},
|
|
knowledgeBases: [],
|
|
mcpServers: [],
|
|
pipelines: [],
|
|
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);
|
|
}
|