Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions emrg/gui/renderer/css/layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,29 @@
animation: banner-in var(--dur-med) var(--ease);
}

/* ── GitHub 连接横幅(Windows GCM rant Stage 2) ────── */
#github-banner {
position: absolute;
top: var(--sp-3);
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: var(--sp-2);
background: var(--amber-soft);
color: var(--amber);
border: 1px solid var(--border);
border-radius: 999px;
padding: 6px var(--sp-3) 6px var(--sp-4);
font-size: var(--fs-secondary);
box-shadow: var(--shadow-md);
z-index: 20;
animation: banner-in var(--dur-med) var(--ease);
white-space: nowrap;
max-width: 90%;
}
#github-banner .btn { padding: 4px 12px; min-height: 0; font-size: var(--fs-secondary); }

/* ── 侧边栏折叠(⌘B / 窄屏) ────────────── */
body.sidebar-collapsed #sidebar {
display: none;
Expand Down
7 changes: 7 additions & 0 deletions emrg/gui/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
<!-- 断连横幅 -->
<div id="conn-banner" class="hidden"></div>

<!-- GitHub 连接横幅(Windows GCM rant Stage 2:演化需要 GitHub 但未认证时出现) -->
<div id="github-banner" class="hidden">
<span id="github-banner-msg"></span>
<button type="button" id="github-banner-connect" class="btn btn-primary" data-i18n="settings.githubBannerConnect">去连接</button>
<button type="button" id="github-banner-dismiss" class="btn btn-ghost" title="关闭" data-i18n-title="settings.githubBannerDismiss">✕</button>
</div>

