diff --git a/docs/en/benchmarks/index.md b/docs/en/benchmarks/index.md index d58d4f38d..b1068a101 100644 --- a/docs/en/benchmarks/index.md +++ b/docs/en/benchmarks/index.md @@ -36,7 +36,7 @@ benchmark: accessible: 86.73 percent task resolution metric: tasks resolved with PowerContext on orientation: - title: Two benchmarks. Two different questions. + title: Two benchmarks, two questions. lead: "Memory quality matters twice: first when an agent must recover what happened, then when it must use context to finish real work." tests: - name: LoCoMo @@ -128,7 +128,7 @@ benchmark: scope_title: What this result covers scope: The 90.78% result is 1,398 correct answers from 1,540 questions in categories 1-4. It does not claim results for LoCoMo event summarization or multimodal dialogue generation. swe: - title: SWE-bench Pro tests whether context changes the patch. + title: Does context improve the patch? lead: Each task starts from a real codebase and issue. Codex edits the repository, and the official task tests decide whether the patch resolves the problem. method: - title: Same task set @@ -422,8 +422,10 @@ benchmark: score: 1.51% ci: ±1.51 reading: - title: Read each result for the question it answers. + title: Compare what each result measures. lead: The two evaluations share a context theme, but their inputs, outputs, and graders are intentionally different. + scroll_hint: Swipe sideways to compare both evaluations. + table_label: Comparison of the LoCoMo and SWE-bench Pro evaluations columns: dimension: Evaluation dimension rows: diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index 328f017ef..46fb8a42c 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -1,20 +1,22 @@ --- template: docs-overview.html -title: Choose an Agent or API and complete the first loop +title: Choose an Agent or API description: Add the HTTP Memory API to an existing AI application, or configure an Agent and verify Memory and Handoff. page_type: docs-overview overview: intro: If you are new to PowerContext, choose the entry that matches your application. Use HTTP directly from an existing AI application, or start with the Agent quickstart for Codex, Claude Code, OpenCode, and other hosts. sections: - - title: Recommended learning path - description: Complete a local API or Agent loop first, then connect another host or adopt the complete work workflow. + - title: Start here + description: Complete an API or Agent quickstart first. The remaining guides help you continue in the Agent you use. cards: - title: HTTP API lifecycle tutorial description: Use one small Python flow to connect context, Experience, Skill, and Review; use Scalar for endpoint reference. href: en/docs/tutorials/api-quickstart/ + featured: true - title: Agent step-by-step quickstart description: Choose a host, install and diagnose it, complete Memory, then use its one-line, pc_*, or /pc Handoff path. href: en/docs/tutorials/agent-quickstart/ + featured: true - title: Complete Codex tutorial description: Go deeper into the Codex Hook, MCP Skill, Memory lifecycle, and one-line durable Handoff. href: en/docs/tutorials/codex-quickstart/ diff --git a/docs/en/index.md b/docs/en/index.md index 2bebebbfc..2c7daa96b 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -1,50 +1,44 @@ --- template: home.html title: PowerContext -description: Add Memory to your own AI over HTTP, or choose a supported agent for cross-session recovery and Handoff. +description: Continue work in a new session without restating decisions, constraints, and progress. Use a supported Agent or connect your own AI over HTTP. hide: - navigation - toc - footer home: hero: - label: Open source · Project scoped · Local by default + label: Open source. Local storage by default. title: - - Start a new session. - - Keep moving. - lead: PowerContext keeps decisions, constraints, next steps, and work boundaries outside the chat. Existing AI applications can call HTTP directly; Codex, Claude Code, DSH, OpenCode, and other agents can connect to the same Server. - note: You control the model, identity, and write authorization; PowerContext provides cited Memory, request-time context, and history-preserving revisions. + - Pick up where you left off. + lead: Save the decisions, constraints, and progress you will need later in PowerContext. Find them again in another session. actions: - - label: Choose your agent + - label: Start with an Agent href: en/docs/tutorials/agent-quickstart/ kind: primary - - label: Add the API to your AI + - label: Connect your AI over HTTP href: en/docs/tutorials/api-quickstart/ kind: secondary continuity: - label: One project, multiple sessions - title: One agent stops. Work continues. - lead: Different agents can implement, review, and validate compatibility. Handoff transfers an inspected boundary; a human decides whether to continue. + title: No need to repeat yourself. + lead: Save information that needs to last. Later sessions can use it and see how it changed. steps: - - title: Agent A implements - description: Record decisions, constraints, and next steps, then assemble the objective, changes, checks, and omissions as a Handoff. - - title: Agent B checks independently - description: Receive the exact Revision in another host and verify its evidence and risks against the current repository. - - title: A human decides - description: Confirm scope, capability, and authorization, then continue, request clarification, or decline. Receipt is not completion. + - title: Save + description: Record the decisions, constraints, and next steps you will need later. + - title: Find + description: Find the relevant information in another Agent or application without pasting content you already saved. + - title: Continue + description: Check that the information still fits the current situation, then continue the work. ownership: - label: Memory and Handoff title: - - Keep what lasts. + - Save what matters. - Hand off the work. - lead: Memory keeps decisions, constraints, conventions, and next steps in a searchable history. Revise or retire an entry without losing the record. - handoff: A Handoff captures the current objective, verified progress, blockers, and next action. Commit it when the work becomes a project milestone. - result: "LOCOMO: 90.78% correct · 1.38 s p95 search latency" - command: powercontext server run + lead: Memory keeps information that stands on its own and will matter later. You can revise or retire an entry while preserving its history. + handoff: A Handoff saves the current objective and progress. It also records blockers and the next action so another person, Agent, or later session can continue. A proven approach can become an Experience. Recurring work can become a Skill. primary_action: - label: Open the Agent quickstart + label: Read the Agent quickstart href: en/docs/tutorials/agent-quickstart/ secondary_action: - label: Open the API lifecycle tutorial + label: Connect your AI over HTTP href: en/docs/tutorials/api-quickstart/ --- diff --git a/docs/zh/benchmarks/index.md b/docs/zh/benchmarks/index.md index 5d45a27e6..a7b249b5c 100644 --- a/docs/zh/benchmarks/index.md +++ b/docs/zh/benchmarks/index.md @@ -10,8 +10,7 @@ benchmark: hero: label: 评估测试证据 title: - - 把上下文能力, - - 交给评估测试。 + - 上下文压力测试 lead: 两项公开评测,分别检验长程记忆与真实仓库软件工程,并给出可核对的结果。 actions_label: 跳转到具体评测 actions: @@ -36,7 +35,7 @@ benchmark: accessible: 任务解决率 86.73% metric: 开启 PowerContext 后的任务解决率 orientation: - title: 两项评估测试,回答两个不同问题。 + title: 两项评测,两个问题。 lead: Agent 先要找回发生过什么,再要把上下文用于真实工作。两项评测分别检验这两层能力。 tests: - name: LoCoMo @@ -128,7 +127,7 @@ benchmark: scope_title: 结果覆盖范围 scope: 90.78% 来自类别 1-4 的 1,540 个问题,其中答对 1,398 个。该结果不代表 LoCoMo 的事件总结或多模态对话生成任务。 swe: - title: SWE-bench Pro 检验上下文是否改变补丁结果。 + title: 上下文能改善补丁结果吗? lead: 每个任务都从真实代码库与 Issue 开始。Codex 修改仓库,再由任务自带的正式测试判断补丁是否解决问题。 method: - title: 相同任务集 @@ -422,8 +421,10 @@ benchmark: score: 1.51% ci: ±1.51 reading: - title: 用每项结果回答它真正评测的问题。 + title: 先看清每项结果测什么。 lead: 两项评测都与上下文有关,但输入、输出与评分方式不同,不能把两个分数直接横向比较。 + scroll_hint: 左右滑动,比较两项评测。 + table_label: LoCoMo 与 SWE-bench Pro 评测对比 columns: dimension: 评测维度 rows: diff --git a/docs/zh/docs/index.md b/docs/zh/docs/index.md index b70c2c6a3..ebe673d83 100644 --- a/docs/zh/docs/index.md +++ b/docs/zh/docs/index.md @@ -1,20 +1,22 @@ --- template: docs-overview.html -title: 选择 Agent 或 API,跑通第一个闭环 +title: 选择 Agent 或 API description: 为现有 AI 应用接入 HTTP Memory API,或为选定 Agent 配置集成并验证 Memory 与 Handoff。 page_type: docs-overview overview: intro: 第一次使用时,先选择自己的入口。已有 AI 应用可以直接使用 HTTP API;使用 Codex、Claude Code、OpenCode 等 Host 时,从 Agent 分步入门开始。 sections: - - title: 推荐学习路径 - description: 先通过 API 或 Agent 跑通本地闭环,再按需要接入其他 Host 或更完整的工作流程。 + - title: 从这里开始 + description: 先通过 API 或 Agent 跑通一次。其余说明帮助你在所用 Agent 中继续工作。 cards: - title: HTTP API 生命周期教程 description: 用一个小型 Python 流程接入上下文、Experience、Skill 与 Review;接口参考统一使用 Scalar。 href: zh/docs/tutorials/api-quickstart/ + featured: true - title: Agent 分步入门 description: 选择 Host,安装并诊断集成,跑通 Memory,再按一句话、pc_* 或 /pc 路径完成 Handoff。 href: zh/docs/tutorials/agent-quickstart/ + featured: true - title: Codex 完整教程 description: 深入 Codex Hook、MCP Skill、Memory 生命周期与一句话 durable Handoff。 href: zh/docs/tutorials/codex-quickstart/ diff --git a/docs/zh/index.md b/docs/zh/index.md index f5d2c47b7..85da3dcd7 100644 --- a/docs/zh/index.md +++ b/docs/zh/index.md @@ -1,50 +1,45 @@ --- template: home.html title: PowerContext -description: 通过 HTTP 为自己的 AI 接入 Memory,或选择受支持的 Agent 跑通跨会话恢复与 Handoff。 +description: 换个会话继续工作,不必重新交代决定、约束和进展。可以使用受支持的 Agent,也可以通过 HTTP 接入自己的 AI。 hide: - navigation - toc - footer home: hero: - label: 开源 · 项目级 · 默认本地 + label: 开源,默认使用本地存储 title: - - 换一个会话, - - 项目继续向前。 - lead: PowerContext 把决定、约束、下一步和任务边界保存在对话之外。现有 AI 应用可以直接调用 HTTP API;Codex、Claude Code、DSH、OpenCode 等 Agent 也可以连接同一个 Server。 - note: 你控制模型、身份和写入授权;PowerContext 提供带 citation 的 Memory、请求时上下文和保留历史的修订。 + - 换个会话, + - 接着做。 + lead: 把需要保留的决定、约束和进展存进 PowerContext。换个会话,也能找回这些信息。 actions: - - label: 选择你的 Agent + - label: 从 Agent 开始 href: zh/docs/tutorials/agent-quickstart/ kind: primary - - label: 为自己的 AI 接入 API + - label: 通过 HTTP 接入 AI href: zh/docs/tutorials/api-quickstart/ kind: secondary continuity: - label: 同一项目,多个会话 - title: 一个 Agent 停下,工作仍能继续。 - lead: 实现、独立检查和兼容性验证可以由不同 Agent 完成;Handoff 传递经过检查的边界,人决定是否继续。 + title: 重要信息不必反复交代。 + lead: 把需要长期保留的信息记下来。后续会话可以继续使用,也能查看修改记录。 steps: - - title: Agent A 实现 - description: 记录项目决定、约束和下一步,并把目标、改动、检查与遗漏整理为 Handoff。 - - title: Agent B 独立核对 - description: 在另一个 Host 中按 exact Revision 接收,再用当前仓库重新验证证据和风险。 - - title: 人决定 - description: 人确认范围、能力和授权,决定继续、澄清或拒绝;接收不等于任务已经完成。 + - title: 记下 + description: 记下以后还要用的决定、约束和下一步。 + - title: 找回 + description: 换到另一个 Agent 或应用时,找到相关信息,不必重新粘贴已经保存的内容。 + - title: 继续 + description: 确认这些信息仍符合当前情况,然后接着做。 ownership: - label: Memory 与 Handoff title: - - 留下长期信息, - - 交接当前工作。 - lead: Memory 保存决定、约束、约定和下一步,并保留可检索的历史。修订或停用条目,不会丢失记录。 - handoff: Handoff 记录当前目标、已验证进展、阻塞项和下一步行动。工作形成项目里程碑后再提交。 - result: "LOCOMO:答对率 90.78% · 搜索 p95 延迟 1.38 秒" - command: powercontext server run + - 记住以后还要用的, + - 交接手头正在做的。 + lead: 记忆保存以后仍会用到、单独拿出来也能理解的信息。更新或停用其中一条,不会丢失历史版本。 + handoff: 交接保存当前目标和进度,也说明哪里卡住、下一步做什么。换人、换 Agent 或稍后再做时,可以从这里继续。已经验证的做法可以整理成经验,需要反复执行的工作可以整理成技能。 primary_action: - label: 打开 Agent 分步入门 + label: 阅读 Agent 快速入门 href: zh/docs/tutorials/agent-quickstart/ secondary_action: - label: 打开 HTTP API 生命周期教程 + label: 通过 HTTP 接入 AI href: zh/docs/tutorials/api-quickstart/ --- diff --git a/src/powercontext/server/static/dashboard.js b/src/powercontext/server/static/dashboard.js index b4e9f34e9..d8a588bb4 100644 --- a/src/powercontext/server/static/dashboard.js +++ b/src/powercontext/server/static/dashboard.js @@ -26,12 +26,12 @@ import {createPageUi, createRequestGate} from "./page-ui.js?v=locale-complete"; const translations = { en: { - pageTitle: "PowerContext Dashboard", - dashboardTitle: "Dashboard", + pageTitle: "PowerContext Overview", + dashboardTitle: "Overview", skillsTitle: "Skills", reviewTitle: "Review", handoffReportTitle: "Handoff Report", - brandHomeLabel: "PowerContext Dashboard", + brandHomeLabel: "PowerContext Overview", primaryNavigation: "Primary navigation", maintainedBy: "Maintained by OceanBase.", signOut: "Sign out", @@ -39,31 +39,26 @@ const translations = { authIntro: "Enter the bearer token configured for this PowerContext Server. The token stays in this browser tab.", tokenLabel: "Server token", continue: "Continue", - selectScope: "Scope", - period30: "Last 30 days", - estimatedReduction: "Estimated token reduction", - sources: "Sources", - memoryEntries: "Memory entries", - artifacts: "Artifacts", - pendingReview: "Pending review", - artifactFamilies: "Artifact families", - artifactSubtitle: "Current Artifacts and pending Candidates", - family: "Family", - currentArtifacts: "Current Artifacts", - pendingCandidates: "Pending Candidates", + selectScope: "View", + estimatedReduction: "Compared with using the original materials directly", + sources: "Work materials", + memoryEntries: "Memory", + artifacts: "Saved content", + pendingReview: "Awaiting review", + artifactFamilies: "By type", + family: "Type", + currentArtifacts: "Saved", + pendingCandidates: "Awaiting review", experience: "Experience", handoff: "Handoff", memory: "Memory", skill: "Skill", - dailyActivity: "Daily recall", - noRecall: "No hit", - hitNoReduction: "Hit · ≤0", - reductionLow: "1–255", - reductionMedium: "256–1,023", - reductionHigh: "1,024+", - recallTrend: "Recall savings trend", - trendSubtitle: "Estimated token reduction over the last 30 days", - estimatedReductionSeries: "Estimated reduction", + other: "Other", + noContent: "No content yet", + dailyActivity: "Use over the last 30 days", + noActivity: "No use", + moreActivity: "More use", + recallTrend: "Tokens saved or used each day", dark: "Dark", light: "Light", switchDark: "Switch to dark mode", @@ -72,27 +67,36 @@ const translations = { switchEnglish: "Switch to English", languageChinese: "中文", languageEnglish: "EN", - updated: "Updated {value}", - recallHits: "{hits} recall hits from {preparations} preparations", - activitySummary: "{hits} recall hits · estimated token reduction {savings} in the last 30 days", - activityAria: "30 days of scoped recall hits and estimated token reduction", - activityHit: "{date}: {hits} recall hits · estimated token reduction {savings}", - trendDescription: "{hits} recall hits. Estimated token reduction {savings}.", + updated: "As of {value}", + recallCoverage: "In the last 30 days, {comparable} of {preparations} uses could be compared.", + noPreparations: "No use in the last 30 days.", + tokensSaved: "Saved about {tokens} tokens", + tokensAdded: "Used about {tokens} more tokens", + tokensUnchanged: "Token use was about the same", + noComparison: "Not enough data to compare", + activitySummary: "Used {preparations} times in the last 30 days; content was found {hits} times.", + activityUnavailable: "Usage data is not available.", + activityAria: "Context use over the last 30 days", + activityHit: "{date}: used {preparations} times; content found {hits} times", + trendHit: "{date}: {comparable} of {preparations} uses could be compared; {comparison}", + trendDescription: "In the last 30 days, {comparable} of {preparations} uses could be compared. {comparison}.", + axisSaved: "Saved {tokens}", + axisAdded: "Used {tokens}", authRejected: "The Server rejected this token.", - requestFailed: "The Dashboard request failed with HTTP {status}.", + requestFailed: "Couldn't load the Overview. Try again.", serverUnavailable: "The Server is unavailable.", retry: "Retry", - noScopes: "No Dashboard scopes are configured.", - scopeUnavailable: "The selected scope is not available.", - scopeOverview: "Scope overview" + noScopes: "There is no work to show here.", + scopeUnavailable: "The selected work is not available.", + scopeOverview: "Overview for the selected work" }, zh: { - pageTitle: "PowerContext 仪表盘", - dashboardTitle: "仪表盘", + pageTitle: "PowerContext 概览", + dashboardTitle: "概览", skillsTitle: "技能", reviewTitle: "审核", handoffReportTitle: "交接报告", - brandHomeLabel: "PowerContext 仪表盘", + brandHomeLabel: "PowerContext 概览", primaryNavigation: "主导航", maintainedBy: "由 OceanBase 维护。", signOut: "退出", @@ -100,31 +104,26 @@ const translations = { authIntro: "请输入 PowerContext 服务器配置的访问令牌。令牌仅保留在当前浏览器标签页。", tokenLabel: "服务器访问令牌", continue: "继续", - selectScope: "作用域", - period30: "过去 30 天", - estimatedReduction: "预估令牌减少量", - sources: "数据源", - memoryEntries: "记忆条目", - artifacts: "制品", + selectScope: "查看", + estimatedReduction: "相比直接使用原始材料", + sources: "工作材料", + memoryEntries: "记忆", + artifacts: "已保存内容", pendingReview: "待审核", - artifactFamilies: "制品类型", - artifactSubtitle: "当前制品与待审核候选", + artifactFamilies: "按类型查看", family: "类型", - currentArtifacts: "当前制品", - pendingCandidates: "待审核候选", + currentArtifacts: "已保存", + pendingCandidates: "待审核", experience: "经验", handoff: "交接", memory: "记忆", skill: "技能", - dailyActivity: "每日召回", - noRecall: "无命中", - hitNoReduction: "命中 · ≤0", - reductionLow: "1–255", - reductionMedium: "256–1,023", - reductionHigh: "1,024+", - recallTrend: "召回节约趋势", - trendSubtitle: "过去 30 天的预估令牌减少量", - estimatedReductionSeries: "预估减少量", + other: "其他", + noContent: "暂无内容", + dailyActivity: "过去 30 天的使用情况", + noActivity: "未使用", + moreActivity: "使用更多", + recallTrend: "每天节省或多用的 Token", dark: "深色", light: "浅色", switchDark: "切换至深色模式", @@ -133,19 +132,28 @@ const translations = { switchEnglish: "切换至英文", languageChinese: "中文", languageEnglish: "EN", - updated: "更新于 {value}", - recallHits: "{preparations} 次准备中命中 {hits} 次召回", - activitySummary: "过去 30 天命中 {hits} 次召回 · 预估令牌减少量 {savings}", - activityAria: "当前作用域过去 30 天的召回命中和预估令牌减少量", - activityHit: "{date}:命中 {hits} 次召回 · 预估令牌减少量 {savings}", - trendDescription: "召回命中 {hits} 次,预估令牌减少量 {savings}。", + updated: "截至 {value}", + recallCoverage: "过去 30 天共使用 {preparations} 次,其中 {comparable} 次可以比较。", + noPreparations: "过去 30 天暂无使用记录。", + tokensSaved: "节省约 {tokens} Token", + tokensAdded: "多用约 {tokens} Token", + tokensUnchanged: "Token 用量基本相同", + noComparison: "暂时无法比较", + activitySummary: "过去 30 天共使用 {preparations} 次,其中 {hits} 次找到内容。", + activityUnavailable: "暂无使用数据。", + activityAria: "过去 30 天的上下文使用情况", + activityHit: "{date}:使用 {preparations} 次,找到内容 {hits} 次", + trendHit: "{date}:共使用 {preparations} 次,其中 {comparable} 次可以比较,{comparison}", + trendDescription: "过去 30 天共使用 {preparations} 次,其中 {comparable} 次可以比较。{comparison}。", + axisSaved: "节省 {tokens}", + axisAdded: "多用 {tokens}", authRejected: "服务器拒绝了该访问令牌。", - requestFailed: "仪表盘请求失败(HTTP {status})。", - serverUnavailable: "服务器无法访问。", + requestFailed: "无法加载概览,请重试。", + serverUnavailable: "无法连接服务器。", retry: "重试", - noScopes: "未配置仪表盘作用域。", - scopeUnavailable: "选中的作用域不可用。", - scopeOverview: "作用域概览" + noScopes: "这里还没有可查看的工作。", + scopeUnavailable: "无法查看这项工作。", + scopeOverview: "所选工作的概览" } }; const authShell = document.getElementById("auth-shell"); @@ -160,6 +168,7 @@ const signOut = document.getElementById("sign-out"); const scopeSelect = document.getElementById("scope-select"); const authenticationRequired = document.documentElement.dataset.serverAuthRequired === "true"; const svgNamespace = "http://www.w3.org/2000/svg"; +const productArtifactFamilies = new Set(["experience", "handoff", "skill"]); let currentView = null; let currentScopes = []; let currentAuthError = null; @@ -219,7 +228,7 @@ async function authenticate(token, scopeId = "") { return; } if (!response.ok) { - showPageStatus("requestFailed", {status: response.status}, true); + showPageStatus("requestFailed", {}, true); return; } currentScopes = await response.json(); @@ -227,7 +236,7 @@ async function authenticate(token, scopeId = "") { return; } if (currentScopes.length === 0) { - showPageStatus("noScopes", {}, true); + showPageStatus("noScopes"); return; } const selectedScopeId = currentScopes.some((scope) => scope.scope_id === scopeId) @@ -269,7 +278,7 @@ async function loadStatistics(token, scopeId, request = null) { return; } if (!response.ok) { - showPageStatus("requestFailed", {status: response.status}, true); + showPageStatus("requestFailed", {}, true); return; } const statistics = await response.json(); @@ -344,6 +353,7 @@ function renderDashboard(view) { const statistics = view.statistics; const inventory = statistics.inventory; const recall = statistics.recall; + const comparisonAvailable = recall.estimator !== null; authShell.hidden = true; pageStatus.hidden = true; dashboard.hidden = false; @@ -353,19 +363,17 @@ function renderDashboard(view) { setText("dashboard-name", view.selectedScope.display_name); setText("as-of", translate("updated", {value: formatDateTime(statistics.as_of)})); setText("sources", formatNumber(inventory.sources.total)); - setText("memory-entries", formatNumber(inventory.memory.entries.total)); - setText("artifacts", formatNumber(inventory.artifacts.total)); + setText("memory-entries", formatNumber(inventory.memory.entries.active)); + setText("artifacts", formatNumber(savedArtifactCount(inventory))); setText("pending-reviews", formatNumber(inventory.candidates.pending)); - setText("token-reduction", formatCompact(recall.totals.token_reduction)); - - setText("recall-hits", translate("recallHits", { - hits: formatNumber(recall.totals.ready_preparations), - preparations: formatNumber(recall.totals.preparations) - })); + setText("token-reduction", !comparisonAvailable || recall.totals.comparable_preparations === 0 + ? translate("noComparison") + : formatTokenComparison(recall.totals.token_reduction)); + setText("recall-hits", formatRecallCoverage(recall.totals, comparisonAvailable)); renderArtifactFamilies(inventory); - renderHeatmap(recall.daily); - renderTrend(recall.daily); + renderHeatmap(recall.daily, comparisonAvailable); + renderTrend(recall.daily, comparisonAvailable); } function renderScopes(scopes, selectedScopeId) { @@ -373,7 +381,7 @@ function renderScopes(scopes, selectedScopeId) { for (const scope of scopes) { const option = document.createElement("option"); option.value = scope.scope_id; - option.textContent = `${scope.display_name} (${scope.scope_id})`; + option.textContent = scope.display_name; option.selected = scope.scope_id === selectedScopeId; scopeSelect.appendChild(option); } @@ -384,12 +392,31 @@ function renderArtifactFamilies(inventory) { rows.replaceChildren(); const families = new Map(); for (const family of inventory.artifacts.by_family) { - families.set(family.family, {family: family.family, total: family.total, pending: 0}); + const displayFamily = productArtifactFamily(family.family); + if (displayFamily === null) { + continue; + } + const current = families.get(displayFamily) || {family: displayFamily, total: 0, pending: 0}; + current.total += family.total; + families.set(displayFamily, current); } for (const candidate of inventory.candidates.by_family) { - const family = families.get(candidate.family) || {family: candidate.family, total: 0, pending: 0}; - family.pending = candidate.pending; - families.set(candidate.family, family); + const displayFamily = productArtifactFamily(candidate.family); + if (displayFamily === null) { + continue; + } + const family = families.get(displayFamily) || {family: displayFamily, total: 0, pending: 0}; + family.pending += candidate.pending; + families.set(displayFamily, family); + } + if (families.size === 0) { + const row = document.createElement("tr"); + const empty = document.createElement("td"); + empty.colSpan = 3; + empty.textContent = translate("noContent"); + row.appendChild(empty); + rows.appendChild(row); + return; } for (const family of [...families.values()].sort((left, right) => left.family.localeCompare(right.family))) { const row = document.createElement("tr"); @@ -404,26 +431,50 @@ function renderArtifactFamilies(inventory) { } } -function renderHeatmap(days) { +function savedArtifactCount(inventory) { + return inventory.artifacts.by_family.reduce( + (total, family) => total + (family.family === "memory" ? 0 : family.total), + 0 + ); +} + +function productArtifactFamily(family) { + if (family === "memory") { + return null; + } + return productArtifactFamilies.has(family) ? family : "other"; +} + +function renderHeatmap(days, dataAvailable) { const heatmap = document.getElementById("heatmap"); const tooltip = document.getElementById("activity-tooltip"); + const chart = document.getElementById("activity-chart"); + const legend = document.getElementById("activity-legend"); heatmap.replaceChildren(); tooltip.hidden = true; + if (!dataAvailable) { + chart.hidden = true; + legend.hidden = true; + setText("activity-summary", translate("activityUnavailable")); + return; + } + chart.hidden = false; + legend.hidden = false; + let totalPreparations = 0; let totalHits = 0; - let totalSavings = 0; for (const day of days) { + const preparations = day.preparations; const hits = day.ready_preparations; - const savings = day.token_reduction; + totalPreparations += preparations; totalHits += hits; - totalSavings += savings; const cell = document.createElement("span"); - const level = heatmapLevel(hits, savings); + const level = heatmapLevel(preparations); cell.className = `activity-cell level-${level}`; const label = translate("activityHit", { date: formatDate(day.date), - hits: formatNumber(hits), - savings: formatNumber(savings) + preparations: formatNumber(preparations), + hits: formatNumber(hits) }); cell.addEventListener("pointerenter", (event) => showTooltip(tooltip, event, label)); cell.addEventListener("pointermove", (event) => positionTooltip(tooltip, event)); @@ -433,40 +484,43 @@ function renderHeatmap(days) { } heatmap.setAttribute("aria-label", translate("activityAria")); - setText("activity-summary", translate("activitySummary", { - hits: formatNumber(totalHits), - savings: formatNumber(totalSavings) - })); + setText("activity-summary", totalPreparations === 0 + ? translate("noPreparations") + : translate("activitySummary", { + preparations: formatNumber(totalPreparations), + hits: formatNumber(totalHits) + })); } -function heatmapLevel(hits, savings) { - if (hits === 0) { - return 0; - } - if (savings <= 0) { - return 1; - } - if (savings < 256) { - return 2; - } - if (savings < 1024) { - return 3; - } - return 4; +function heatmapLevel(preparations) { + return Math.min(preparations, 4); } -function renderTrend(days) { +function renderTrend(days, dataAvailable) { const chart = document.getElementById("trend-chart"); const tooltip = document.getElementById("trend-tooltip"); + const empty = document.getElementById("trend-empty"); + const wrap = document.getElementById("trend-wrap"); chart.replaceChildren(); tooltip.hidden = true; + const comparableDays = days.filter((day) => day.comparable_preparations > 0); + if (!dataAvailable || comparableDays.length === 0) { + const message = translate("noComparison"); + empty.textContent = message; + empty.hidden = false; + wrap.hidden = true; + setText("trend-description", message); + return; + } + empty.hidden = true; + wrap.hidden = false; const width = 720; const height = 220; - const insetLeft = 50; + const insetLeft = 104; const insetRight = 8; const insetY = 12; const plotHeight = height - insetY * 2; - const reductions = days.map((day) => day.token_reduction); + const reductions = comparableDays.map((day) => day.token_reduction); const observedMin = Math.min(0, ...reductions); const observedMax = Math.max(0, ...reductions); const minValue = observedMin; @@ -489,7 +543,7 @@ function renderTrend(days) { chart.appendChild(line); const label = document.createElementNS(svgNamespace, "text"); - label.textContent = formatCompact(value); + label.textContent = formatTokenAxis(value); label.setAttribute("x", String(insetLeft - 8)); label.setAttribute("y", String(y + 4)); label.setAttribute("class", "chart-axis-label"); @@ -499,8 +553,6 @@ function renderTrend(days) { chart.appendChild(series( days, - "token_reduction", - "chart-savings", minValue, maxValue, width, @@ -515,25 +567,34 @@ function renderTrend(days) { setText("trend-middle", formatShortDate(days[Math.floor(days.length / 2)].date)); setText("trend-end", formatShortDate(days[days.length - 1].date)); - const hits = days.reduce((sum, day) => sum + day.ready_preparations, 0); + const preparations = days.reduce((sum, day) => sum + day.preparations, 0); + const comparisons = days.reduce((sum, day) => sum + day.comparable_preparations, 0); const savings = days.reduce((sum, day) => sum + day.token_reduction, 0); setText("trend-description", translate("trendDescription", { - hits: formatNumber(hits), - savings: formatNumber(savings) + preparations: formatNumber(preparations), + comparable: formatNumber(comparisons), + comparison: formatTokenComparison(savings) })); } -function series(days, field, className, minValue, maxValue, width, height, insetLeft, insetRight, insetY) { - const line = document.createElementNS(svgNamespace, "polyline"); +function series(days, minValue, maxValue, width, height, insetLeft, insetRight, insetY) { + const line = document.createElementNS(svgNamespace, "path"); const plotWidth = width - insetLeft - insetRight; const plotHeight = height - insetY * 2; - const points = days.map((day, index) => { + let drawing = false; + const commands = []; + days.forEach((day, index) => { + if (day.comparable_preparations === 0) { + drawing = false; + return; + } const x = insetLeft + plotWidth * index / Math.max(days.length - 1, 1); - const y = chartY(day[field], minValue, maxValue, plotHeight, insetY); - return `${x.toFixed(2)},${y.toFixed(2)}`; + const y = chartY(day.token_reduction, minValue, maxValue, plotHeight, insetY); + commands.push(`${drawing ? "L" : "M"} ${x.toFixed(2)} ${y.toFixed(2)}`); + drawing = true; }); - line.setAttribute("points", points.join(" ")); - line.setAttribute("class", className); + line.setAttribute("d", commands.join(" ")); + line.setAttribute("class", "chart-savings"); return line; } @@ -552,11 +613,15 @@ function renderTrendPoints( const plotWidth = width - insetLeft - insetRight; const plotHeight = height - insetY * 2; days.forEach((day, index) => { + if (day.comparable_preparations === 0) { + return; + } const point = document.createElementNS(svgNamespace, "circle"); - const label = translate("activityHit", { + const label = translate("trendHit", { date: formatDate(day.date), - hits: formatNumber(day.ready_preparations), - savings: formatNumber(day.token_reduction) + preparations: formatNumber(day.preparations), + comparable: formatNumber(day.comparable_preparations), + comparison: formatTokenComparison(day.token_reduction) }); point.setAttribute("cx", String(insetLeft + plotWidth * index / Math.max(days.length - 1, 1))); point.setAttribute("cy", String(chartY(day.token_reduction, minValue, maxValue, plotHeight, insetY))); @@ -598,19 +663,46 @@ function setText(id, value) { } function formatFamily(value) { - if (translations[ui.locale()][value] || translations.en[value]) { - return translate(value); - } - return value - .split("_") - .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`) - .join(" "); + return translate(value); } function formatCompact(value) { return new Intl.NumberFormat(ui.localeTag(), {notation: "compact", maximumFractionDigits: 1}).format(value); } +function formatRecallCoverage(totals, dataAvailable) { + if (!dataAvailable) { + return ""; + } + if (totals.preparations === 0) { + return translate("noPreparations"); + } + return translate("recallCoverage", { + preparations: formatNumber(totals.preparations), + comparable: formatNumber(totals.comparable_preparations) + }); +} + +function formatTokenComparison(value) { + if (value > 0) { + return translate("tokensSaved", {tokens: formatCompact(value)}); + } + if (value < 0) { + return translate("tokensAdded", {tokens: formatCompact(Math.abs(value))}); + } + return translate("tokensUnchanged"); +} + +function formatTokenAxis(value) { + if (value > 0) { + return translate("axisSaved", {tokens: formatCompact(value)}); + } + if (value < 0) { + return translate("axisAdded", {tokens: formatCompact(Math.abs(value))}); + } + return "0"; +} + function formatDate(value) { return new Intl.DateTimeFormat(ui.localeTag(), {dateStyle: "medium", timeZone: "UTC"}).format(new Date(`${value}T00:00:00Z`)); } diff --git a/src/powercontext/server/static/handoff-report.js b/src/powercontext/server/static/handoff-report.js index 8d0cac06b..58d7380ea 100644 --- a/src/powercontext/server/static/handoff-report.js +++ b/src/powercontext/server/static/handoff-report.js @@ -35,11 +35,11 @@ const authenticationRequired = document.documentElement.dataset.serverAuthRequir const translations = { en: { pageTitle: "PowerContext Handoff Report", - dashboardTitle: "Dashboard", + dashboardTitle: "Overview", skillsTitle: "Skills", reviewTitle: "Review", handoffReportTitle: "Handoff Report", - brandHomeLabel: "PowerContext Dashboard", + brandHomeLabel: "PowerContext Overview", primaryNavigation: "Primary navigation", handoffSummary: "Handoff summary", maintainedBy: "Maintained by OceanBase.", @@ -237,11 +237,11 @@ const translations = { }, zh: { pageTitle: "PowerContext 项目交接报告", - dashboardTitle: "仪表盘", + dashboardTitle: "概览", skillsTitle: "技能", reviewTitle: "审核", handoffReportTitle: "交接报告", - brandHomeLabel: "PowerContext 仪表盘", + brandHomeLabel: "PowerContext 概览", primaryNavigation: "主导航", handoffSummary: "交接摘要", maintainedBy: "由 OceanBase 维护。", diff --git a/src/powercontext/server/static/review.js b/src/powercontext/server/static/review.js index ee7a4e665..cfbb3d1eb 100644 --- a/src/powercontext/server/static/review.js +++ b/src/powercontext/server/static/review.js @@ -27,11 +27,11 @@ import {createPageUi, createRequestGate} from "./page-ui.js?v=locale-complete"; const translations = { en: { pageTitle: "PowerContext Review", - dashboardTitle: "Dashboard", + dashboardTitle: "Overview", skillsTitle: "Skills", reviewTitle: "Review", handoffReportTitle: "Handoff Report", - brandHomeLabel: "PowerContext Dashboard", + brandHomeLabel: "PowerContext Overview", primaryNavigation: "Primary navigation", maintainedBy: "Maintained by OceanBase.", signOut: "Sign out", @@ -168,7 +168,7 @@ const translations = { serverUnavailable: "The Server is unavailable.", authRejected: "The Server rejected this token.", retry: "Retry", - noScopes: "No Dashboard scopes are configured.", + noScopes: "There is no work to show here.", scopeUnavailable: "The selected scope is not available.", loading: "Loading...", loadingMore: "Loading more...", @@ -179,11 +179,11 @@ const translations = { }, zh: { pageTitle: "PowerContext 审核", - dashboardTitle: "仪表盘", + dashboardTitle: "概览", skillsTitle: "技能", reviewTitle: "审核", handoffReportTitle: "交接报告", - brandHomeLabel: "PowerContext 仪表盘", + brandHomeLabel: "PowerContext 概览", primaryNavigation: "主导航", maintainedBy: "由 OceanBase 维护。", signOut: "退出", @@ -320,7 +320,7 @@ const translations = { serverUnavailable: "服务器无法访问。", authRejected: "服务器拒绝了该访问令牌。", retry: "重试", - noScopes: "未配置仪表盘作用域。", + noScopes: "这里还没有可查看的工作。", scopeUnavailable: "选中的作用域不可用。", loading: "正在加载……", loadingMore: "正在加载更多……", @@ -628,7 +628,7 @@ async function authenticate(token, preferredScopeId = "") { return; } if (scopes.length === 0) { - showPageStatus("noScopes", {}, true); + showPageStatus("noScopes"); return; } const selectedScopeId = scopes.some((scope) => scope.scope_id === preferredScopeId) diff --git a/src/powercontext/server/static/site.css b/src/powercontext/server/static/site.css index 1c8709edf..9a4507c4e 100644 --- a/src/powercontext/server/static/site.css +++ b/src/powercontext/server/static/site.css @@ -4144,13 +4144,38 @@ button:disabled { } .topbar-inner { + height: auto; + padding-top: 10px; gap: 10px; + flex-wrap: wrap; } .primary-nav { + order: 3; + flex: 1 0 100%; + width: 100%; + min-width: 0; + height: 44px; + display: flex; + overflow-x: auto; + gap: 0; + scrollbar-width: none; + } + + .primary-nav::-webkit-scrollbar { display: none; } + .primary-nav a { + flex: 1 0 auto; + justify-content: center; + padding-inline: 8px; + } + + .topbar { + height: auto; + } + .review-toolbar label { flex-basis: 100%; } @@ -4372,6 +4397,11 @@ button:disabled { } @media (max-width: 360px) { + .primary-nav a { + padding-inline: 4px; + font-size: 12px; + } + .brand-wordmark { width: 116px; } diff --git a/src/powercontext/server/static/skills.js b/src/powercontext/server/static/skills.js index 9d5468f48..d32f23be7 100644 --- a/src/powercontext/server/static/skills.js +++ b/src/powercontext/server/static/skills.js @@ -27,11 +27,11 @@ import {createPageUi, createRequestGate} from "./page-ui.js?v=locale-complete"; const translations = { en: { pageTitle: "PowerContext Skills Library", - dashboardTitle: "Dashboard", + dashboardTitle: "Overview", skillsTitle: "Skills", reviewTitle: "Review", handoffReportTitle: "Handoff Report", - brandHomeLabel: "PowerContext Dashboard", + brandHomeLabel: "PowerContext Overview", primaryNavigation: "Primary navigation", maintainedBy: "Maintained by OceanBase.", signOut: "Sign out", @@ -140,16 +140,16 @@ const translations = { requestFailed: "The Skills request failed with HTTP {status}.", serverUnavailable: "The Server is unavailable.", retry: "Retry", - noScopes: "No Dashboard scopes are configured.", + noScopes: "There is no work to show here.", scopeUnavailable: "The selected scope is not available." }, zh: { pageTitle: "PowerContext 技能库", - dashboardTitle: "仪表盘", + dashboardTitle: "概览", skillsTitle: "技能", reviewTitle: "审核", handoffReportTitle: "交接报告", - brandHomeLabel: "PowerContext 仪表盘", + brandHomeLabel: "PowerContext 概览", primaryNavigation: "主导航", maintainedBy: "由 OceanBase 维护。", signOut: "退出", @@ -258,7 +258,7 @@ const translations = { requestFailed: "技能请求失败(HTTP {status})。", serverUnavailable: "服务器无法访问。", retry: "重试", - noScopes: "未配置仪表盘作用域。", + noScopes: "这里还没有可查看的工作。", scopeUnavailable: "选中的作用域不可用。" } }; @@ -474,7 +474,7 @@ async function authenticate(token, preferred = "") { return; } if (scopes.length === 0) { - showPageStatus("noScopes", {}, true); + showPageStatus("noScopes"); return; } currentScopeId = scopes.some((scope) => scope.scope_id === preferred) diff --git a/src/powercontext/server/templates/components/activity_heatmap.html b/src/powercontext/server/templates/components/activity_heatmap.html index 8e11f726b..c6cc0f181 100644 --- a/src/powercontext/server/templates/components/activity_heatmap.html +++ b/src/powercontext/server/templates/components/activity_heatmap.html @@ -17,19 +17,22 @@
-

Daily recall

+

Use over the last 30 days

-
- +
+
-
diff --git a/src/powercontext/server/templates/components/footer.html b/src/powercontext/server/templates/components/footer.html index e13af2ce1..b21c2f602 100644 --- a/src/powercontext/server/templates/components/footer.html +++ b/src/powercontext/server/templates/components/footer.html @@ -19,7 +19,7 @@
- +
diff --git a/src/powercontext/server/templates/components/recall_trend.html b/src/powercontext/server/templates/components/recall_trend.html index 851ac354d..12e8efc5e 100644 --- a/src/powercontext/server/templates/components/recall_trend.html +++ b/src/powercontext/server/templates/components/recall_trend.html @@ -17,14 +17,11 @@
-

Recall savings trend

-

Estimated token reduction over the last 30 days

-
-
-
+ +
-

Dashboard

+

Overview

- +
- Last 30 days - +
-
+
-
Estimated token reduction
+
Compared with using the original materials directly
0
-
Sources
+
Work materials
0
-
Memory entries
+
Memory
0
-
Artifacts
+
Saved content
0
-
Pending review
+
Awaiting review
0
@@ -72,16 +71,15 @@

-

Artifact families

-

Current Artifacts and pending Candidates

+

By type

- - - + + + @@ -94,5 +92,5 @@

Artifact families

{% endblock %} {% block scripts %} - + {% endblock %} diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 41c3efd1d..8b81ae31f 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -60,10 +60,6 @@ def test_dashboard_is_enabled_by_default_without_authentication_or_scopes(tmp_pa assert home.status_code == 200 assert skills.status_code == 200 assert review.status_code == 200 - assert 'class="server-content" id="skills-library"' in skills.text - assert 'class="server-content" id="review-inbox"' in review.text - assert 'data-server-session="active"' in home.text - assert 'data-server-auth-required="false"' in home.text assert scopes.status_code == 200 assert scopes.json() == [] @@ -144,50 +140,6 @@ def test_dashboard_is_the_authenticated_server_ui_entry(tmp_path) -> None: assert removed_dashboard_alias.status_code == 404 assert missing_scopes.status_code == 401 assert scopes.status_code == 200 - assert 'data-server-session="missing"' in home.text - assert 'id="auth-shell"' in home.text - assert 'id="auth-shell" hidden' not in home.text - assert 'id="page-status" hidden' in home.text - assert 'class="server-content" id="dashboard"' in home.text - assert 'id="dashboard" hidden' not in home.text - assert 'data-server-auth-required="true"' in home.text - assert 'data-i18n-aria-label="brandHomeLabel"' in home.text - assert 'data-i18n-aria-label="primaryNavigation"' in home.text - assert 'data-i18n-aria-label="scopeOverview"' in home.text - assert 'data-i18n-aria-label="activityAria"' in home.text - assert "dashboard.js?v=default-startup-locale-v1" in home.text - assert 'data-i18n="skillsTitle"' in skills.text - assert 'aria-current="page" data-i18n="skillsTitle"' in skills.text - assert 'id="skills-scope-search"' in skills.text - assert 'role="combobox"' in skills.text - assert 'aria-controls="skills-scope-options"' in skills.text - assert 'id="skills-scope-options" role="listbox"' in skills.text - assert 'id="skills-search"' in skills.text - assert 'id="skills-authority-filter"' in skills.text - assert 'id="skills-list" role="listbox"' in skills.text - assert 'id="skills-managed-content"' in skills.text - assert 'id="skills-delivery"' in skills.text - assert 'id="skills-create-revision"' in skills.text - assert 'id="skills-publish-dialog"' in skills.text - assert "skills.js?v=agent-targets-v1" in skills.text - assert 'data-i18n="reviewTitle"' in review.text - assert 'aria-current="page" data-i18n="reviewTitle"' in review.text - assert 'id="review-scope-select"' not in review.text - assert 'id="review-scope-search"' in review.text - assert 'role="combobox"' in review.text - assert 'aria-controls="review-scope-options"' in review.text - assert 'id="review-scope-options" role="listbox"' in review.text - assert 'id="review-family-filter"' in review.text - assert 'id="review-status-filter"' in review.text - assert 'id="review-list" role="listbox"' in review.text - assert 'id="review-revision-form" hidden' in review.text - assert 'id="review-approve-dialog"' in review.text - assert 'id="review-reject-dialog"' in review.text - assert 'id="review-publication"' in review.text - assert 'id="review-create-skill-revision"' in review.text - assert 'id="review-revision-title"' in review.text - assert 'id="review-publish-dialog"' in review.text - assert "review.js?v=agent-targets-v1" in review.text assert scopes.json() == [ {"scope_id": "person:psiace", "display_name": "PsiACE"}, {"scope_id": "project:powercontext", "display_name": "PowerContext"}, @@ -619,106 +571,9 @@ def test_handoff_report_page_is_available_without_the_statistics_dashboard(tmp_p assert disabled_review.status_code == 404 assert disabled_dashboard.status_code == 404 assert disabled_dashboard_scopes.status_code == 404 - assert 'data-i18n="dashboardTitle"' not in enabled_page.text - assert 'data-i18n="skillsTitle"' not in enabled_page.text - assert 'data-server-session="missing"' in enabled_page.text - assert 'id="auth-shell"' in enabled_page.text - assert 'id="auth-shell" hidden' not in enabled_page.text - assert 'id="page-status" hidden' in enabled_page.text - assert 'class="server-content" id="handoff-report"' in enabled_page.text - assert 'id="handoff-report" hidden' in enabled_page.text - assert 'data-period-mode="day"' in enabled_page.text - assert 'data-period-mode="week"' in enabled_page.text - assert 'data-period-mode="month"' in enabled_page.text - assert 'id="period-start" type="date"' in enabled_page.text - assert 'id="period-end" type="date"' in enabled_page.text - assert 'id="handoff-content-list"' in enabled_page.text - assert 'id="handoff-save-status"' in enabled_page.text - assert 'id="handoff-editor-actions"' in enabled_page.text - assert 'id="edit-handoff-content"' in enabled_page.text - assert 'id="save-handoff-revision"' in enabled_page.text - assert 'form="handoff-content-editor"' in enabled_page.text - assert 'id="cancel-handoff-edit"' in enabled_page.text - assert 'id="handoff-editor"' not in enabled_page.text - assert "data-handoff-choice=" not in enabled_page.text - assert 'id="receiver-live-state"' not in enabled_page.text - assert 'id="receiver-capability"' not in enabled_page.text - assert 'id="receiver-authorization"' not in enabled_page.text - assert 'id="continuity-timeline"' in enabled_page.text - assert 'id="continuity-timeline-toggle"' in enabled_page.text - assert 'aria-controls="continuity-timeline"' in enabled_page.text - assert 'data-i18n-aria-label="handoffSummary"' in enabled_page.text - assert 'id="auto-refresh-status"' in enabled_page.text - assert 'id="handoff-revision-history"' in enabled_page.text - assert 'id="revision-history-summary"' in enabled_page.text - assert 'id="transfer-state-status"' in enabled_page.text - assert 'id="outcome-state-status"' in enabled_page.text - assert 'id="task-outcome-form"' not in enabled_page.text - assert 'id="project-select"' not in enabled_page.text - assert 'id="project-search"' in enabled_page.text - assert 'role="combobox"' in enabled_page.text - assert 'aria-controls="project-options"' in enabled_page.text - assert 'id="project-options" role="listbox"' in enabled_page.text - assert 'id="project-search-status" role="status"' in enabled_page.text - assert 'id="workstream-list"' in enabled_page.text - assert 'id="workstream-switcher-toolbar"' in enabled_page.text - assert 'id="workstream-search"' in enabled_page.text - assert 'id="previous-workstream"' in enabled_page.text - assert 'id="workstream-position"' in enabled_page.text - assert 'id="next-workstream"' in enabled_page.text - assert 'id="workstream-filter-empty"' in enabled_page.text - assert 'id="handoff-snapshot"' in enabled_page.text - assert 'id="open-handoff-workbench"' not in enabled_page.text - assert 'id="handoff-workbench-panel"' not in enabled_page.text - assert 'id="handoff-workstream"' not in enabled_page.text - assert 'id="activity-title"' in enabled_page.text - assert 'id="activity-breakdown-list"' in enabled_page.text - assert '
' in enabled_page.text - assert '
FamilyCurrent ArtifactsPending CandidatesTypeSavedAwaiting review
diff --git a/theme/powercontext/docs-overview.html b/theme/powercontext/docs-overview.html index f0b5d980c..34f2f3472 100644 --- a/theme/powercontext/docs-overview.html +++ b/theme/powercontext/docs-overview.html @@ -22,14 +22,30 @@

{{ page.title }}

{% for section in page.meta["overview"]["sections"] %}

{{ section["title"] }}

{{ section["description"] }}

-
- {% for card in section["cards"] %} + {% set featured_cards = section["cards"] | selectattr("featured") | list %} + {% if featured_cards %} +
+ {% for card in featured_cards %} {{ card["title"] }} {{ card["description"] }} {% endfor %} -
+
+ {% endif %} + {% endfor %} {% endblock %} diff --git a/theme/powercontext/home.html b/theme/powercontext/home.html index 3fc134237..faedab751 100644 --- a/theme/powercontext/home.html +++ b/theme/powercontext/home.html @@ -51,14 +51,15 @@

{% for line in page.meta["home"]["hero"]["title"] %}{{ line -

{{ page.meta["home"]["hero"]["note"] }}

+ {% if "note" in page.meta["home"]["hero"] %} +

{{ page.meta["home"]["hero"]["note"] }}

+ {% endif %}
-

{{ page.meta["home"]["continuity"]["title"] }}

{{ page.meta["home"]["continuity"]["lead"] }}

@@ -73,7 +74,6 @@

{{ page.meta["home"]["continuity"]["title"] }}

-

{% for line in page.meta["home"]["ownership"]["title"] %}{{ line }}{% if not loop.last %}
{% endif %}{% endfor %}

@@ -81,8 +81,12 @@

{% for line in page.meta["home"]["ownership"]["title"]

{{ page.meta["home"]["ownership"]["lead"] }}

{{ page.meta["home"]["ownership"]["handoff"] }}

-

{{ page.meta["home"]["ownership"]["result"] }}

-
$ {{ page.meta["home"]["ownership"]["command"] }}
+ {% if "result" in page.meta["home"]["ownership"] %} +

{{ page.meta["home"]["ownership"]["result"] }}

+ {% endif %} + {% if "command" in page.meta["home"]["ownership"] %} +
$ {{ page.meta["home"]["ownership"]["command"] }}
+ {% endif %}
{{ page.meta["home"]["ownership"]["primary_action"]["label"] }} {{ page.meta["home"]["ownership"]["secondary_action"]["label"] }} diff --git a/theme/powercontext/partials/footer.html b/theme/powercontext/partials/footer.html index b97603b15..52091b8a8 100644 --- a/theme/powercontext/partials/footer.html +++ b/theme/powercontext/partials/footer.html @@ -70,13 +70,15 @@
diff --git a/zensical.toml b/zensical.toml index 529538266..aa57d22db 100644 --- a/zensical.toml +++ b/zensical.toml @@ -101,20 +101,20 @@ nav = [ { "文档" = [ { "开始使用" = [ { "概览" = "zh/docs/index.md" }, - { "HTTP API 生命周期教程" = "zh/docs/tutorials/api-quickstart.md" }, - { "Agent 分步入门" = "zh/docs/tutorials/agent-quickstart.md" }, + { "通过 HTTP API 开始使用" = "zh/docs/tutorials/api-quickstart.md" }, + { "智能体分步入门" = "zh/docs/tutorials/agent-quickstart.md" }, { "Codex 完整教程" = "zh/docs/tutorials/codex-quickstart.md" }, { "安装和运行" = "zh/docs/how-to/install-and-run.md" }, - { "完整功能 Quick Start" = "zh/docs/how-to/full-capability-runtime.md" }, - { "部署 Server" = "zh/docs/how-to/deploy-server.md" }, + { "完整功能入门" = "zh/docs/how-to/full-capability-runtime.md" }, + { "部署服务" = "zh/docs/how-to/deploy-server.md" }, { "核心概念" = "zh/docs/explanation/core-concepts.md" }, - { "理解 Memory 和 Handoff" = "zh/docs/explanation/memory-and-handoff.md" }, - { "Experience 与 Skill 生命周期" = "zh/docs/explanation/experience-and-skill-lifecycle.md" }, + { "记忆与交接" = "zh/docs/explanation/memory-and-handoff.md" }, + { "经验与技能生命周期" = "zh/docs/explanation/experience-and-skill-lifecycle.md" }, { "在 Codex 中交接工作" = "zh/docs/how-to/handoff-with-codex.md" }, { "使用 Handoff Report" = "zh/docs/how-to/use-handoff-report.md" }, - { "审核 Candidate" = "zh/docs/how-to/review-candidates.md" }, - { "创建并审核 Experience" = "zh/docs/how-to/create-and-review-experience.md" }, - { "创建并导出 managed Skill" = "zh/docs/how-to/create-and-export-skill.md" }, + { "处理待审核内容" = "zh/docs/how-to/review-candidates.md" }, + { "创建并审核经验" = "zh/docs/how-to/create-and-review-experience.md" }, + { "创建并导出技能" = "zh/docs/how-to/create-and-export-skill.md" }, { "排查问题" = "zh/docs/how-to/troubleshoot.md" }, ] }, { "集成方式" = [ @@ -123,14 +123,14 @@ nav = [ { "Pydantic AI 适配器预览" = "zh/docs/how-to/configure-pydantic-ai.md" }, { "配置 DeepSeek Harness" = "zh/docs/how-to/configure-dsh.md" }, { "配置 Hermes" = "zh/docs/how-to/configure-hermes.md" }, - { "配置 LangChain middleware" = "zh/docs/how-to/configure-langchain.md" }, + { "配置 LangChain 中间件" = "zh/docs/how-to/configure-langchain.md" }, { "配置 LangGraph" = "zh/docs/how-to/configure-langgraph.md" }, { "配置 Pi" = "zh/docs/how-to/configure-pi.md" }, { "配置 WorkBuddy" = "zh/docs/how-to/configure-workbuddy.md" }, { "配置 OpenClaw" = "zh/docs/how-to/configure-openclaw.md" }, { "配置 OpenCode" = "zh/docs/how-to/configure-opencode.md" }, - { "配置 Agent Plugin" = "zh/docs/how-to/configure-agent-plugin.md" }, - { "用 Phoenix 查看 trace" = "zh/docs/how-to/trace-with-phoenix.md" }, + { "配置智能体插件" = "zh/docs/how-to/configure-agent-plugin.md" }, + { "用 Phoenix 查看调用记录" = "zh/docs/how-to/trace-with-phoenix.md" }, ] }, { "参考" = [ { "接口" = "zh/docs/reference/interfaces.md" }, @@ -139,7 +139,7 @@ nav = [ { "HTTP API 参考" = "api/index.html" }, ] }, ] }, - { "评估测试" = "zh/benchmarks/index.md" }, + { "评测" = "zh/benchmarks/index.md" }, { "开发" = [ { "Core Protocol" = "zh/development/core-protocol.md" }, { "Memory Layer" = "zh/development/memory-layer.md" },

{{ benchmark["reading"]["columns"]["dimension"] }}LoCoMoSWE-bench Pro