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()
|
this.updateTitle()
|
||||||
}
|
}
|
||||||
const limit = this.renderer.height >= 20 ? 8 : 4
|
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.startSessionRefresh()
|
||||||
this.renderTitleColor()
|
this.renderTitleColor()
|
||||||
this.sessionMenu.update(this.composer.plainText, limit)
|
this.sessionMenu.update(this.composer.plainText, limit)
|
||||||
@@ -2361,7 +2366,11 @@ export class NanobotTui {
|
|||||||
this.apiReauthenticator,
|
this.apiReauthenticator,
|
||||||
)
|
)
|
||||||
if (this.quitting || loadId !== this.sessionLoadId || !this.sessionMenu.visible) return
|
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"
|
this.status.content = sessions.length ? `${sessions.length} sessions` : "No saved sessions"
|
||||||
} catch {
|
} catch {
|
||||||
// Keep the existing picker usable during a transient refresh failure.
|
// Keep the existing picker usable during a transient refresh failure.
|
||||||
|
|||||||
@@ -52,10 +52,12 @@ describe("SessionMenu", () => {
|
|||||||
warning: "#F5C451",
|
warning: "#F5C451",
|
||||||
})
|
})
|
||||||
setup.renderer.root.add(menu.root)
|
setup.renderer.root.add(menu.root)
|
||||||
menu.open(sessions, "one", 6)
|
menu.open(sessions, "one", 6, "Codex")
|
||||||
await setup.renderOnce()
|
await setup.renderOnce()
|
||||||
|
|
||||||
expect(setup.captureCharFrame()).toContain("› ● API migration")
|
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")
|
expect(menu.choose()?.chatId).toBe("one")
|
||||||
|
|
||||||
menu.update("release stable", 6)
|
menu.update("release stable", 6)
|
||||||
@@ -231,4 +233,31 @@ describe("SessionMenu", () => {
|
|||||||
expect(duplicates).toContain("frontend/nanobot")
|
expect(duplicates).toContain("frontend/nanobot")
|
||||||
expect(duplicates).toContain("backend/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 spinnerFrame = 0
|
||||||
private spinnerTimer: ReturnType<typeof setInterval> | null = null
|
private spinnerTimer: ReturnType<typeof setInterval> | null = null
|
||||||
private rows: SessionMenuRow[] = []
|
private rows: SessionMenuRow[] = []
|
||||||
|
private defaultModelPreset = ""
|
||||||
private readonly snapshots = new Map<string, {
|
private readonly snapshots = new Map<string, {
|
||||||
preview: string
|
preview: string
|
||||||
runStartedAt: number | null
|
runStartedAt: number | null
|
||||||
@@ -58,12 +59,9 @@ export class SessionMenu {
|
|||||||
].join(" "),
|
].join(" "),
|
||||||
render: (session, selected) => {
|
render: (session, selected) => {
|
||||||
const age = updatedLabel(session.updatedAt)
|
const age = updatedLabel(session.updatedAt)
|
||||||
const preview = session.preview.trim()
|
|
||||||
const detail = [
|
const detail = [
|
||||||
this.showWorkspaces ? this.workspaceLabel(session) : "",
|
this.showWorkspaces ? this.workspaceLabel(session) : "",
|
||||||
session.modelPreset,
|
this.modelOverride(session),
|
||||||
age,
|
|
||||||
preview && preview !== sessionLabel(session) ? preview : "",
|
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" · ")
|
.join(" · ")
|
||||||
@@ -73,7 +71,9 @@ export class SessionMenu {
|
|||||||
: selected ? this.theme.text : this.theme.muted
|
: selected ? this.theme.text : this.theme.muted
|
||||||
return [
|
return [
|
||||||
...(marker ? [chunk(`${marker.text} `, marker.color)] : []),
|
...(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",
|
emptyText: "No matching sessions",
|
||||||
@@ -86,14 +86,25 @@ export class SessionMenu {
|
|||||||
return this.picker.visible
|
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.observe(sessions, currentChatId)
|
||||||
this.rows = this.prepareRows(sessions, currentChatId)
|
this.rows = this.prepareRows(sessions, currentChatId)
|
||||||
this.picker.show(this.rows, "", limit)
|
this.picker.show(this.rows, "", limit)
|
||||||
this.syncSpinner()
|
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.observe(sessions, currentChatId)
|
||||||
this.rows = this.prepareRows(sessions, currentChatId)
|
this.rows = this.prepareRows(sessions, currentChatId)
|
||||||
this.picker.replace(this.rows)
|
this.picker.replace(this.rows)
|
||||||
@@ -236,6 +247,11 @@ export class SessionMenu {
|
|||||||
private workspaceLabel(session: SessionSummary): string {
|
private workspaceLabel(session: SessionSummary): string {
|
||||||
return this.workspaceLabels.get(normalizeWorkspacePath(session.workspaceScope?.project_path)) || ""
|
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 {
|
function chunk(text: string, color: string): TextChunk {
|
||||||
|
|||||||
Reference in New Issue
Block a user