This commit is contained in:
2026-07-26 14:02:37 +07:00
parent bc56546b49
commit 367ebc1c7f
171 changed files with 4617 additions and 10402 deletions
@@ -16,16 +16,19 @@ export class CustomMessageComponent extends Container {
private customComponent?: Component;
private markdownTheme: MarkdownTheme;
private _expanded = false;
private outputPad: number;
constructor(
message: CustomMessage<unknown>,
customRenderer?: MessageRenderer,
markdownTheme: MarkdownTheme = getMarkdownTheme(),
outputPad = 1,
) {
super();
this.message = message;
this.customRenderer = customRenderer;
this.markdownTheme = markdownTheme;
this.outputPad = outputPad;
this.addChild(new Spacer(1));
@@ -42,6 +45,13 @@ export class CustomMessageComponent extends Container {
}
}
setOutputPad(outputPad: number): void {
if (this.outputPad !== outputPad) {
this.outputPad = outputPad;
this.rebuild();
}
}
override invalidate(): void {
super.invalidate();
this.rebuild();
@@ -58,7 +68,11 @@ export class CustomMessageComponent extends Container {
// Try custom renderer first - it handles its own styling
if (this.customRenderer) {
try {
const component = this.customRenderer(this.message, { expanded: this._expanded }, theme);
const component = this.customRenderer(
this.message,
{ expanded: this._expanded, outputPad: this.outputPad },
theme,
);
if (component) {
// Custom renderer provides its own styled component
this.customComponent = component;
@@ -36,7 +36,7 @@ function enableAll(enabledIds: EnabledIds, allIds: string[], targetIds?: string[
for (const id of targets) {
if (!result.includes(id)) result.push(id);
}
return result.length === allIds.length ? null : result;
return result.length === allIds.length && result.every((id) => allIds.includes(id)) ? null : result;
}
function clearAll(enabledIds: EnabledIds, allIds: string[], targetIds?: string[]): EnabledIds {
@@ -67,7 +67,7 @@ function getSortedIds(enabledIds: EnabledIds, allIds: string[]): string[] {
interface ModelItem {
fullId: string;
model: Model<any>;
model: Model<any> | undefined;
enabled: boolean;
}
@@ -152,20 +152,20 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
}
private buildItems(): ModelItem[] {
// Filter out IDs that no longer have a corresponding model (e.g., after logout)
return getSortedIds(this.enabledIds, this.allIds)
.filter((id) => this.modelsById.has(id))
.map((id) => ({
fullId: id,
model: this.modelsById.get(id)!,
enabled: isEnabled(this.enabledIds, id),
}));
return getSortedIds(this.enabledIds, this.allIds).map((id) => ({
fullId: id,
model: this.modelsById.get(id),
enabled: isEnabled(this.enabledIds, id),
}));
}
private getFooterText(): string {
const enabledCount = this.enabledIds?.length ?? this.allIds.length;
const enabledCount = this.enabledIds?.filter((id) => this.modelsById.has(id)).length ?? this.allIds.length;
const unavailableCount = this.enabledIds?.filter((id) => !this.modelsById.has(id)).length ?? 0;
const allEnabled = this.enabledIds === null;
const countText = allEnabled ? "all enabled" : `${enabledCount}/${this.allIds.length} enabled`;
const countText = allEnabled
? "all enabled"
: `${enabledCount}/${this.allIds.length} enabled${unavailableCount ? ` · ${unavailableCount} unavailable` : ""}`;
const parts = [
`${keyText("tui.select.confirm")} toggle`,
`${keyText("app.models.enableAll")} all`,
@@ -184,8 +184,10 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
const query = this.searchInput.getValue();
const items = this.buildItems();
this.filteredItems = query
? fuzzyFilter(items, query, (i) =>
getModelSearchText({ id: i.model.id, provider: i.model.provider, name: i.model.name }),
? fuzzyFilter(items, query, (item) =>
item.model
? getModelSearchText({ id: item.model.id, provider: item.model.provider, name: item.model.name })
: item.fullId,
)
: items;
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredItems.length - 1));
@@ -216,9 +218,16 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
const item = this.filteredItems[i]!;
const isSelected = i === this.selectedIndex;
const prefix = isSelected ? theme.fg("accent", "→ ") : " ";
const modelText = isSelected ? theme.fg("accent", item.model.id) : item.model.id;
const providerBadge = theme.fg("muted", ` [${item.model.provider}]`);
const status = allEnabled ? "" : item.enabled ? theme.fg("success", " ✓") : theme.fg("dim", " ✗");
const id = item.model?.id ?? item.fullId;
const modelText = isSelected ? theme.fg("accent", id) : id;
const providerBadge = theme.fg("muted", item.model ? ` [${item.model.provider}]` : " [unavailable]");
const status = item.model
? allEnabled
? ""
: item.enabled
? theme.fg("success", " ✓")
: theme.fg("dim", " ✗")
: theme.fg("dim", " ✗");
this.listContainer.addChild(new Text(`${prefix}${modelText}${providerBadge}${status}`, 0, 0));
}
@@ -232,7 +241,13 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
if (this.filteredItems.length > 0) {
const selected = this.filteredItems[this.selectedIndex];
this.listContainer.addChild(new Spacer(1));
this.listContainer.addChild(new Text(theme.fg("muted", ` Model Name: ${selected.model.name}`), 0, 0));
this.listContainer.addChild(
new Text(
theme.fg("muted", ` ${selected.model ? `Model Name: ${selected.model.name}` : "Model unavailable"}`),
0,
0,
),
);
}
}
@@ -310,7 +325,7 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
// Toggle provider of current item
if (kb.matches(data, "app.models.toggleProvider")) {
const item = this.filteredItems[this.selectedIndex];
if (item) {
if (item?.model) {
const provider = item.model.provider;
const providerIds = this.allIds.filter((id) => this.modelsById.get(id)!.provider === provider);
const allEnabled = providerIds.every((id) => isEnabled(this.enabledIds, id));