feat: selector de modelo con ventana desplazable y modelo por defecto del repo

This commit is contained in:
sargatxet committed 2026-10-18 18:33:20 +02:00
1 parent f6d2870635
commit bab0d93dd2
3 files changed
+60 -4

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
{
"defaultProvider": "github-copilot",
"defaultModel": "claude-opus-5.5"
}
+7
View File
@@ -7,6 +7,13 @@ y el proyecto usa [Versionado Semántico](https://semver.org/lang/es/).
## [No publicado] ## [No publicado]
### Cambiado
- El selector de modelo (`/modelo-proyecto` y `elegir_modelo_proyecto`) muestra
como máximo 15 modelos a la vez en una ventana que se desplaza con el cursor;
↑/↓ dan la vuelta de un extremo a otro y el cursor parte del modelo actual
del proyecto. Fuera de la TUI se mantiene el diálogo estándar.
### Pendiente ### Pendiente
- Fijar las reglas de modelo y de funcionamiento que la skill escribirá en el - Fijar las reglas de modelo y de funcionamiento que la skill escribirá en el
+49 -4
View File
@@ -16,9 +16,13 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
import { join, resolve } from "node:path"; import { join, resolve } from "node:path";
import type { Api, Model } from "@earendil-works/pi-ai"; import type { Api, Model } from "@earendil-works/pi-ai";
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
import { Container, type SelectItem, SelectList, Text } from "@earendil-works/pi-tui";
import { Type } from "typebox"; import { Type } from "typebox";
const SETTINGS_REL = join(".pi", "settings.json"); const SETTINGS_REL = join(".pi", "settings.json");
/** Máximo de modelos visibles a la vez en el selector (la lista se desplaza). */
const MAX_VISIBLES = 15;
function clave(m: Model<Api>): string { function clave(m: Model<Api>): string {
return `${m.provider}/${m.id}`; return `${m.provider}/${m.id}`;
@@ -88,10 +92,51 @@ async function elegir(ctx: ExtensionContext, proyecto: string): Promise<Model<Ap
const lista = disponibles(ctx); const lista = disponibles(ctx);
if (lista.length === 0) throw new Error("no hay modelos disponibles (¿falta /login o credenciales?)"); if (lista.length === 0) throw new Error("no hay modelos disponibles (¿falta /login o credenciales?)");
const actual = modeloDelProyecto(proyecto); const actual = modeloDelProyecto(proyecto);
const opciones = lista.map((m) => etiqueta(m, actual)); const titulo = `Modelo por defecto del proyecto (${proyecto})`;
const elegido = await ctx.ui.select(`Modelo por defecto del proyecto (${proyecto})`, opciones);
if (!elegido) return undefined; // Fuera de la TUI (p. ej. RPC) no hay componentes propios: diálogo estándar.
return lista[opciones.indexOf(elegido)]; if (ctx.mode !== "tui") {
const opciones = lista.map((m) => etiqueta(m, actual));
const elegido = await ctx.ui.select(titulo, opciones);
if (!elegido) return undefined;
return lista[opciones.indexOf(elegido)];
}
const items: SelectItem[] = lista.map((m) => ({ value: clave(m), label: etiqueta(m, actual) }));
const valor = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
const contenedor = new Container();
contenedor.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
contenedor.addChild(new Text(theme.fg("accent", theme.bold(titulo))));
// Ventana de MAX_VISIBLES elementos que se desplaza con el cursor;
// ↑ en el primero salta al último y ↓ en el último vuelve al primero.
const selector = new SelectList(items, Math.min(items.length, MAX_VISIBLES), {
selectedPrefix: (t) => theme.fg("accent", t),
selectedText: (t) => theme.fg("accent", t),
description: (t) => theme.fg("muted", t),
scrollInfo: (t) => theme.fg("dim", t),
noMatch: (t) => theme.fg("warning", t),
});
const inicial = actual ? items.findIndex((i) => i.value === actual) : -1;
if (inicial >= 0) selector.setSelectedIndex(inicial);
selector.onSelect = (item) => done(item.value);
selector.onCancel = () => done(null);
contenedor.addChild(selector);
contenedor.addChild(new Text(theme.fg("dim", "↑↓ moverse • enter elegir • esc cancelar")));
contenedor.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
return {
render: (width: number) => contenedor.render(width),
invalidate: () => contenedor.invalidate(),
handleInput: (data: string) => {
selector.handleInput(data);
tui.requestRender();
},
};
});
if (!valor) return undefined;
return lista.find((m) => clave(m) === valor);
} }
export default function (pi: ExtensionAPI) { export default function (pi: ExtensionAPI) {