feat(tts): add descriptive inline menu with action descriptions
- Add value/label support for command arg choices - TTS menu now shows descriptive title listing each action - Capitalize button labels (On, Off, Status, etc.) - Update Telegram, Discord, and Slack handlers to use labels Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
145618d625
commit
c120aa8a2e
@ -186,9 +186,18 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
||||
args: [
|
||||
{
|
||||
name: "action",
|
||||
description: "on | off | status | provider | limit | summary | audio | help",
|
||||
description: "TTS action",
|
||||
type: "string",
|
||||
choices: ["on", "off", "status", "provider", "limit", "summary", "audio", "help"],
|
||||
choices: [
|
||||
{ value: "on", label: "On" },
|
||||
{ value: "off", label: "Off" },
|
||||
{ value: "status", label: "Status" },
|
||||
{ value: "provider", label: "Provider" },
|
||||
{ value: "limit", label: "Limit" },
|
||||
{ value: "summary", label: "Summary" },
|
||||
{ value: "audio", label: "Audio" },
|
||||
{ value: "help", label: "Help" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "value",
|
||||
@ -197,7 +206,19 @@ function buildChatCommands(): ChatCommandDefinition[] {
|
||||
captureRemaining: true,
|
||||
},
|
||||
],
|
||||
argsMenu: "auto",
|
||||
argsMenu: {
|
||||
arg: "action",
|
||||
title:
|
||||
"TTS Actions:\n" +
|
||||
"• On – Enable TTS for responses\n" +
|
||||
"• Off – Disable TTS\n" +
|
||||
"• Status – Show current settings\n" +
|
||||
"• Provider – Set voice provider (edge, elevenlabs, openai)\n" +
|
||||
"• Limit – Set max characters for TTS\n" +
|
||||
"• Summary – Toggle AI summary for long texts\n" +
|
||||
"• Audio – Generate TTS from custom text\n" +
|
||||
"• Help – Show usage guide",
|
||||
},
|
||||
}),
|
||||
defineChatCommand({
|
||||
key: "whoami",
|
||||
|
||||
@ -255,33 +255,41 @@ function resolveDefaultCommandContext(cfg?: ClawdbotConfig): {
|
||||
};
|
||||
}
|
||||
|
||||
export type ResolvedCommandArgChoice = { value: string; label: string };
|
||||
|
||||
export function resolveCommandArgChoices(params: {
|
||||
command: ChatCommandDefinition;
|
||||
arg: CommandArgDefinition;
|
||||
cfg?: ClawdbotConfig;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
}): string[] {
|
||||
}): ResolvedCommandArgChoice[] {
|
||||
const { command, arg, cfg } = params;
|
||||
if (!arg.choices) return [];
|
||||
const provided = arg.choices;
|
||||
if (Array.isArray(provided)) return provided;
|
||||
const defaults = resolveDefaultCommandContext(cfg);
|
||||
const context: CommandArgChoiceContext = {
|
||||
cfg,
|
||||
provider: params.provider ?? defaults.provider,
|
||||
model: params.model ?? defaults.model,
|
||||
command,
|
||||
arg,
|
||||
};
|
||||
return provided(context);
|
||||
const raw = Array.isArray(provided)
|
||||
? provided
|
||||
: (() => {
|
||||
const defaults = resolveDefaultCommandContext(cfg);
|
||||
const context: CommandArgChoiceContext = {
|
||||
cfg,
|
||||
provider: params.provider ?? defaults.provider,
|
||||
model: params.model ?? defaults.model,
|
||||
command,
|
||||
arg,
|
||||
};
|
||||
return provided(context);
|
||||
})();
|
||||
return raw.map((choice) =>
|
||||
typeof choice === "string" ? { value: choice, label: choice } : choice,
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveCommandArgMenu(params: {
|
||||
command: ChatCommandDefinition;
|
||||
args?: CommandArgs;
|
||||
cfg?: ClawdbotConfig;
|
||||
}): { arg: CommandArgDefinition; choices: string[]; title?: string } | null {
|
||||
}): { arg: CommandArgDefinition; choices: ResolvedCommandArgChoice[]; title?: string } | null {
|
||||
const { command, args, cfg } = params;
|
||||
if (!command.args || !command.argsMenu) return null;
|
||||
if (command.argsParsing === "none") return null;
|
||||
|
||||
@ -12,14 +12,16 @@ export type CommandArgChoiceContext = {
|
||||
arg: CommandArgDefinition;
|
||||
};
|
||||
|
||||
export type CommandArgChoicesProvider = (context: CommandArgChoiceContext) => string[];
|
||||
export type CommandArgChoice = string | { value: string; label: string };
|
||||
|
||||
export type CommandArgChoicesProvider = (context: CommandArgChoiceContext) => CommandArgChoice[];
|
||||
|
||||
export type CommandArgDefinition = {
|
||||
name: string;
|
||||
description: string;
|
||||
type: CommandArgType;
|
||||
required?: boolean;
|
||||
choices?: string[] | CommandArgChoicesProvider;
|
||||
choices?: CommandArgChoice[] | CommandArgChoicesProvider;
|
||||
captureRemaining?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@ -93,16 +93,18 @@ function buildDiscordCommandOptions(params: {
|
||||
typeof focused?.value === "string" ? focused.value.trim().toLowerCase() : "";
|
||||
const choices = resolveCommandArgChoices({ command, arg, cfg });
|
||||
const filtered = focusValue
|
||||
? choices.filter((choice) => choice.toLowerCase().includes(focusValue))
|
||||
? choices.filter((choice) => choice.label.toLowerCase().includes(focusValue))
|
||||
: choices;
|
||||
await interaction.respond(
|
||||
filtered.slice(0, 25).map((choice) => ({ name: choice, value: choice })),
|
||||
filtered.slice(0, 25).map((choice) => ({ name: choice.label, value: choice.value })),
|
||||
);
|
||||
}
|
||||
: undefined;
|
||||
const choices =
|
||||
resolvedChoices.length > 0 && !autocomplete
|
||||
? resolvedChoices.slice(0, 25).map((choice) => ({ name: choice, value: choice }))
|
||||
? resolvedChoices
|
||||
.slice(0, 25)
|
||||
.map((choice) => ({ name: choice.label, value: choice.value }))
|
||||
: undefined;
|
||||
return {
|
||||
name: arg.name,
|
||||
@ -351,7 +353,11 @@ export function createDiscordCommandArgFallbackButton(params: DiscordCommandArgC
|
||||
|
||||
function buildDiscordCommandArgMenu(params: {
|
||||
command: ChatCommandDefinition;
|
||||
menu: { arg: CommandArgDefinition; choices: string[]; title?: string };
|
||||
menu: {
|
||||
arg: CommandArgDefinition;
|
||||
choices: Array<{ value: string; label: string }>;
|
||||
title?: string;
|
||||
};
|
||||
interaction: CommandInteraction;
|
||||
cfg: ReturnType<typeof loadConfig>;
|
||||
discordConfig: DiscordConfig;
|
||||
@ -365,11 +371,11 @@ function buildDiscordCommandArgMenu(params: {
|
||||
const buttons = choices.map(
|
||||
(choice) =>
|
||||
new DiscordCommandArgButton({
|
||||
label: choice,
|
||||
label: choice.label,
|
||||
customId: buildDiscordCommandArgCustomId({
|
||||
command: commandLabel,
|
||||
arg: menu.arg.name,
|
||||
value: choice,
|
||||
value: choice.value,
|
||||
userId,
|
||||
}),
|
||||
cfg: params.cfg,
|
||||
|
||||
@ -103,7 +103,7 @@ function buildSlackCommandArgMenuBlocks(params: {
|
||||
title: string;
|
||||
command: string;
|
||||
arg: string;
|
||||
choices: string[];
|
||||
choices: Array<{ value: string; label: string }>;
|
||||
userId: string;
|
||||
}) {
|
||||
const rows = chunkItems(params.choices, 5).map((choices) => ({
|
||||
@ -111,11 +111,11 @@ function buildSlackCommandArgMenuBlocks(params: {
|
||||
elements: choices.map((choice) => ({
|
||||
type: "button",
|
||||
action_id: SLACK_COMMAND_ARG_ACTION_ID,
|
||||
text: { type: "plain_text", text: choice },
|
||||
text: { type: "plain_text", text: choice.label },
|
||||
value: encodeSlackCommandArgValue({
|
||||
command: params.command,
|
||||
arg: params.arg,
|
||||
value: choice,
|
||||
value: choice.value,
|
||||
userId: params.userId,
|
||||
}),
|
||||
})),
|
||||
|
||||
@ -366,10 +366,10 @@ export const registerTelegramNativeCommands = ({
|
||||
rows.push(
|
||||
slice.map((choice) => {
|
||||
const args: CommandArgs = {
|
||||
values: { [menu.arg.name]: choice },
|
||||
values: { [menu.arg.name]: choice.value },
|
||||
};
|
||||
return {
|
||||
text: choice,
|
||||
text: choice.label,
|
||||
callback_data: buildCommandTextFromArgs(commandDefinition, args),
|
||||
};
|
||||
}),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user