Skip to content
Open
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
1 change: 1 addition & 0 deletions src/config-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ async function selectModel(
): Promise<OpenclawModel | null> {
return runMenu<OpenclawModel>({
message: t("select_model"),
searchable:true,
items: models.map((m) => ({
label: `${m.name} (${m.key})`,
value: m,
Expand Down
4 changes: 3 additions & 1 deletion src/utils/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ export interface MenuConfig<T = unknown> {
items: MenuItem<T>[];
loop?: boolean;
context?: MenuContext;
searchable?: boolean;
}

export async function runMenu<T>(config: MenuConfig<T>): Promise<T | null> {
const { message, items, loop = false, context } = config;
const { message, items, loop = false, context, searchable = false } = config;

const ctx = context ?? { logger: createLogger("Menu") };

Expand All @@ -40,6 +41,7 @@ export async function runMenu<T>(config: MenuConfig<T>): Promise<T | null> {
selected = await escSelect<MenuItem<T>>({
message,
choices,
searchable,
});
} catch (err) {
if (isPromptCancelled(err)) {
Expand Down
31 changes: 28 additions & 3 deletions src/utils/prompt.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { select, input, password } from "@inquirer/prompts";
import { select, search, input, password } from "@inquirer/prompts";

/**
* 用户通过 ESC 键取消 prompt 时抛出的错误
Expand Down Expand Up @@ -51,11 +51,36 @@ async function withEscapeCancel<T>(
* 支持 ESC 取消的 select
*/
export async function escSelect<T>(
config: Parameters<typeof select<T>>[0],
config: Parameters<typeof select<T>>[0] & { searchable?: boolean },
context?: Parameters<typeof select<T>>[1]
): Promise<T> {
const { searchable, ...selectConfig } = config;

if (searchable) {
// Convert select config to search config
const choices = selectConfig.choices || [];
const searchConfig = {
message: selectConfig.message,
source: async (searchTerm: string | undefined) => {
const term = (searchTerm || "").toLowerCase();
return choices.filter((choice) => {
if (choice && typeof choice === "object" && "name" in choice && choice.name) {
return choice.name.toLowerCase().includes(term);
}
return true;
});
},
pageSize: selectConfig.pageSize,
theme: selectConfig.theme,
};

return withEscapeCancel((signal) =>
search(searchConfig as Parameters<typeof search<T>>[0], { ...context, signal })
);
}

return withEscapeCancel((signal) =>
select(config, { ...context, signal })
select(selectConfig, { ...context, signal })
);
}

Expand Down