feat: prepare GoodBuddy 0.8.0
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
# GoodBuddy 静态官网
|
||||
|
||||
`sites` 是无需构建步骤或额外依赖的静态官网源码,可直接托管整个目录。
|
||||
|
||||
## 本地预览
|
||||
|
||||
在仓库根目录运行:
|
||||
|
||||
```powershell
|
||||
python -m http.server 4173 --bind 127.0.0.1 --directory sites
|
||||
```
|
||||
|
||||
然后访问 <http://localhost:4173/>。也可以直接用浏览器打开 `sites/index.html`。
|
||||
|
||||
## 校验
|
||||
|
||||
```powershell
|
||||
node sites/scripts/validate.mjs
|
||||
node --check sites/app.js
|
||||
node --check sites/site.config.js
|
||||
```
|
||||
|
||||
校验脚本会检查必需文件、页内链接、本地资源、关键产品文案、主题与响应式规则,以及未发布状态下的下载链接保护。
|
||||
|
||||
## Release 配置
|
||||
|
||||
未来 v0.8.0 Release 地址集中在 `site.config.js`:
|
||||
|
||||
```js
|
||||
window.GOODBUDDY_SITE_CONFIG = Object.freeze({
|
||||
version: "0.8.0",
|
||||
releasePublished: false,
|
||||
releaseUrl: "https://github.com/mesalogo/goodbuddy/releases/tag/v0.8.0",
|
||||
});
|
||||
```
|
||||
|
||||
正式 Release 确认发布后,将 `releasePublished` 改为 `true`,页面上的下载入口才会指向 Release 页面。官网不配置或猜测具体安装资产名称。
|
||||
|
||||
## 文件
|
||||
|
||||
- `index.html`:页面结构与简体中文内容
|
||||
- `styles.css`:语义令牌、浅深主题、焦点与响应式布局
|
||||
- `app.js`:主题、移动导航、当前章节和 Release 状态
|
||||
- `site.config.js`:版本与未来 Release 地址
|
||||
- `assets/favicon.svg`:站点图标
|
||||
- `scripts/validate.mjs`:无依赖静态检查
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
(() => {
|
||||
"use strict";
|
||||
|
||||
const root = document.documentElement;
|
||||
const header = document.querySelector("[data-site-header]");
|
||||
const menuToggle = document.querySelector("[data-menu-toggle]");
|
||||
const navigation = document.querySelector("[data-navigation]");
|
||||
const themeToggle = document.querySelector("[data-theme-toggle]");
|
||||
const themeColor = document.querySelector('meta[name="theme-color"]');
|
||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const config = window.GOODBUDDY_SITE_CONFIG;
|
||||
|
||||
const getSavedTheme = () => {
|
||||
try {
|
||||
const savedTheme = localStorage.getItem("goodbuddy-site-theme");
|
||||
return savedTheme === "light" || savedTheme === "dark" ? savedTheme : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const applyTheme = (theme, persist = false) => {
|
||||
root.dataset.theme = theme;
|
||||
themeToggle?.setAttribute(
|
||||
"aria-label",
|
||||
theme === "dark" ? "切换为浅色主题" : "切换为深色主题",
|
||||
);
|
||||
themeColor?.setAttribute("content", theme === "dark" ? "#07101f" : "#f6f8fb");
|
||||
|
||||
if (persist) {
|
||||
try {
|
||||
localStorage.setItem("goodbuddy-site-theme", theme);
|
||||
} catch {
|
||||
// The selected theme still applies for the current page.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const closeMenu = () => {
|
||||
header?.classList.remove("is-menu-open");
|
||||
menuToggle?.setAttribute("aria-expanded", "false");
|
||||
menuToggle?.setAttribute("aria-label", "打开导航");
|
||||
};
|
||||
|
||||
const setHeaderState = () => {
|
||||
header?.classList.toggle("is-scrolled", window.scrollY > 12);
|
||||
};
|
||||
|
||||
const configureReleaseLinks = () => {
|
||||
const releaseLinks = document.querySelectorAll("[data-release-link]");
|
||||
const isReady =
|
||||
config?.releasePublished === true &&
|
||||
typeof config.releaseUrl === "string" &&
|
||||
/^https:\/\/github\.com\/mesalogo\/goodbuddy\/releases\/tag\/v0\.8\.0$/.test(
|
||||
config.releaseUrl,
|
||||
);
|
||||
|
||||
releaseLinks.forEach((link) => {
|
||||
if (!isReady) {
|
||||
link.removeAttribute("href");
|
||||
link.removeAttribute("target");
|
||||
link.removeAttribute("rel");
|
||||
link.setAttribute("aria-disabled", "true");
|
||||
link.classList.add("is-disabled");
|
||||
link.textContent = "发布后开放";
|
||||
return;
|
||||
}
|
||||
|
||||
link.href = config.releaseUrl;
|
||||
link.target = "_blank";
|
||||
link.rel = "noreferrer";
|
||||
link.removeAttribute("aria-disabled");
|
||||
link.classList.remove("is-disabled");
|
||||
link.innerHTML = `前往 v${config.version} Release<span class="sr-only">(在新窗口打开)</span>`;
|
||||
});
|
||||
};
|
||||
|
||||
applyTheme(getSavedTheme() ?? (systemTheme.matches ? "dark" : "light"));
|
||||
configureReleaseLinks();
|
||||
setHeaderState();
|
||||
|
||||
themeToggle?.addEventListener("click", () => {
|
||||
applyTheme(root.dataset.theme === "dark" ? "light" : "dark", true);
|
||||
});
|
||||
|
||||
systemTheme.addEventListener("change", (event) => {
|
||||
if (!getSavedTheme()) {
|
||||
applyTheme(event.matches ? "dark" : "light");
|
||||
}
|
||||
});
|
||||
|
||||
menuToggle?.addEventListener("click", () => {
|
||||
const willOpen = !header?.classList.contains("is-menu-open");
|
||||
header?.classList.toggle("is-menu-open", willOpen);
|
||||
menuToggle.setAttribute("aria-expanded", String(willOpen));
|
||||
menuToggle.setAttribute("aria-label", willOpen ? "关闭导航" : "打开导航");
|
||||
});
|
||||
|
||||
navigation?.addEventListener("click", (event) => {
|
||||
if (event.target instanceof HTMLAnchorElement) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape" && header?.classList.contains("is-menu-open")) {
|
||||
closeMenu();
|
||||
menuToggle?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
if (
|
||||
header?.classList.contains("is-menu-open") &&
|
||||
event.target instanceof Node &&
|
||||
!header.contains(event.target)
|
||||
) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("scroll", setHeaderState, { passive: true });
|
||||
|
||||
const sections = [...document.querySelectorAll("main section[id]")];
|
||||
const navLinks = [...document.querySelectorAll('.site-navigation a[href^="#"]')];
|
||||
|
||||
if ("IntersectionObserver" in window) {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const visibleSection = entries
|
||||
.filter((entry) => entry.isIntersecting)
|
||||
.sort((left, right) => right.intersectionRatio - left.intersectionRatio)[0];
|
||||
|
||||
if (!visibleSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
navLinks.forEach((link) => {
|
||||
const isCurrent = link.getAttribute("href") === `#${visibleSection.target.id}`;
|
||||
if (isCurrent) {
|
||||
link.setAttribute("aria-current", "true");
|
||||
} else {
|
||||
link.removeAttribute("aria-current");
|
||||
}
|
||||
});
|
||||
},
|
||||
{ rootMargin: "-25% 0px -55%", threshold: [0.05, 0.2, 0.5] },
|
||||
);
|
||||
|
||||
sections.forEach((section) => observer.observe(section));
|
||||
}
|
||||
|
||||
const currentYear = document.querySelector("[data-current-year]");
|
||||
if (currentYear) {
|
||||
currentYear.textContent = String(new Date().getFullYear());
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="8" y1="8" x2="56" y2="56" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0877e8"/>
|
||||
<stop offset="1" stop-color="#08b89b"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="64" height="64" rx="16" fill="#fff"/>
|
||||
<path d="M9 34a14 14 0 1 1 28 0v12H23A14 14 0 0 1 9 34Z" fill="none" stroke="url(#g)" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27 34a14 14 0 1 1 28 0 14 14 0 0 1-28 0Z" fill="none" stroke="url(#g)" stroke-width="7"/>
|
||||
<path d="M32 20v-7M41 17l5-5M23 17l-5-5" fill="none" stroke="url(#g)" stroke-width="4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 702 B |
@@ -0,0 +1,538 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta
|
||||
name="description"
|
||||
content="GoodBuddy 是安全可控的桌面智能助手与 Agent 工作空间。0.8.0 即将带来 Subagent、智能路由与 IM 开发者预览。"
|
||||
/>
|
||||
<meta name="theme-color" content="#f6f8fb" />
|
||||
<title>GoodBuddy|安全可控的桌面智能助手</title>
|
||||
<link rel="icon" href="./assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
<script>
|
||||
(() => {
|
||||
try {
|
||||
const savedTheme = localStorage.getItem("goodbuddy-site-theme");
|
||||
const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
document.documentElement.dataset.theme =
|
||||
savedTheme === "light" || savedTheme === "dark"
|
||||
? savedTheme
|
||||
: systemDark
|
||||
? "dark"
|
||||
: "light";
|
||||
} catch {
|
||||
document.documentElement.dataset.theme = "light";
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main-content">跳到主要内容</a>
|
||||
|
||||
<header class="site-header" data-site-header>
|
||||
<div class="header-inner">
|
||||
<a class="brand" href="#home" aria-label="GoodBuddy 首页">
|
||||
<svg class="brand-mark" viewBox="0 0 40 40" aria-hidden="true">
|
||||
<path d="M6 21a9 9 0 1 1 18 0v8H15a9 9 0 0 1-9-8Z" />
|
||||
<path d="M16 21a9 9 0 1 1 18 0 9 9 0 0 1-18 0Z" />
|
||||
<path d="M20 13V8M25 10l3-3M15 10l-3-3" />
|
||||
</svg>
|
||||
<span>GoodBuddy</span>
|
||||
</a>
|
||||
|
||||
<button
|
||||
class="icon-button menu-toggle"
|
||||
type="button"
|
||||
aria-label="打开导航"
|
||||
aria-expanded="false"
|
||||
aria-controls="site-navigation"
|
||||
data-menu-toggle
|
||||
>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M4 7h16M4 12h16M4 17h16" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<nav class="site-navigation" id="site-navigation" aria-label="主导航" data-navigation>
|
||||
<a href="#features">功能</a>
|
||||
<a href="#release">0.8.0</a>
|
||||
<a href="#download">下载</a>
|
||||
<a href="#security">安全</a>
|
||||
</nav>
|
||||
|
||||
<div class="header-actions">
|
||||
<button class="icon-button" type="button" aria-label="切换为深色主题" data-theme-toggle>
|
||||
<svg class="theme-icon theme-icon--sun" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
|
||||
</svg>
|
||||
<svg class="theme-icon theme-icon--moon" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M20.4 14.6A8.5 8.5 0 0 1 9.4 3.6a8.5 8.5 0 1 0 11 11Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<a
|
||||
class="button button--quiet header-github"
|
||||
href="https://github.com/mesalogo/goodbuddy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
GitHub
|
||||
<span class="sr-only">(在新窗口打开)</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="main-content">
|
||||
<section class="hero section" id="home" aria-labelledby="hero-title">
|
||||
<div class="section-inner hero-grid">
|
||||
<div class="hero-copy">
|
||||
<div class="eyebrow">
|
||||
<span class="status-dot" aria-hidden="true"></span>
|
||||
GoodBuddy 0.8.0 即将发布
|
||||
</div>
|
||||
<h1 id="hero-title">把 AI 放在桌面,<br /><span>也把控制权留在手中。</span></h1>
|
||||
<p class="hero-lead">
|
||||
GoodBuddy 是安全可控的桌面智能助手与 Agent 工作空间。连接模型、知识与工具,
|
||||
在清晰的范围和审批边界内完成真正的工作。
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<a class="button button--primary" href="#release">查看 0.8.0 亮点</a>
|
||||
<a
|
||||
class="button button--secondary is-disabled"
|
||||
aria-disabled="true"
|
||||
data-release-link
|
||||
>发布后开放</a>
|
||||
</div>
|
||||
<ul class="hero-facts" aria-label="产品特性概览">
|
||||
<li>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m5 10 3 3 7-7" /></svg>
|
||||
Windows / macOS / Linux
|
||||
</li>
|
||||
<li>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m5 10 3 3 7-7" /></svg>
|
||||
项目范围隔离
|
||||
</li>
|
||||
<li>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m5 10 3 3 7-7" /></svg>
|
||||
工具调用可审批
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="product-stage"
|
||||
role="img"
|
||||
aria-label="GoodBuddy 桌面应用界面示意:在项目范围内对话、引用知识并审批工具调用"
|
||||
>
|
||||
<div class="stage-glow stage-glow--one"></div>
|
||||
<div class="stage-glow stage-glow--two"></div>
|
||||
<div class="app-window">
|
||||
<div class="window-bar">
|
||||
<div class="window-dots" aria-hidden="true"><span></span><span></span><span></span></div>
|
||||
<div class="window-title">GoodBuddy</div>
|
||||
<div class="window-status"><span></span> 本地工作区</div>
|
||||
</div>
|
||||
<div class="app-layout">
|
||||
<aside class="app-sidebar" aria-hidden="true">
|
||||
<div class="mini-brand">
|
||||
<svg viewBox="0 0 40 40">
|
||||
<path d="M6 21a9 9 0 1 1 18 0v8H15a9 9 0 0 1-9-8Z" />
|
||||
<path d="M16 21a9 9 0 1 1 18 0 9 9 0 0 1-18 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="side-item is-active"><span></span>对话</div>
|
||||
<div class="side-item"><span></span>知识库</div>
|
||||
<div class="side-item"><span></span>智能心跳</div>
|
||||
<div class="side-item"><span></span>任务与活动</div>
|
||||
<div class="sidebar-spacer"></div>
|
||||
<div class="side-item"><span></span>设置</div>
|
||||
</aside>
|
||||
<div class="app-content">
|
||||
<div class="app-content-header">
|
||||
<div>
|
||||
<strong>产品发布准备</strong>
|
||||
<span>项目:GoodBuddy 0.8.0</span>
|
||||
</div>
|
||||
<div class="mode-pill">计划模式</div>
|
||||
</div>
|
||||
<div class="message-area">
|
||||
<div class="message message--user">梳理 0.8.0 发布前还需要完成的工作。</div>
|
||||
<div class="message message--assistant">
|
||||
<div class="assistant-label">
|
||||
<span class="assistant-avatar">G</span>
|
||||
<strong>GoodBuddy</strong>
|
||||
</div>
|
||||
<p>我会先核对发布清单与项目知识,再给出不执行变更的计划。</p>
|
||||
<div class="tool-card">
|
||||
<div class="tool-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h16M4 12h10M4 18h7" /></svg>
|
||||
</div>
|
||||
<div><strong>读取项目知识</strong><span>范围:GoodBuddy 0.8.0</span></div>
|
||||
<span class="tool-state">已完成</span>
|
||||
</div>
|
||||
<div class="plan-lines" aria-hidden="true"><span></span><span></span><span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="composer">
|
||||
<span>继续补充要求…</span>
|
||||
<div class="composer-actions"><span>计划</span><b>↑</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="floating-card floating-card--approval">
|
||||
<span class="floating-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 5 6v5c0 4.5 2.8 8.6 7 10 4.2-1.4 7-5.5 7-10V6l-7-3Z" /><path d="m9 12 2 2 4-4" /></svg>
|
||||
</span>
|
||||
<span><strong>执行前确认</strong><small>每次工具调用都清晰可见</small></span>
|
||||
</div>
|
||||
<div class="floating-card floating-card--scope">
|
||||
<span class="scope-dot"></span>
|
||||
<span><strong>项目范围</strong><small>上下文不会悄悄混用</small></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="proof-strip" aria-label="核心设计原则">
|
||||
<div class="section-inner proof-grid">
|
||||
<div><strong>3 种</strong><span>问答 / 计划 / 执行模式</span></div>
|
||||
<div><strong>2 层</strong><span>全局与项目知识范围</span></div>
|
||||
<div><strong>明确</strong><span>工具权限与活动记录</span></div>
|
||||
<div><strong>跨平台</strong><span>x64 与 arm64</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section features-section" id="features" aria-labelledby="features-title">
|
||||
<div class="section-inner">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<p class="kicker">围绕真实工作流设计</p>
|
||||
<h2 id="features-title">不是另一个聊天窗口</h2>
|
||||
</div>
|
||||
<p>
|
||||
从上下文组织到执行审批,每一步都让范围、状态和风险保持可见。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-grid">
|
||||
<article class="feature-card feature-card--wide">
|
||||
<div class="feature-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M12 3 5 6v5c0 4.5 2.8 8.6 7 10 4.2-1.4 7-5.5 7-10V6l-7-3Z" />
|
||||
<path d="M9 12h6M12 9v6" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="feature-number">01</span>
|
||||
<h3>受控 Agent 运行时</h3>
|
||||
<p>问答与计划模式在运行时保持只读;执行模式中的工具操作经过现有审批控制,并保留取消、超时与输出边界。</p>
|
||||
<div class="mode-row" aria-label="三种工作模式">
|
||||
<span>问答 <small>只读</small></span>
|
||||
<span>计划 <small>只读</small></span>
|
||||
<span class="is-accent">执行 <small>需审批</small></span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M4 6.5C4 5.1 5.1 4 6.5 4H10l2 2h5.5C18.9 6 20 7.1 20 8.5v9c0 1.4-1.1 2.5-2.5 2.5h-11A2.5 2.5 0 0 1 4 17.5v-11Z" />
|
||||
<path d="M8 11h8M8 15h5" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="feature-number">02</span>
|
||||
<h3>有范围的知识</h3>
|
||||
<p>区分全局与项目知识。搜索、引用和创建都围绕当前范围展开,让上下文来源清楚可追溯。</p>
|
||||
</article>
|
||||
|
||||
<article class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M4 13h3l2-6 4 12 2-6h5" />
|
||||
<path d="M4 4h16v16H4z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="feature-number">03</span>
|
||||
<h3>智能心跳与任务</h3>
|
||||
<p>将周期计划、运行状态、结果与活动记录放在同一条可检查的工作链路中。</p>
|
||||
</article>
|
||||
|
||||
<article class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M7 13.5 13.5 7a3.2 3.2 0 0 1 4.5 4.5l-8 8a5 5 0 1 1-7-7l8-8" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="feature-number">04</span>
|
||||
<h3>文档与图像输入</h3>
|
||||
<p>单次最多添加 8 个附件,支持同时传入 5 张图片;在一个会话中汇集任务所需材料。</p>
|
||||
</article>
|
||||
|
||||
<article class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M4 5h16v14H4z" />
|
||||
<path d="m4 16 5-5 3 3 2-2 6 6" />
|
||||
<circle cx="15.5" cy="8.5" r="1.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="feature-number">05</span>
|
||||
<h3>可控的图像生成</h3>
|
||||
<p>生图质量支持 auto、low、medium、high 四档。结果以单张图像呈现,并作为本地工件保存。</p>
|
||||
</article>
|
||||
|
||||
<article class="feature-card feature-card--wide feature-card--accent">
|
||||
<div class="feature-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="feature-number">06</span>
|
||||
<h3>模型与工具,由你连接</h3>
|
||||
<p>在桌面端管理模型配置、MCP 工具与运行时。密钥留在主进程的加密设置存储中,不交给网页渲染层。</p>
|
||||
<div class="provider-pills" aria-label="支持的连接类型">
|
||||
<span>模型提供商</span><span>MCP</span><span>OpenCode</span><span>Continue</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section release-section" id="release" aria-labelledby="release-title">
|
||||
<div class="section-inner">
|
||||
<div class="release-heading">
|
||||
<div class="version-lockup" aria-hidden="true">
|
||||
<span>VERSION</span>
|
||||
<strong>0.8.0</strong>
|
||||
</div>
|
||||
<div>
|
||||
<p class="kicker">下一站</p>
|
||||
<h2 id="release-title">0.8.0 更新亮点</h2>
|
||||
<p>更聪明地组织工作,也更诚实地标注能力边界。以下功能状态以正式 Release 说明为准。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ol class="release-list">
|
||||
<li class="release-item">
|
||||
<div class="release-index">01</div>
|
||||
<div class="release-copy">
|
||||
<div class="release-label">0.8.0</div>
|
||||
<h3>Subagent 与智能路由</h3>
|
||||
<p>面向复杂任务的协作与路由能力归入 0.8.0,不将仍在开发中的路径描述为当前稳定能力。</p>
|
||||
</div>
|
||||
<div class="release-visual route-visual" aria-hidden="true">
|
||||
<span class="route-node route-node--main">主任务</span>
|
||||
<span class="route-line route-line--one"></span>
|
||||
<span class="route-line route-line--two"></span>
|
||||
<span class="route-node route-node--sub-one">研究</span>
|
||||
<span class="route-node route-node--sub-two">验证</span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="release-item">
|
||||
<div class="release-index">02</div>
|
||||
<div class="release-copy">
|
||||
<div class="release-label release-label--preview">开发者预览</div>
|
||||
<h3>IM 渠道接入</h3>
|
||||
<p>钉钉与企业微信以开发者预览提供;个人微信处于实验性边界,不作为面向生产环境的稳定承诺。</p>
|
||||
</div>
|
||||
<div class="release-visual channel-visual" aria-label="渠道状态">
|
||||
<span><b>钉钉</b><small>开发者预览</small></span>
|
||||
<span><b>企业微信</b><small>开发者预览</small></span>
|
||||
<span class="is-experimental"><b>个人微信</b><small>实验性边界</small></span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="release-item">
|
||||
<div class="release-index">03</div>
|
||||
<div class="release-copy">
|
||||
<div class="release-label">多模态输入</div>
|
||||
<h3>更多材料,一次带上</h3>
|
||||
<p>单次最多 8 个附件,并已验证同时传入 5 张图片。限制保持可见,避免把超出边界的输入静默带入任务。</p>
|
||||
</div>
|
||||
<div class="release-visual attachment-visual" aria-hidden="true">
|
||||
<div class="attachment-stack"><span></span><span></span><span></span></div>
|
||||
<div><strong>8</strong><small>附件上限</small></div>
|
||||
<div><strong>5</strong><small>图片上限</small></div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="release-item">
|
||||
<div class="release-index">04</div>
|
||||
<div class="release-copy">
|
||||
<div class="release-label">图像生成</div>
|
||||
<h3>清晰选择质量档位</h3>
|
||||
<p>支持 auto、low、medium、high 四档质量。当前按单张结果呈现,不承诺批量多图生成。</p>
|
||||
</div>
|
||||
<div class="release-visual quality-visual" aria-label="图像质量档位">
|
||||
<span>auto</span><span>low</span><span>medium</span><span class="is-selected">high</span>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section download-section" id="download" aria-labelledby="download-title">
|
||||
<div class="section-inner">
|
||||
<div class="section-heading section-heading--center">
|
||||
<div>
|
||||
<p class="kicker">原生桌面体验</p>
|
||||
<h2 id="download-title">准备好,在你的设备上运行</h2>
|
||||
</div>
|
||||
<p>
|
||||
v0.8.0 Release 尚未发布。下载入口将在发布后统一开放,目前不提供虚构的资产名称或下载地址。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="download-grid">
|
||||
<article class="download-card">
|
||||
<div class="platform-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="m3 5 8-1v8H3V5Zm10-1.3L21 3v9h-8V3.7ZM3 14h8v8l-8-1v-7Zm10 0h8v9l-8-1v-8Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div><h3>Windows</h3><p>x64 / arm64 · NSIS / 便携版</p></div>
|
||||
<a class="button button--download is-disabled" aria-disabled="true" data-release-link>发布后开放</a>
|
||||
</article>
|
||||
<article class="download-card">
|
||||
<div class="platform-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M16.8 12.7c0-2.7 2.2-4 2.3-4.1A5 5 0 0 0 15.2 6c-1.7-.2-3.2 1-4.1 1-.9 0-2.2-1-3.6-1-1.8 0-3.5 1.1-4.5 2.7-2 3.5-.5 8.7 1.4 11.5.9 1.4 2 2.8 3.5 2.7 1.4 0 1.9-.9 3.7-.9 1.7 0 2.2.9 3.7.9s2.5-1.4 3.4-2.7a10 10 0 0 0 1.6-3.3 4.6 4.6 0 0 1-3.5-4.2ZM14.1 4.3A4.7 4.7 0 0 0 15.2 1a4.8 4.8 0 0 0-3.1 1.6A4.4 4.4 0 0 0 11 5.8c1.2.1 2.3-.5 3.1-1.5Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div><h3>macOS</h3><p>x64 / arm64 · DMG / ZIP</p></div>
|
||||
<a class="button button--download is-disabled" aria-disabled="true" data-release-link>发布后开放</a>
|
||||
</article>
|
||||
<article class="download-card">
|
||||
<div class="platform-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M12 3c-3 0-4.7 2.5-4.5 5.4-1.3 1.4-2 3.4-2 5.6 0 3.9 2.9 7 6.5 7s6.5-3.1 6.5-7c0-2.2-.7-4.2-2-5.6C16.7 5.5 15 3 12 3Z" />
|
||||
<path d="M9.3 10.2h.1M14.6 10.2h.1M9.5 15c1.6 1.2 3.4 1.2 5 0M7 19l-2 2M17 19l2 2" />
|
||||
</svg>
|
||||
</div>
|
||||
<div><h3>Linux</h3><p>x64 / arm64 · AppImage / DEB</p></div>
|
||||
<a class="button button--download is-disabled" aria-disabled="true" data-release-link>发布后开放</a>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="release-notice" role="status">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="9" /><path d="M12 11v5M12 8h.01" />
|
||||
</svg>
|
||||
<div>
|
||||
<strong>Release 状态:尚未发布</strong>
|
||||
<span>本站下载按钮由单一配置控制;正式发布前不会指向占位资产。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section security-section" id="security" aria-labelledby="security-title">
|
||||
<div class="section-inner security-grid">
|
||||
<div class="security-intro">
|
||||
<div class="security-shield" aria-hidden="true">
|
||||
<svg viewBox="0 0 48 48">
|
||||
<path d="M24 5 9 11v11c0 9.7 6 18.2 15 21 9-2.8 15-11.3 15-21V11L24 5Z" />
|
||||
<path d="m17.5 24 4.5 4.5 9-10" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="kicker">Security by boundary</p>
|
||||
<h2 id="security-title">安全不是开关,<br />而是每一层的边界</h2>
|
||||
<p>
|
||||
GoodBuddy 将桌面渲染、密钥、工具运行与用户数据分层处理。
|
||||
风险操作保持可见,未受信运行时不会绕过审批边界。
|
||||
</p>
|
||||
<a
|
||||
class="text-link"
|
||||
href="https://github.com/mesalogo/goodbuddy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
在 GitHub 查看项目
|
||||
<span aria-hidden="true">↗</span>
|
||||
<span class="sr-only">(在新窗口打开)</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="security-list">
|
||||
<article>
|
||||
<span class="security-number">01</span>
|
||||
<div><h3>密钥不进入渲染层</h3><p>API 密钥留在主进程,并写入加密设置存储;网页界面不获得直接 Node 访问。</p></div>
|
||||
</article>
|
||||
<article>
|
||||
<span class="security-number">02</span>
|
||||
<div><h3>跨进程能力明确暴露</h3><p>通过窄化的预加载桥接调用能力,IPC 输入经过共享模式校验,并核验可信发送方。</p></div>
|
||||
</article>
|
||||
<article>
|
||||
<span class="security-number">03</span>
|
||||
<div><h3>运行时按不可信处理</h3><p>OpenCode 与 Continue 子运行时受环境白名单、沙箱检查及逐工具审批约束。</p></div>
|
||||
</article>
|
||||
<article>
|
||||
<span class="security-number">04</span>
|
||||
<div><h3>状态与审计语义可见</h3><p>取消、超时、输出边界和活动记录属于执行链路的一部分,不用模糊的“已完成”掩盖风险。</p></div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section final-cta" aria-labelledby="cta-title">
|
||||
<div class="section-inner">
|
||||
<div class="cta-card">
|
||||
<div class="cta-orbit" aria-hidden="true"><span></span><span></span></div>
|
||||
<div>
|
||||
<p class="kicker">GoodBuddy 0.8.0</p>
|
||||
<h2 id="cta-title">一个更能做事,也更懂边界的桌面伙伴。</h2>
|
||||
<p>关注 Release,第一时间获取正式版本、校验信息与完整更新说明。</p>
|
||||
</div>
|
||||
<div class="cta-actions">
|
||||
<a
|
||||
class="button button--primary is-disabled"
|
||||
aria-disabled="true"
|
||||
data-release-link
|
||||
>发布后开放</a>
|
||||
<a
|
||||
class="button button--secondary"
|
||||
href="https://github.com/mesalogo/goodbuddy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
查看 GitHub
|
||||
<span class="sr-only">(在新窗口打开)</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="section-inner footer-inner">
|
||||
<a class="brand brand--footer" href="#home" aria-label="返回 GoodBuddy 首页">
|
||||
<svg class="brand-mark" viewBox="0 0 40 40" aria-hidden="true">
|
||||
<path d="M6 21a9 9 0 1 1 18 0v8H15a9 9 0 0 1-9-8Z" />
|
||||
<path d="M16 21a9 9 0 1 1 18 0 9 9 0 0 1-18 0Z" />
|
||||
<path d="M20 13V8M25 10l3-3M15 10l-3-3" />
|
||||
</svg>
|
||||
<span>GoodBuddy</span>
|
||||
</a>
|
||||
<p>安全可控的桌面智能助手与 Agent 工作空间。</p>
|
||||
<div class="footer-links">
|
||||
<a href="#features">功能</a>
|
||||
<a href="#release">0.8.0</a>
|
||||
<a href="#security">安全</a>
|
||||
<a href="https://github.com/mesalogo/goodbuddy" target="_blank" rel="noreferrer">
|
||||
GitHub<span class="sr-only">(在新窗口打开)</span>
|
||||
</a>
|
||||
</div>
|
||||
<small>© <span data-current-year></span> GoodBuddy. 本站不使用第三方统计脚本。</small>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="./site.config.js"></script>
|
||||
<script src="./app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,165 @@
|
||||
import { readFile, stat } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const siteRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const errors = [];
|
||||
|
||||
const requiredFiles = [
|
||||
"index.html",
|
||||
"styles.css",
|
||||
"app.js",
|
||||
"site.config.js",
|
||||
"assets/favicon.svg",
|
||||
"README.md",
|
||||
];
|
||||
|
||||
const report = (condition, message) => {
|
||||
if (!condition) {
|
||||
errors.push(message);
|
||||
}
|
||||
};
|
||||
|
||||
const readSiteFile = async (relativePath) => {
|
||||
try {
|
||||
return await readFile(path.join(siteRoot, relativePath), "utf8");
|
||||
} catch {
|
||||
errors.push(`缺少文件:${relativePath}`);
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
await Promise.all(
|
||||
requiredFiles.map(async (relativePath) => {
|
||||
try {
|
||||
const fileStats = await stat(path.join(siteRoot, relativePath));
|
||||
report(fileStats.isFile(), `不是普通文件:${relativePath}`);
|
||||
} catch {
|
||||
errors.push(`缺少文件:${relativePath}`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const [html, css, appJs, configJs] = await Promise.all([
|
||||
readSiteFile("index.html"),
|
||||
readSiteFile("styles.css"),
|
||||
readSiteFile("app.js"),
|
||||
readSiteFile("site.config.js"),
|
||||
]);
|
||||
|
||||
for (const [relativePath, content] of [
|
||||
["index.html", html],
|
||||
["styles.css", css],
|
||||
["app.js", appJs],
|
||||
["site.config.js", configJs],
|
||||
]) {
|
||||
report(!/[ \t]+$/m.test(content), `${relativePath} 包含行尾空白`);
|
||||
report(!content.includes("\t"), `${relativePath} 包含 Tab 缩进`);
|
||||
}
|
||||
|
||||
report(/<html\s+lang="zh-CN">/.test(html), "页面语言必须是 zh-CN");
|
||||
report(/<meta\s+name="viewport"/.test(html), "缺少 viewport 元信息");
|
||||
report((html.match(/<h1[\s>]/g) ?? []).length === 1, "页面必须且只能包含一个 h1");
|
||||
report(/class="skip-link"\s+href="#main-content"/.test(html), "缺少跳到主要内容链接");
|
||||
report(/<main\s+id="main-content">/.test(html), "缺少 main-content 主区域");
|
||||
report(/aria-label="主导航"/.test(html), "主导航缺少可访问名称");
|
||||
report(/data-theme-toggle/.test(html), "缺少主题切换控件");
|
||||
report(/prefers-reduced-motion:\s*reduce/.test(css), "缺少减少动态效果规则");
|
||||
report(/\[data-theme="dark"\]/.test(css), "缺少深色主题令牌");
|
||||
|
||||
for (const breakpoint of ["1199px", "959px", "719px"]) {
|
||||
report(css.includes(`max-width: ${breakpoint}`), `缺少 ${breakpoint} 响应式断点`);
|
||||
}
|
||||
|
||||
const requiredCopy = [
|
||||
"Subagent 与智能路由",
|
||||
"钉钉与企业微信以开发者预览提供",
|
||||
"个人微信处于实验性边界",
|
||||
"单次最多添加 8 个附件,支持同时传入 5 张图片",
|
||||
"auto、low、medium、high",
|
||||
"当前按单张结果呈现,不承诺批量多图生成",
|
||||
"发布后开放",
|
||||
"安全不是开关",
|
||||
];
|
||||
|
||||
for (const copy of requiredCopy) {
|
||||
report(html.includes(copy), `缺少准确文案:${copy}`);
|
||||
}
|
||||
|
||||
report(
|
||||
/version:\s*"0\.8\.0"/.test(configJs),
|
||||
"site.config.js 必须集中配置 0.8.0 版本",
|
||||
);
|
||||
report(
|
||||
/releasePublished:\s*false/.test(configJs),
|
||||
"Release 未发布前 releasePublished 必须为 false",
|
||||
);
|
||||
report(
|
||||
/releaseUrl:\s*"https:\/\/github\.com\/mesalogo\/goodbuddy\/releases\/tag\/v0\.8\.0"/.test(
|
||||
configJs,
|
||||
),
|
||||
"未来 v0.8.0 Release URL 配置不正确",
|
||||
);
|
||||
report(
|
||||
appJs.includes("config?.releasePublished === true"),
|
||||
"下载链接必须受 releasePublished 配置保护",
|
||||
);
|
||||
|
||||
const ids = [...html.matchAll(/\sid="([^"]+)"/g)].map((match) => match[1]);
|
||||
const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index);
|
||||
report(duplicateIds.length === 0, `存在重复 id:${[...new Set(duplicateIds)].join(", ")}`);
|
||||
|
||||
const attributes = [...html.matchAll(/\s(?:href|src)="([^"]+)"/g)].map((match) => match[1]);
|
||||
const fragmentLinks = attributes.filter((value) => value.startsWith("#") && value.length > 1);
|
||||
|
||||
for (const fragment of fragmentLinks) {
|
||||
report(ids.includes(fragment.slice(1)), `页内链接目标不存在:${fragment}`);
|
||||
}
|
||||
|
||||
const localAssets = attributes.filter(
|
||||
(value) =>
|
||||
!value.startsWith("#") &&
|
||||
!value.startsWith("https://") &&
|
||||
!value.startsWith("http://") &&
|
||||
!value.startsWith("mailto:") &&
|
||||
!value.startsWith("data:"),
|
||||
);
|
||||
|
||||
for (const asset of localAssets) {
|
||||
const cleanAsset = asset.split(/[?#]/, 1)[0].replace(/^\.\//, "");
|
||||
try {
|
||||
const assetStats = await stat(path.join(siteRoot, cleanAsset));
|
||||
report(assetStats.isFile(), `本地资源不是文件:${asset}`);
|
||||
} catch {
|
||||
errors.push(`本地资源不存在:${asset}`);
|
||||
}
|
||||
}
|
||||
|
||||
const externalBlankLinks = [
|
||||
...html.matchAll(/<a\b(?=[^>]*target="_blank")[^>]*>/g),
|
||||
].map((match) => match[0]);
|
||||
|
||||
for (const link of externalBlankLinks) {
|
||||
report(/rel="[^"]*noreferrer[^"]*"/.test(link), `新窗口链接缺少 noreferrer:${link}`);
|
||||
}
|
||||
|
||||
report(
|
||||
!/<a\b[^>]*href="[^"]+\.(?:exe|dmg|zip|AppImage|deb)(?:[?#][^"]*)?"/i.test(html),
|
||||
"Release 未发布前不得提供具体安装资产链接",
|
||||
);
|
||||
report(
|
||||
!/(?:react|vue|angular|bootstrap|tailwind)(?:\.min)?\.(?:js|css)/i.test(html),
|
||||
"静态官网不得引入额外框架资源",
|
||||
);
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error(`官网静态检查失败(${errors.length} 项):`);
|
||||
for (const error of errors) {
|
||||
console.error(`- ${error}`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log(
|
||||
`官网静态检查通过:${requiredFiles.length} 个必需文件,${ids.length} 个唯一 id,${localAssets.length} 个本地资源引用。`,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
window.GOODBUDDY_SITE_CONFIG = Object.freeze({
|
||||
version: "0.8.0",
|
||||
releasePublished: false,
|
||||
releaseUrl: "https://github.com/mesalogo/goodbuddy/releases/tag/v0.8.0",
|
||||
});
|
||||
+2231
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user