feat(website-demo): SW mock 接口增强与演示默认深色主题

- 模型列表扩充至 5 个(K3/DeepSeek-V4-Pro/Flash/GLM-5.2/MiniMax-M3),补齐 success 字段
- 工作区「当前选中」改为会话态动态组装,修复切换工作区后 toast/高亮拿错工作区
- 演示默认主题从经典配色改为夜间配色
- 缓存版本 bump 至 v19
This commit is contained in:
JOJO 2026-09-03 21:54:37 +08:00
parent 7fa2715ee3
commit bd2cf0f56d
2 changed files with 206 additions and 30 deletions

View File

@ -56,9 +56,9 @@
<script>
/* 演示引导:注册 mock SW等 controllerchange 后换路由、再加载真实前端 */
(function () {
// 固定演示呈现状态:经典配色 + 极简模式 + 快捷窗口自动展开,不受访客浏览器缓存影响
// 固定演示呈现状态:夜间配色 + 极简模式 + 快捷窗口自动展开,不受访客浏览器缓存影响
try {
localStorage.setItem('agents_ui_theme', 'classic');
localStorage.setItem('agents_ui_theme', 'dark');
localStorage.setItem('agents_personalization_experiments', JSON.stringify({ blockDisplayMode: 'minimal' }));
localStorage.setItem('agents_quick_dock_auto_expand', 'true');
} catch (e) {}

View File

@ -8,16 +8,41 @@
*/
const DEMO_PAGE = '/demo.html';
const CACHE = 'astrion-demo-v4';
const CACHE = 'astrion-demo-v19';
/* ── 静态响应体 ── */
const MODELS = {
success: true,
items: [
{
context_window: 256000, description: 'Kimi-K3', fast_only: false,
max_output_tokens: 32768, model_key: 'Kimi-K3', multimodal: 'image,video',
name: 'Kimi-K3', supports_reasoning_effort: false, supports_thinking: true,
context_window: 1048576, description: 'Kimi-K3', fast_only: false,
max_output_tokens: 64000, model_key: 'Kimi-K3', multimodal: 'image,video',
name: 'Kimi-K3', supports_reasoning_effort: true, supports_thinking: true,
thinking_only: false, visible: true
},
{
context_window: 1000000, description: 'DeepSeek-V4-Pro', fast_only: false,
max_output_tokens: 384000, model_key: 'DeepSeek-V4-Pro', multimodal: 'none',
name: 'DeepSeek-V4-Pro', supports_reasoning_effort: true, supports_thinking: true,
thinking_only: false, visible: true
},
{
context_window: 1000000, description: 'DeepSeek-V4-Flash', fast_only: false,
max_output_tokens: 384000, model_key: 'DeepSeek-V4-Flash', multimodal: 'none',
name: 'DeepSeek-V4-Flash', supports_reasoning_effort: true, supports_thinking: true,
thinking_only: false, visible: true
},
{
context_window: 262144, description: 'GLM-5.2', fast_only: false,
max_output_tokens: 64072, model_key: 'GLM-5.2', multimodal: 'none',
name: 'GLM-5.2', supports_reasoning_effort: true, supports_thinking: true,
thinking_only: false, visible: true
},
{
context_window: 1000000, description: 'MiniMax-M3', fast_only: false,
max_output_tokens: 131072, model_key: 'MiniMax-M3', multimodal: 'none',
name: 'MiniMax-M3', supports_reasoning_effort: false, supports_thinking: true,
thinking_only: false, visible: true
}
]
@ -28,18 +53,26 @@ const SESSION_STATUS = {
session: { host_mode: true, logged_in: true, role: 'admin', run_mode: 'thinking', thinking_mode: true, username: 'host' }
};
const WORKSPACES = {
success: true,
data: {
current_workspace_id: 'ws-daily',
default_workspace_id: 'ws-daily',
workspaces: [
{ workspace_id: 'ws-daily', label: '日常使用', path: '/Users/demo/daily', is_current: true, running_task_count: 0 },
{ workspace_id: 'ws-starraid', label: 'StarRaid', path: '/Users/demo/starraid', is_current: false, running_task_count: 0 },
{ workspace_id: 'ws-dev', label: '开发实战', path: '/Users/demo/dev', is_current: false, running_task_count: 0 }
]
}
};
// 工作区列表为静态数据「当前工作区」是会话态——select 接口会改它,
// 列表与 select 响应都从它动态组装;否则 current_workspace_id 永远钉死,
// 前端切换后 toast/分组高亮会拿错工作区(实测弹窗恒显示「日常使用」)。
const WS_LIST = [
{ workspace_id: 'ws-daily', label: '日常使用', path: '/Users/demo/daily', running_task_count: 0 },
{ workspace_id: 'ws-starraid', label: 'StarRaid', path: '/Users/demo/starraid', running_task_count: 0 },
{ workspace_id: 'ws-dev', label: '开发实战', path: '/Users/demo/dev', running_task_count: 0 }
];
// 初始与 demo 默认落点(弹幕调研对话所属工作区)一致
let currentWorkspaceId = 'ws-starraid';
function buildWorkspaces() {
return {
success: true,
data: {
current_workspace_id: currentWorkspaceId,
default_workspace_id: 'ws-daily',
workspaces: WS_LIST.map((w) => ({ ...w, is_current: w.workspace_id === currentWorkspaceId }))
}
};
}
const UI_BLOCKS = {
block_compress_conversation: false, block_conversation_review: false,
@ -52,24 +85,28 @@ const STATUS = {
success: true,
version: '2026-08-23',
host_mode: true,
container: { username: 'demo', mode: 'host', workspace_path: '/Users/demo/daily', running: true },
admin_policy: { disabled_models: [], forced_category_states: {}, ui_blocks: UI_BLOCKS, version: '2026-08-23' }
};
const PERSONALIZATION = {
success: true,
data: {
theme: 'classic',
theme: 'dark',
tool_intent_enabled: true,
group_sidebar_by_workspace: true,
block_display_mode: 'minimal',
compact_message_display: 'brief',
communication_style: 'auto',
conversation_continuity: 'medium',
agents_md_auto_inject: true,
auto_deep_compress_enabled: true,
deep_compress_trigger_tokens: 200000,
auto_generate_title: true,
auto_shallow_compress_enabled: false
},
context_compression_settings: {
context_window_tokens: 256000, deep_trigger_tokens: 256000,
context_window_tokens: 256000, deep_trigger_tokens: 200000,
shallow_keep_recent_tools: 15, shallow_keep_user_turn_tools: 3,
shallow_max_replace_per_round: 10, shallow_trigger_tokens: 1000,
shallow_trigger_tool_calls_interval: 10
@ -81,7 +118,14 @@ const PERMISSION_MODE = { success: true, mode: 'unrestricted', options: ['readon
const EXECUTION_MODE = { success: true, enabled: true, options: ['sandbox', 'direct'], pending_mode: null, state: { default_mode: 'sandbox', mode: 'sandbox' } };
const NETWORK_PERMISSION = { success: true, enabled: true, mode: 'restricted', options: ['restricted', 'full', 'none'], pending_mode: null };
const TUTORIAL = { success: true, data: { applicable: false, should_prompt: false, tutorial_completed: true, username: 'host' } };
const USAGE = { success: true, data: { quotas: { fast: { count: 0, limit: 200 }, thinking: { count: 0, limit: 200 }, search: { count: 0, limit: 20 } }, role: 'user' } };
// 宿主机模式不统计用量record_model_call/record_search_call 直接 returnquotas count 恒为 0、
// window_start/reset_at 为 nullget_quota_snapshot 行为limit 为配置默认值。勿编造非 0 数字。
const USAGE = { success: true, data: { quotas: { fast: { count: 0, limit: 200, window_start: null, reset_at: null }, thinking: { count: 0, limit: 200, window_start: null, reset_at: null }, search: { count: 0, limit: 20, window_start: null, reset_at: null } }, role: 'user' } };
// ── Token 统计 mock右下角 token 菜单/抽屉数据源)──
// 数据为每条对话的真实 token_statistics由 build-mocks.cjs 导出为 mock/tokens-<id>.json。
// 当前上下文走 /api/conversations/{id}/tokens统计区走 /token-statistics。上限 200k 由
// PERSONALIZATION 的 deep_compress_trigger_tokens 决定(深度压缩阈值)。
const EMPTY_OK = { success: true, data: {} };
const EMPTY_LIST = { success: true, data: [] };
const GIT_SUMMARY = { success: true, data: { available: false } };
@ -128,11 +172,34 @@ self.addEventListener('fetch', (e) => {
if (!url.pathname.startsWith('/api/')) return; // 静态资源放行
e.respondWith(routeApi(url));
e.respondWith(routeApi(url, e.request));
});
async function routeApi(url) {
// ── 模拟流式回复:演示站发送消息时,返回固定文案「实际功能请部署后体验」逐字流式吐出 ──
// 协议对齐真实后端POST /api/tasks 创建任务GET /api/tasks/{id}?from=N 增量拉事件
// text_start → 逐字 text_chunk → text_end → task_completestatus running→succeeded
// 逐字 100ms用户拍板前端轮询周期 250ms每轮稳定拿 2~3 字,节奏快而匀。
const DEMO_REPLY = '实际功能请部署后体验';
const DEMO_TASK_EVENT_INTERVAL_MS = 100;
let demoTask = null; // { taskId, convId, startTs, events }SW 休眠后丢失,重发即可
function buildDemoReplyEvents() {
const evts = [{ idx: 0, type: 'text_start', data: {} }];
Array.from(DEMO_REPLY).forEach((ch, i) => {
evts.push({ idx: i + 1, type: 'text_chunk', data: { content: ch } });
});
evts.push({ idx: DEMO_REPLY.length + 1, type: 'text_end', data: { full_content: DEMO_REPLY } });
evts.push({
idx: DEMO_REPLY.length + 2,
type: 'task_complete',
data: { has_running_sub_agents: false, has_running_background_commands: false, has_running_multi_agent: false }
});
return evts;
}
async function routeApi(url, req) {
const p = url.pathname;
const method = req ? req.method : 'GET';
// 子智能体活动详情
const actMatch = p.match(/^\/api\/sub_agents\/(sub_[\w-]+)\/activity$/);
@ -150,21 +217,72 @@ async function routeApi(url) {
return json({ success: true, data: [] });
}
// 多智能体活跃实例(演示无)
if (p === '/api/multiagent/active_sub_agents') return json({ success: true, agents: [] });
// 多智能体活跃实例猫咪对话conv_20260902_235153_495有 4 个实例,其余对话为空。
// 这是 QuickDock 子智能体窗口在多智能体对话下的唯一数据源,钉空会让快捷窗口无法展开。
if (p === '/api/multiagent/active_sub_agents') {
const convId = url.searchParams.get('conversation_id') || '';
const r = await fetch(`/mock/ma-agents-${convId}.json`);
if (r.ok) return new Response(await r.text(), { headers: { 'Content-Type': 'application/json; charset=utf-8' } });
return json({ success: true, agents: [] });
}
// 待办 / 后台命令(演示对话为空)
if (p === '/api/todo-list') return json({ success: true, data: null });
// 待办列表:有 todo mock 的对话如猫咪返回真实内容否则空——QuickDock 待办窗口数据源
if (p === '/api/todo-list') {
const convId = url.searchParams.get('conversation_id') || '';
if (convId) {
const r = await fetch(`/mock/todo-${convId}.json`);
if (r.ok) return new Response(await r.text(), { headers: { 'Content-Type': 'application/json; charset=utf-8' } });
}
return json({ success: true, data: null });
}
if (p === '/api/background_commands') return json({ success: true, data: [] });
// 文件预览(快捷窗口点文件名):返回真实调研报告内容
// 文件预览/下载(对话内文件卡片、下载链接)
if (p === '/api/file/content') {
const name = (url.searchParams.get('path') || '').split('/').pop();
// 二进制/富文本资源放 mock/files/(按扩展名给 MIME
const ext = (name.split('.').pop() || '').toLowerCase();
const MIME = {
pdf: 'application/pdf',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
html: 'text/html; charset=utf-8',
png: 'image/png',
jpg: 'image/jpeg',
};
if (MIME[ext]) {
const rb = await fetch(`/mock/files/${name}`);
if (rb.ok) return new Response(await rb.arrayBuffer(), { headers: { 'Content-Type': MIME[ext] } });
}
// 文本资源mock/file-*
const r = await fetch(`/mock/file-${name}`);
if (r.ok) return new Response(await r.text(), { headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
return new Response('演示环境没有该文件', { status: 404 });
}
// 消息内嵌媒体view_image 截图等mock/media/{sha}.png
const mediaMatch = p.match(/^\/api\/conversations\/media\/(.+)$/);
if (mediaMatch) {
const id = decodeURIComponent(mediaMatch[1]).replace(/^sha256:/, '');
const r = await fetch(`/mock/media/${id}.png`);
if (r.ok) return new Response(await r.arrayBuffer(), { headers: { 'Content-Type': 'image/png' } });
return new Response('演示环境没有该媒体', { status: 404 });
}
// 对话 token当前上下文 / 统计区(均读真实导出的 mock/tokens-<id>.json
const tokensMatch = p.match(/^\/api\/conversations\/(conv_[\w-]+)\/tokens$/);
if (tokensMatch) {
const r = await fetch(`/mock/tokens-${tokensMatch[1]}.json`);
const d = r.ok ? await r.json() : null;
return json({ success: true, data: { total_tokens: (d && d.current_context_tokens) || 0 } });
}
const tokenStatsMatch = p.match(/^\/api\/conversations\/(conv_[\w-]+)\/token-statistics$/);
if (tokenStatsMatch) {
const r = await fetch(`/mock/tokens-${tokenStatsMatch[1]}.json`);
if (r.ok) return json({ success: true, data: await r.json() });
return json({ success: true, data: { total_input_tokens: 0, total_output_tokens: 0, total_tokens: 0, total_cached_input_tokens: 0, cache_exempt_input_tokens: 0, current_context_tokens: 0 } });
}
// 对话 bootstrap
const bootMatch = p.match(/^\/api\/conversations\/(conv_[\w-]+)\/bootstrap$/);
if (bootMatch) {
@ -173,18 +291,41 @@ async function routeApi(url) {
return json({ success: false, error: '对话不存在' }, 404);
}
// 对话列表
// 版本控制状态:必须显式 mock前端 fetchVersioningStatus 会用 payload.host_mode
// 覆盖全局 versioningHostMode若落到兜底 EMPTY_OKdata:{}host_mode=undefined
// 会把 host 模式打回 false分组视图立刻消失且 persist 进 localStorage 持续作恶。
const versioningMatch = p.match(/^\/api\/conversations\/(conv_[\w-]+)\/versioning$/);
if (versioningMatch) {
return json({ success: true, data: { host_mode: true, enabled: false, mode: 'overwrite' } });
}
// 对话列表(支持 workspace_id 过滤:按 WORKSPACES 的 path 匹配对话 project_path
if (p === '/api/conversations') {
const ma = url.searchParams.get('multi_agent_mode') === '1';
const r = await fetch(`/mock/conversations-${ma ? 'ma' : 'normal'}.json`);
return new Response(await r.text(), { headers: { 'Content-Type': 'application/json; charset=utf-8' } });
const wsId = url.searchParams.get('workspace_id');
if (!wsId) {
return new Response(await r.text(), { headers: { 'Content-Type': 'application/json; charset=utf-8' } });
}
const all = await r.json();
const ws = WS_LIST.find((w) => w.workspace_id === wsId);
const list = (all.data.conversations || []).filter((c) => !ws || c.project_path === ws.path);
return json({ success: true, data: { conversations: list, has_more: false, limit: 50, offset: 0, total: list.length } });
}
// 各状态端点
if (p === '/api/session-status') return json(SESSION_STATUS);
if (p === '/api/csrf-token') return json({ success: true, csrf_token: 'demo' });
if (p === '/api/v1/models') return json(MODELS);
if (p === '/api/host/workspaces') return json(WORKSPACES);
if (p === '/api/host/workspaces/select') {
const target = url.searchParams.get('workspace_id') || '';
const ws = WS_LIST.find((w) => w.workspace_id === target);
if (ws) currentWorkspaceId = ws.workspace_id;
// 故意不返回 project_path前端 toast 取值顺序是 project_path || 工作区名 || id
// 演示区路径(/Users/demo/xxx没有展示价值缺省后 toast 显示工作区名称。
return json({ success: true, data: { current_workspace_id: currentWorkspaceId, default_workspace_id: 'ws-daily', workspace_label: ws ? ws.label : '' } });
}
if (p === '/api/host/workspaces') return json(buildWorkspaces());
if (p === '/api/status') return json(STATUS);
if (p === '/api/personalization') return json(PERSONALIZATION);
if (p === '/api/work-mode') return json(WORK_MODE);
@ -196,6 +337,41 @@ async function routeApi(url) {
if (p === '/api/project/git-summary') return json(GIT_SUMMARY);
if (p === '/api/health') return json({ status: 'ok' });
if (p === '/api/focused') return json({});
// 发送消息 → 创建模拟任务(前端随后按 task_id 轮询事件)
if (p === '/api/tasks' && method === 'POST') {
const body = await req.json().catch(() => ({}));
demoTask = {
taskId: `demo_task_${Date.now()}`,
convId: String(body.conversation_id || ''),
startTs: Date.now(),
events: buildDemoReplyEvents(),
};
return json({ success: true, data: { task_id: demoTask.taskId, status: 'running', created_at: new Date().toISOString() } });
}
// 模拟任务轮询:按经过时间逐步放事件,放完后 status=succeeded前端停止轮询
const demoTaskMatch = p.match(/^\/api\/tasks\/(demo_task_\d+)$/);
if (demoTaskMatch) {
if (!demoTask || demoTask.taskId !== demoTaskMatch[1]) {
return json({ success: false, error: '任务不存在或已过期' }, 404);
}
const from = Math.max(0, Number(url.searchParams.get('from') || 0));
const visible = Math.min(demoTask.events.length, Math.floor((Date.now() - demoTask.startTs) / DEMO_TASK_EVENT_INTERVAL_MS));
const done = visible >= demoTask.events.length;
return json({
success: true,
data: {
task_id: demoTask.taskId,
status: done ? 'succeeded' : 'running',
events: demoTask.events.slice(from, visible),
next_offset: visible,
conversation_id: demoTask.convId,
updated_at: new Date().toISOString(),
}
});
}
if (p === '/api/tasks' || p === '/api/terminals') return json(EMPTY_LIST);
if (p === '/api/tool-settings') return json({ success: true, categories: [] });
if (p === '/api/effective-policy') return json({ success: true, data: { categories: {} } });