mirror of
https://github.com/wassname/pi-telegram.git
synced 2026-06-27 14:45:47 +08:00
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:
@@ -1023,6 +1023,7 @@ export default function (pi: ExtensionAPI) {
|
||||
|
||||
async function getModelMenuState(
|
||||
chatId: number,
|
||||
args: string | undefined,
|
||||
ctx: ExtensionContext,
|
||||
): Promise<TelegramModelMenuState> {
|
||||
const { SettingsManager } = await import("@mariozechner/pi-coding-agent");
|
||||
@@ -1040,6 +1041,7 @@ export default function (pi: ExtensionAPI) {
|
||||
availableModels,
|
||||
configuredScopedModelPatterns: configuredScopedModels,
|
||||
cliScopedModelPatterns: cliScopedModels ?? undefined,
|
||||
filterQuery: args,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1255,6 +1257,7 @@ export default function (pi: ExtensionAPI) {
|
||||
async function openModelMenu(
|
||||
chatId: number,
|
||||
replyToMessageId: number,
|
||||
args: string | undefined,
|
||||
ctx: ExtensionContext,
|
||||
): Promise<void> {
|
||||
if (!ctx.isIdle() && !canOfferInFlightTelegramModelSwitch(ctx)) {
|
||||
@@ -1265,7 +1268,7 @@ export default function (pi: ExtensionAPI) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
const state = await getModelMenuState(chatId, ctx);
|
||||
const state = await getModelMenuState(chatId, args, ctx);
|
||||
if (state.allModels.length === 0) {
|
||||
await sendTextReply(
|
||||
chatId,
|
||||
@@ -1791,8 +1794,8 @@ export default function (pi: ExtensionAPI) {
|
||||
}
|
||||
}
|
||||
|
||||
const commandName = parseTelegramCommand(rawText)?.name;
|
||||
const handled = await handleTelegramCommand(commandName, firstMessage, ctx);
|
||||
const command = parseTelegramCommand(rawText);
|
||||
const handled = await handleTelegramCommand(command?.name, command?.args, firstMessage, ctx);
|
||||
if (handled) return;
|
||||
|
||||
await enqueueTelegramTurn(messages, ctx);
|
||||
|
||||
+17
-4
@@ -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,
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -526,6 +526,16 @@ test("Agent end plan classifies turn outcomes correctly", () => {
|
||||
assert.equal(abortedPlan.kind, "aborted");
|
||||
assert.equal(abortedPlan.shouldClearPreview, true);
|
||||
assert.equal(abortedPlan.shouldDispatchNext, false);
|
||||
const abortedTextPlan = __telegramTestUtils.buildTelegramAgentEndPlan({
|
||||
hasTurn: true,
|
||||
stopReason: "aborted",
|
||||
preserveQueuedTurnsAsHistory: true,
|
||||
hasFinalText: true,
|
||||
hasQueuedAttachments: false,
|
||||
});
|
||||
assert.equal(abortedTextPlan.kind, "text");
|
||||
assert.equal(abortedTextPlan.shouldClearPreview, false);
|
||||
assert.equal(abortedTextPlan.shouldDispatchNext, false);
|
||||
const errorPlan = __telegramTestUtils.buildTelegramAgentEndPlan({
|
||||
hasTurn: true,
|
||||
stopReason: "error",
|
||||
|
||||
Reference in New Issue
Block a user