mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 08:13:11 +03:00
refactor(tui): simplify session rows
This commit is contained in:
+11
-2
@@ -2147,7 +2147,12 @@ export class NanobotTui {
|
||||
this.updateTitle()
|
||||
}
|
||||
const limit = this.renderer.height >= 20 ? 8 : 4
|
||||
this.sessionMenu.open(sessions, this.client.activeChatId, limit)
|
||||
this.sessionMenu.open(
|
||||
sessions,
|
||||
this.client.activeChatId,
|
||||
limit,
|
||||
this.defaultModelPreset,
|
||||
)
|
||||
this.startSessionRefresh()
|
||||
this.renderTitleColor()
|
||||
this.sessionMenu.update(this.composer.plainText, limit)
|
||||
@@ -2361,7 +2366,11 @@ export class NanobotTui {
|
||||
this.apiReauthenticator,
|
||||
)
|
||||
if (this.quitting || loadId !== this.sessionLoadId || !this.sessionMenu.visible) return
|
||||
this.sessionMenu.replace(sessions, this.client.activeChatId)
|
||||
this.sessionMenu.replace(
|
||||
sessions,
|
||||
this.client.activeChatId,
|
||||
this.defaultModelPreset,
|
||||
)
|
||||
this.status.content = sessions.length ? `${sessions.length} sessions` : "No saved sessions"
|
||||
} catch {
|
||||
// Keep the existing picker usable during a transient refresh failure.
|
||||
|
||||
@@ -52,10 +52,12 @@ describe("SessionMenu", () => {
|
||||
warning: "#F5C451",
|
||||
})
|
||||
setup.renderer.root.add(menu.root)
|
||||
menu.open(sessions, "one", 6)
|
||||
menu.open(sessions, "one", 6, "Codex")
|
||||
await setup.renderOnce()
|
||||
|
||||
expect(setup.captureCharFrame()).toContain("› ● API migration")
|
||||
expect(setup.captureCharFrame()).not.toContain("Move authentication")
|
||||
expect(setup.captureCharFrame()).not.toContain("Codex")
|
||||
expect(menu.choose()?.chatId).toBe("one")
|
||||
|
||||
menu.update("release stable", 6)
|
||||
@@ -231,4 +233,31 @@ describe("SessionMenu", () => {
|
||||
expect(duplicates).toContain("frontend/nanobot")
|
||||
expect(duplicates).toContain("backend/nanobot")
|
||||
})
|
||||
|
||||
test("shows only model overrides and keeps previews searchable", async () => {
|
||||
setup = await createTestRenderer({ width: 80, height: 18, screenMode: "alternate-screen" })
|
||||
const menu = new SessionMenu(setup.renderer, {
|
||||
text: "#FFFFFF",
|
||||
muted: "#999999",
|
||||
border: "#555555",
|
||||
accent: "#FF8A33",
|
||||
warning: "#F5C451",
|
||||
})
|
||||
setup.renderer.root.add(menu.root)
|
||||
|
||||
menu.open(sessions, "one", 6, "Codex")
|
||||
await setup.renderOnce()
|
||||
const frame = setup.captureCharFrame()
|
||||
expect(frame).not.toContain("Codex")
|
||||
expect(frame).not.toContain("Prepare the stable release")
|
||||
|
||||
menu.update("prepare stable", 6)
|
||||
await setup.renderOnce()
|
||||
expect(menu.choose()?.chatId).toBe("two")
|
||||
|
||||
menu.replace([{ ...sessions[1]!, modelPreset: "Deep Research" }], "one", "Codex")
|
||||
await setup.renderOnce()
|
||||
expect(setup.captureCharFrame()).toContain("Deep Research")
|
||||
menu.hide()
|
||||
})
|
||||
})
|
||||
|
||||
+23
-7
@@ -32,6 +32,7 @@ export class SessionMenu {
|
||||
private spinnerFrame = 0
|
||||
private spinnerTimer: ReturnType<typeof setInterval> | null = null
|
||||
private rows: SessionMenuRow[] = []
|
||||
private defaultModelPreset = ""
|
||||
private readonly snapshots = new Map<string, {
|
||||
preview: string
|
||||
runStartedAt: number | null
|
||||
@@ -58,12 +59,9 @@ export class SessionMenu {
|
||||
].join(" "),
|
||||
render: (session, selected) => {
|
||||
const age = updatedLabel(session.updatedAt)
|
||||
const preview = session.preview.trim()
|
||||
const detail = [
|
||||
this.showWorkspaces ? this.workspaceLabel(session) : "",
|
||||
session.modelPreset,
|
||||
age,
|
||||
preview && preview !== sessionLabel(session) ? preview : "",
|
||||
this.modelOverride(session),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ")
|
||||
@@ -73,7 +71,9 @@ export class SessionMenu {
|
||||
: selected ? this.theme.text : this.theme.muted
|
||||
return [
|
||||
...(marker ? [chunk(`${marker.text} `, marker.color)] : []),
|
||||
chunk(`${sessionLabel(session)}${detail ? ` ${detail}` : ""}`, foreground),
|
||||
chunk(sessionLabel(session), foreground),
|
||||
...(detail ? [chunk(` ${detail}`, this.theme.muted)] : []),
|
||||
...(age ? [chunk(` ${age}`, this.theme.muted)] : []),
|
||||
]
|
||||
},
|
||||
emptyText: "No matching sessions",
|
||||
@@ -86,14 +86,25 @@ export class SessionMenu {
|
||||
return this.picker.visible
|
||||
}
|
||||
|
||||
open(sessions: SessionSummary[], currentChatId: string, limit: number): void {
|
||||
open(
|
||||
sessions: SessionSummary[],
|
||||
currentChatId: string,
|
||||
limit: number,
|
||||
defaultModelPreset = "",
|
||||
): void {
|
||||
this.defaultModelPreset = defaultModelPreset
|
||||
this.observe(sessions, currentChatId)
|
||||
this.rows = this.prepareRows(sessions, currentChatId)
|
||||
this.picker.show(this.rows, "", limit)
|
||||
this.syncSpinner()
|
||||
}
|
||||
|
||||
replace(sessions: SessionSummary[], currentChatId: string): void {
|
||||
replace(
|
||||
sessions: SessionSummary[],
|
||||
currentChatId: string,
|
||||
defaultModelPreset = this.defaultModelPreset,
|
||||
): void {
|
||||
this.defaultModelPreset = defaultModelPreset
|
||||
this.observe(sessions, currentChatId)
|
||||
this.rows = this.prepareRows(sessions, currentChatId)
|
||||
this.picker.replace(this.rows)
|
||||
@@ -236,6 +247,11 @@ export class SessionMenu {
|
||||
private workspaceLabel(session: SessionSummary): string {
|
||||
return this.workspaceLabels.get(normalizeWorkspacePath(session.workspaceScope?.project_path)) || ""
|
||||
}
|
||||
|
||||
private modelOverride(session: SessionSummary): string {
|
||||
const preset = session.modelPreset?.trim() || ""
|
||||
return preset && preset !== this.defaultModelPreset ? preset : ""
|
||||
}
|
||||
}
|
||||
|
||||
function chunk(text: string, color: string): TextChunk {
|
||||
|
||||
Reference in New Issue
Block a user