feat: improve model selection UX and fix queue/status behaviors

- Add search/filtering to the `/model` command with multi-word matching
- Finalize partial stream previews (e.g. thinking blocks) on turn abort instead of clearing them
- Dynamically format low-cost `$ value` metrics up to 5 decimal places in status outputs
- Update queue tests to expect text-turn plans for aborted turns with partial text
This commit is contained in:
wassname
2026-04-23 12:42:35 +08:00
parent 64f9d0242f
commit 39da73ce3c
5 changed files with 50 additions and 8 deletions
+17 -4
View File
@@ -124,6 +124,7 @@ export interface BuildTelegramModelMenuStateParams {
availableModels: Model<any>[];
configuredScopedModelPatterns: string[];
cliScopedModelPatterns?: string[];
filterQuery?: string;
}
export type TelegramMenuCallbackAction =
@@ -416,8 +417,17 @@ export function getModelMenuItems(
export function buildTelegramModelMenuState(
params: BuildTelegramModelMenuStateParams,
): TelegramModelMenuState {
let filteredAvailableModels = params.availableModels;
if (params.filterQuery) {
const terms = params.filterQuery.toLowerCase().split(/\s+/).filter(Boolean);
filteredAvailableModels = filteredAvailableModels.filter((m) => {
const target = `${m.provider}/${m.id}`.toLowerCase();
return terms.every((t) => target.includes(t));
});
}
const allModels = sortScopedModels(
params.availableModels.map((model) => ({ model })),
filteredAvailableModels.map((model) => ({ model })),
params.activeModel,
);
const scopedModels =
@@ -425,7 +435,7 @@ export function buildTelegramModelMenuState(
? sortScopedModels(
resolveScopedModelPatterns(
params.configuredScopedModelPatterns,
params.availableModels,
filteredAvailableModels,
),
params.activeModel,
)
@@ -433,17 +443,20 @@ export function buildTelegramModelMenuState(
let note: string | undefined;
if (
params.configuredScopedModelPatterns.length > 0 &&
scopedModels.length === 0
scopedModels.length === 0 &&
!params.filterQuery
) {
note = params.cliScopedModelPatterns
? "No CLI scoped models matched the current auth configuration. Showing all available models."
: "No scoped models matched the current auth configuration. Showing all available models.";
} else if (params.filterQuery && filteredAvailableModels.length === 0) {
note = "No models matched your search query.";
}
return {
chatId: params.chatId,
messageId: 0,
page: 0,
scope: scopedModels.length > 0 ? "scoped" : "all",
scope: (scopedModels.length > 0 && !params.filterQuery) ? "scoped" : "all",
scopedModels,
allModels,
note,
+9
View File
@@ -367,6 +367,15 @@ export function buildTelegramAgentEndPlan(options: {
};
}
if (options.stopReason === "aborted") {
if (options.hasFinalText) {
return {
kind: "text",
shouldClearPreview: false,
shouldDispatchNext,
shouldSendErrorMessage: false,
shouldSendAttachmentNotice: false,
};
}
return {
kind: "aborted",
shouldClearPreview: true,
+8 -1
View File
@@ -65,12 +65,19 @@ function buildUsageSummary(stats: TelegramUsageStats): string | undefined {
return tokenParts.length > 0 ? tokenParts.join(" ") : undefined;
}
function formatCost(cost: number): string {
if (cost === 0) return "0.00";
if (cost < 0.001) return cost.toFixed(5);
if (cost < 0.01) return cost.toFixed(4);
return cost.toFixed(3);
}
function buildCostSummary(
stats: TelegramUsageStats,
usesSubscription: boolean,
): string | undefined {
if (!stats.totalCost && !usesSubscription) return undefined;
return `$${stats.totalCost.toFixed(3)}${usesSubscription ? " (sub)" : ""}`;
return `$${formatCost(stats.totalCost)}${usesSubscription ? " (sub)" : ""}`;
}
function buildContextSummary(