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
11 changes: 9 additions & 2 deletions emrg/gui/renderer/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -559,10 +559,17 @@ const App = (() => {
function sessionProjectName(sid) {
const os = state.openSessions.find((s) => s.sid === sid);
if (os && os.projectName) return os.projectName;
// rant 17:48:07:回退用会话自身 cwd 末段(与历史列表一致),再 "home"
const cur = state.sessions.find((s) => s.session_id === sid);
if (cur && cur.cwd) {
const norm = String(cur.cwd).replace(/\\/g, "/").replace(/\/+$/, "");
const seg = norm.split("/");
if (seg[seg.length - 1]) return seg[seg.length - 1];
}
return "home";
}

// 会话视图顶部标题栏:项目/名称(id) 或 项目/id(有 title 时带 (id) 后缀
// 会话视图顶部标题栏:统一 project/name|id(rant 2026-08-20T17:48:07
function renderSessionHeader(sid) {
if (!sid) return;
const view = [...$("workspace").children].find((c) => c.dataset?.sid === sid);
Expand All @@ -575,7 +582,7 @@ const App = (() => {
const cur = state.sessions.find((s) => s.session_id === sid) || {};
const project = sessionProjectName(sid);
const title = cur.title && cur.title !== sid ? cur.title : "";
const text = title ? `${project}/${title}(${sid})` : `${project}/${sid}`;
const text = `${project}/${title}|${sid}`;
header.textContent = text;
header.title = text; // 悬停完整信息
}
Expand Down
10 changes: 0 additions & 10 deletions emrg/gui/renderer/js/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,6 @@ const I18N = (() => {
"chat.elapsed": "耗时 {s}",
"chat.expand": "展开全文",
"chat.toolGroupSummary": "{count} 个工具执行 · {time}",
// 时间分组(utils.js/sidebar.js)
"util.groupToday": "今天",
"util.groupYesterday": "昨天",
"util.groupEarlier": "更早",
// 结果面板(result-panel.js)
// Markdown 代码块(markdown.js)
"md.copyCode": "复制代码",
Expand Down Expand Up @@ -410,7 +406,6 @@ const I18N = (() => {
"app.closeSession": "❌ 关闭会话(保留数据)",
"app.closeFailed": "关闭会话失败了:{msg}",
"sidebar.openSessions": "打开的会话",
"sidebar.openSessionOf": "{project} / {title}",
"app.noModels": "还没有配置模型",
"app.goSettings": "去设置添加",
"app.modelSwitchFailed": "切换模型失败了:{msg}",
Expand Down Expand Up @@ -727,10 +722,6 @@ const I18N = (() => {
"chat.elapsed": "took {s}",
"chat.expand": "Expand full text",
"chat.toolGroupSummary": "{count} tool calls · {time}",
// Time groups (utils.js/sidebar.js)
"util.groupToday": "Today",
"util.groupYesterday": "Yesterday",
"util.groupEarlier": "Earlier",
// Result panel (result-panel.js)
// Markdown code block (markdown.js)
"md.copyCode": "Copy code",
Expand Down Expand Up @@ -820,7 +811,6 @@ const I18N = (() => {
"app.closeSession": "❌ Close session (keep data)",
"app.closeFailed": "Failed to close session: {msg}",
"sidebar.openSessions": "Open sessions",
"sidebar.openSessionOf": "{project} / {title}",
"app.noModels": "No models configured",
"app.goSettings": "Add in Settings",
"app.modelSwitchFailed": "Failed to switch model: {msg}",
Expand Down
60 changes: 31 additions & 29 deletions emrg/gui/renderer/js/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ const Sidebar = (() => {
const known = (App.state && App.state.sessions) || [];
for (const entry of openSessions) {
const cur = known.find((s) => s.session_id === entry.sid) || {};
const title = entry.title || cur.title || entry.sid; // entry.title 优先(跨项目),再 cur.title(当前项目),最后 sid
const title = entry.title || cur.title || ""; // entry.title 优先(跨项目),再 cur.title;id 单独显示,不降级为 sid
const item = el("div", { class: "conv-item open-session-item" });
item.dataset.sid = entry.sid;
item.appendChild(el("span", { class: "conv-title" }, _t("sidebar.openSessionOf", { project: entry.projectName || "", title })));
item.appendChild(el("span", { class: "conv-title" }, sessionLabel(entry.projectName || "", title, entry.sid)));
item.addEventListener("click", () => App.switchSession(entry.sid));
item.addEventListener("contextmenu", (e) => {
e.preventDefault();
Expand All @@ -41,7 +41,20 @@ const Sidebar = (() => {
highlight(App.state.sessionId);
}

/** 渲染分组对话列表 */
/** 会话条目统一格式 project/name|id(rant 2026-08-20T17:48:07 三处统一) */
function sessionLabel(project, title, sid) {
return `${project}/${title}|${sid}`;
}

/** cwd 末段作项目名(Path(s.cwd).name 语义,兼容 \\ 与 /) */
function cwdProjectName(cwd) {
if (!cwd) return "";
const norm = String(cwd).replace(/\\/g, "/").replace(/\/+$/, "");
const seg = norm.split("/");
return seg[seg.length - 1] || "";
}

/** 渲染会话列表(rant 17:48:07:去掉今天/昨天/更早分组,按最后活跃倒序,project/name|id) */
function render(list) {
sessions = list || [];
const nav = $("conv-list");
Expand All @@ -50,32 +63,21 @@ const Sidebar = (() => {
nav.appendChild(el("div", { class: "conv-item placeholder" }, EMRG_Copy.COPY.noSessions));
return;
}
// rant 21:19:分组标签本地化(顺序保持 今天→昨天→更早 不变)
const groups = {};
for (const lbl of [_t("util.groupToday"), _t("util.groupYesterday"), _t("util.groupEarlier")]) {
groups[lbl] = [];
}
for (const s of sessions) {
const g = groupLabel(s.updated_at || s.created_at);
if (!groups[g]) groups[g] = [];
groups[g].push(s);
}
for (const [label, items] of Object.entries(groups)) {
if (!items.length) continue;
nav.appendChild(el("div", { class: "conv-group-label" }, label));
for (const s of items) {
const item = el("div", { class: "conv-item" });
item.dataset.sid = s.session_id;
const title = s.title || s.session_id; // G27:title 优先
item.appendChild(el("span", { class: "conv-title" }, title));
item.addEventListener("click", () => App.switchSession(s.session_id));
// 右键菜单:重命名 / 删除(友好确认)
item.addEventListener("contextmenu", (e) => {
e.preventDefault();
App.showConvMenu(item, s.session_id, title);
});
nav.appendChild(item);
}
const sorted = [...sessions].sort((a, b) =>
String(b.updated_at || b.created_at || "").localeCompare(String(a.updated_at || a.created_at || "")));
for (const s of sorted) {
const item = el("div", { class: "conv-item" });
item.dataset.sid = s.session_id;
const title = s.title || ""; // G27:title 优先,无 title 则空(id 已单独显示)
const project = cwdProjectName(s.cwd);
item.appendChild(el("span", { class: "conv-title" }, sessionLabel(project, title, s.session_id)));
item.addEventListener("click", () => App.switchSession(s.session_id));
// 右键菜单:重命名 / 删除(友好确认)
item.addEventListener("contextmenu", (e) => {
e.preventDefault();
App.showConvMenu(item, s.session_id, title || s.session_id);
});
nav.appendChild(item);
}
highlight(App.state.sessionId);
}
Expand Down
14 changes: 0 additions & 14 deletions emrg/gui/renderer/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,6 @@ function genRequestId() {
});
}

/** 会话时间分组:今天 / 昨天 / 更早 */
function groupLabel(ts) {
if (!ts) return _t("util.groupEarlier");
const d = new Date(ts);
if (Number.isNaN(d.getTime())) return _t("util.groupEarlier");
const now = new Date();
const startOfDay = (x) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime();
const dayDiff = Math.round((startOfDay(now) - startOfDay(d)) / 86400000);
if (dayDiff <= 0) return _t("util.groupToday");
if (dayDiff === 1) return _t("util.groupYesterday");
return _t("util.groupEarlier");
}

/** rant 21:19:i18n 取词(i18n.js 缺失时回退 key 本身) */
function _t(key, params) {
try {
Expand Down Expand Up @@ -116,7 +103,6 @@ window.$ = $;
window.el = el;
window.escapeHtml = escapeHtml;
window.genRequestId = genRequestId;
window.groupLabel = groupLabel;
window.applyTheme = applyTheme;
window.relTime = relTime;
window.showToast = showToast;
9 changes: 0 additions & 9 deletions emrg/gui/test/i18n.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,6 @@ test("Stage2:动态文案键(app/chat/dlg/panel)双语齐全", () => {
assert.strictEqual(evalIn(zh, 'I18N.t("dlg.deleteModelBody", { name: "gpt-4o" })'), "「gpt-4o」将从可用模型里移除。");
});

test("Stage2:时间分组标签本地化(util.group*)", () => {
const { ctx } = makeSandbox({ navigator: { language: "en-US" } });
assert.strictEqual(evalIn(ctx, 'I18N.t("util.groupToday")'), "Today");
assert.strictEqual(evalIn(ctx, 'I18N.t("util.groupYesterday")'), "Yesterday");
assert.strictEqual(evalIn(ctx, 'I18N.t("util.groupEarlier")'), "Earlier");
const { ctx: zh } = makeSandbox({ navigator: { language: "zh-CN" } });
assert.strictEqual(evalIn(zh, 'I18N.t("util.groupToday")'), "今天");
});

test("Stage2:成长卡/关于区静态文案键(#501 吸收)", () => {
const { ctx } = makeSandbox({ navigator: { language: "en-US" } });
assert.strictEqual(evalIn(ctx, 'I18N.t("copy.growthCountPrefix")'), "Self-evolved");
Expand Down
13 changes: 7 additions & 6 deletions emrg/gui/test/renderer.smoke.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,9 @@ test("boot:config 就绪 → 加载会话列表", async () => {
switchSession: async () => ({}),
});
await tick();
// conv-list 应有分组标签 + 会话项
// conv-list 应有会话项(rant 17:48:07:无分组标签,直接 project/name|id)
const items = vm.runInContext('document.getElementById("conv-list").children.length', ctx);
assert.ok(items >= 2, `conv-list 应有分组标签+会话项,实际 ${items}`);
assert.ok(items >= 1, `conv-list 应有会话项,实际 ${items}`);
});

test("流式 delta 追加 + 工具行 running→done 状态流转", async () => {
Expand Down Expand Up @@ -1941,15 +1941,15 @@ test("P4 s2: open_sessions 事件 → 渲染打开会话区(项目名/标题 +
assert.strictEqual(els["open-sessions-label"].hidden, true, "label hidden when no open sessions");
});

test("P4 s2: 跨项目打开会话(entry.title 优先,state.sessions 无该 sid)→ 显示 entry.title 而非 sid", async () => {
test("P4 s2: 跨项目打开会话(entry.title 优先,state.sessions 无该 sid)→ 统一 project/name|id 格式", async () => {
const { ctx, els } = makeSandbox({});
await tick();
await vm.runInContext(
'App.state.sessionId = "sess-x";' +
'App.state.sessions = [{ session_id: "sess-local", title: "Local" }];' + // 当前项目会话;无 sess-x / sess-other
'App.handleEvent({ type: "open_sessions", data: { openSessions: [' + // main 已按 lastActive 倒序
' { sid: "sess-x", projectName: "evolution", projectPath: "/p/evolution", lastActive: "t3", title: "Evolution Task" },' + // 跨项目 + title
' { sid: "sess-other", projectName: "mem", projectPath: "/p/mem", lastActive: "t2" },' + // 跨项目无 title → sid 兜底
' { sid: "sess-other", projectName: "mem", projectPath: "/p/mem", lastActive: "t2" },' + // 跨项目无 title → 空 name
' { sid: "sess-local", projectName: "emrg", projectPath: "/p/emrg", lastActive: "t1" }' + // 当前项目 → state.sessions title
'] } });',
ctx
Expand All @@ -1958,11 +1958,12 @@ test("P4 s2: 跨项目打开会话(entry.title 优先,state.sessions 无该
assert.strictEqual(nav.children.length, 3, "three open-session items rendered");
const t0 = nav.children[0].children[0] || nav.children[0];
assert.ok((t0.textContent || "").includes("Evolution Task"), "cross-project entry shows entry.title");
assert.ok(!(t0.textContent || "").includes("sess-x"), "does NOT fall back to sid when entry.title present");
assert.ok((t0.textContent || "").includes("sess-x"), "id 单独显示(project/name|id)");
const t1 = nav.children[1].children[0] || nav.children[1];
assert.ok((t1.textContent || "").includes("sess-other"), "cross-project no title + not in state.sessions → sid fallback");
assert.ok((t1.textContent || "").includes("sess-other"), "cross-project no title → id 兜底显示");
const t2 = nav.children[2].children[0] || nav.children[2];
assert.ok((t2.textContent || "").includes("Local"), "current-project entry still resolves via state.sessions title");
assert.ok((t2.textContent || "").includes("sess-local"), "当前项目条目同样带 id(project/name|id)");
});

test("P4 s2: closeOpenSession 关闭激活会话 → 切到剩余打开会话 + 容器释放", async () => {
Expand Down
Loading