<!-- 聊天区 -->
<div id="chat-view"></div>
<!-- 回到底部悬浮按钮(上滑阅读时出现) -->
Expand Down
47 changes: 47 additions & 0 deletions emrg/gui/renderer/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,8 @@ const App = (() => {
const prev = state.lastKnownEvolutionCount;
state.lastKnownEvolutionCount = state.evolutionCount;
if (prev == null || state.evolutionCount <= prev) return; // 首次连接/无增长不提示
// 演化刚完成 = 演化真正需要 GitHub 的时刻(Windows GCM rant Stage 2)
maybeShowGithubBanner();
// 频率控制:一天最多 1 次(localStorage 可能不可用 → 静默跳过)
const today = new Date().toISOString().slice(0, 10);
try {
Expand All @@ -733,6 +735,50 @@ const App = (() => {
if (star) star.classList.remove("pulse");
}

// ── GitHub 连接横幅(Windows GCM rant Stage 2) ────
// 触发时机:演化计数增长(演化刚完成、需要 GitHub 推 PR)且未认证时。
// 本地聊天不依赖 GitHub → 启动时不打扰。横幅可关闭;关闭后本会话不再弹。
let _githubBannerDismissed = false;

function showGithubBanner() {
const b = $("github-banner");
if (!b) return;
const msg = $("github-banner-msg");
if (msg) msg.textContent = _t("settings.githubBannerMsg");
b.classList.remove("hidden");
}

function hideGithubBanner() {
const b = $("github-banner");
if (b) b.classList.add("hidden");
}

async function maybeShowGithubBanner() {
if (_githubBannerDismissed) return;
try {
const s = await window.emrg.githubStatus();
if (s && s.authenticated) return; // 已连接 → 无需提示
showGithubBanner();
} catch { /* githubStatus 不可用(daemon 忙/未合入)→ 静默跳过 */ }
}

function initGithubBanner() {
const connect = $("github-banner-connect");
if (connect) {
connect.addEventListener("click", () => {
hideGithubBanner();
Dialogs.showSettings(); // 设置页 GitHub 连接区(Stage 2a)
});
}
const dismiss = $("github-banner-dismiss");
if (dismiss) {
dismiss.addEventListener("click", () => {
_githubBannerDismissed = true;
hideGithubBanner();
});
}
}

function initEvolutionToast() {
const see = $("evolution-toast-see");
if (see) {
Expand Down Expand Up @@ -1186,6 +1232,7 @@ const App = (() => {
Dialogs.initRenameDialog();
Dialogs.initGithubSection(); // Windows GCM rant Stage 2:设置页 GitHub 连接
Dialogs.initDeviceDialog(); // Windows GCM rant Stage 2b:device flow 对话框
initGithubBanner(); // Windows GCM rant Stage 2:演化需 GitHub 但未认证时的连接横幅
initModelSwitcher();
initModeSwitcher(); // WorkBuddy P2:Ask/Auto 工作模式
ResultPanel.init(); // WorkBuddy P1:结果面板(⌘\ 折叠 + 窄屏自动隐藏)
Expand Down
8 changes: 8 additions & 0 deletions emrg/gui/renderer/js/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ const I18N = (() => {
"settings.githubDeviceCancel": "取消",
"settings.githubDeviceWait": "等待浏览器中确认…",
"settings.githubDeviceFailed": "GitHub 连接失败:{msg}",
// Windows GCM rant Stage 2:连接横幅
"settings.githubBannerMsg": "启用自进化需连接 GitHub →",
"settings.githubBannerConnect": "去连接",
"settings.githubBannerDismiss": "关闭",

// 首启引导
"welcome.title": "欢迎使用 EMRG",
Expand Down Expand Up @@ -388,6 +392,10 @@ const I18N = (() => {
"settings.githubDeviceCancel": "Cancel",
"settings.githubDeviceWait": "Waiting for confirmation in browser…",
"settings.githubDeviceFailed": "GitHub connect failed: {msg}",
// Windows GCM rant Stage 2: connect banner
"settings.githubBannerMsg": "Connect GitHub to enable self-evolution →",
"settings.githubBannerConnect": "Connect",
"settings.githubBannerDismiss": "Dismiss",

// Welcome / onboarding
"welcome.title": "Welcome to EMRG",
Expand Down
47 changes: 47 additions & 0 deletions emrg/gui/test/renderer.smoke.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const ELEMENT_IDS = [
"tasks-dialog", "tasks-list", "tasks-close",
"result-panel", "result-list", "result-toggle",
"growth-card", "growth-count", "about-recent",
"github-banner", "github-banner-msg", "github-banner-connect", "github-banner-dismiss",
];

/** 构造浏览器沙箱(win 即全局对象) */
Expand Down Expand Up @@ -297,9 +298,55 @@ test("多模型管理:modelDetails 加载渲染 + saveSettings 传 models 数
assert.strictEqual(saved.theme, "system");
assert.ok(Array.isArray(saved.models));
assert.strictEqual(saved.models.length, 2, "models 数组含默认 + gpt-4o");

const gpt = saved.models.find((m) => m.name === "gpt-4o");
assert.strictEqual(gpt.vision, true);
});
test("GCM rant Stage 2:演化增长 + 未认证 → GitHub 连接横幅出现(正反两态)", async () => {
// 正态:未认证 + 演化计数增长 → 横幅出现(先加 hidden 模拟 index.html 初始态)
const { ctx } = makeSandbox({
githubStatus: async () => ({ authenticated: false, user: null }),
});
await tick();
await vm.runInContext(`(async function() {
document.getElementById("github-banner").classList.add("hidden");
App.state.evolutionCount = 42;
App.state.lastKnownEvolutionCount = 40;
App.maybeShowEvolutionToast();
})()`, ctx);
await tick(); // 等待 githubStatus promise 解析
const visible = vm.runInContext(
'!document.getElementById("github-banner").classList.contains("hidden")',
ctx
);
assert.strictEqual(visible, true, "未认证 + 演化增长 → 横幅应出现");

// 负态:已认证 → 横幅保持隐藏
const { ctx: ctx2 } = makeSandbox({
githubStatus: async () => ({ authenticated: true, user: "octocat" }),
});
await tick();
await vm.runInContext(`(async function() {
document.getElementById("github-banner").classList.add("hidden");
App.state.evolutionCount = 42;
App.state.lastKnownEvolutionCount = 40;
App.maybeShowEvolutionToast();
})()`, ctx2);
await tick();
const hidden = vm.runInContext(
'document.getElementById("github-banner").classList.contains("hidden")',
ctx2
);
assert.strictEqual(hidden, true, "已认证 → 横幅应保持隐藏");

// 源码断言:连接/关闭按钮绑定 + 演化增长钩子
const src = fs.readFileSync(path.join(RENDERER_JS, "app.js"), "utf8");
assert.ok(src.includes("Dialogs.showSettings()"), "横幅[去连接]应打开设置页 GitHub 区");
assert.ok(src.includes("_githubBannerDismissed = true"), "关闭横幅应本会话不再弹");
const toastIdx = src.indexOf("function maybeShowEvolutionToast()");
const toastBlock = src.slice(toastIdx, toastIdx + 800);
assert.ok(toastBlock.includes("maybeShowGithubBanner()"), "演化增长应触发 GitHub 横幅检查");
});

test("右键菜单:重命名对话框 → renameSession 调用(设计 §3.2)", async () => {
let renamed = null;
Expand Down
Loading