Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,20 @@ export class ChatModelsViewModel extends Disposable {
}
}

renameModel(model: IModelItemEntry, newName: string | undefined): void {
this.languageModelsService.updateModelDisplayName(model.modelEntry.identifier, newName);
const metadata = this.languageModelsService.lookupLanguageModel(model.modelEntry.identifier);
const index = this.viewModelEntries.indexOf(model);
if (metadata && index !== -1) {
model.modelEntry.metadata = metadata;
this.splice(index, 1, [model]);
}
}

getModelDisplayName(model: IModelItemEntry): string | undefined {
return this.languageModelsService.getModelDisplayName(model.modelEntry.identifier);
}

private static getId(modelEntry: IModelEntry): string {
return `${modelEntry.identifier}.${modelEntry.metadata.version}-visible:${modelEntry.metadata.isUserSelectable}`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { ICommandService } from '../../../../../platform/commands/common/command
import { IEditorProgressService } from '../../../../../platform/progress/common/progress.js';
import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
import { CONTEXT_MODELS_SEARCH_FOCUS } from '../../common/constants.js';
import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js';

const $ = DOM.$;

Expand Down Expand Up @@ -312,6 +313,8 @@ class GutterColumnRenderer extends ModelsTableColumnRenderer<IToggleCollapseColu

constructor(
private readonly viewModel: ChatModelsViewModel,
private readonly isInternal: boolean,
@IQuickInputService private readonly quickInputService: IQuickInputService,
) {
super();
}
Expand Down Expand Up @@ -372,6 +375,40 @@ class GutterColumnRenderer extends ModelsTableColumnRenderer<IToggleCollapseColu
run: async () => this.viewModel.toggleVisibility(entry)
});
templateData.actionBar.push(toggleVisibilityAction, { icon: true, label: false });

// Add rename action for internal users only
if (this.isInternal) {
const hasCustomName = this.viewModel.getModelDisplayName(entry) !== undefined;
const renameAction = toAction({
id: 'renameModel',
label: localize('models.rename', 'Rename'),
class: `model-rename ${ThemeIcon.asClassName(Codicon.edit)}${hasCustomName ? ' model-has-custom-name' : ''}`,
tooltip: localize('models.renameTooltip', 'Rename model'),
run: async () => this.promptRenameModel(entry)
});
templateData.actionBar.push(renameAction, { icon: true, label: false });
}
}

private async promptRenameModel(entry: IModelItemEntry): Promise<void> {
const currentCustomName = this.viewModel.getModelDisplayName(entry);
const result = await this.quickInputService.input({
title: localize('models.renameTitle', "Rename Model"),
prompt: localize('models.renamePrompt', "Enter a new display name for this model"),
value: currentCustomName ?? entry.modelEntry.metadata.name,
validateInput: async (value) => {
if (value.trim().length === 0) {
return localize('models.renameEmpty', "Display name cannot be empty");
}
return undefined;
}
});

if (result !== undefined) {
const trimmedResult = result.trim();
// Update the model name (renameModel handles the logic of clearing if unchanged)
this.viewModel.renameModel(entry, trimmedResult);
}
}
}

Expand Down Expand Up @@ -929,7 +966,8 @@ export class ChatModelsWidget extends Disposable {
this.tableDisposables.clear();
DOM.clearNode(this.tableContainer);

const gutterColumnRenderer = this.instantiationService.createInstance(GutterColumnRenderer, this.viewModel);
const isInternal = this.chatEntitlementService.isInternal;
const gutterColumnRenderer = this.instantiationService.createInstance(GutterColumnRenderer, this.viewModel, isInternal);
const modelNameColumnRenderer = this.instantiationService.createInstance(ModelNameColumnRenderer);
const costColumnRenderer = this.instantiationService.createInstance(MultiplierColumnRenderer);
const tokenLimitsColumnRenderer = this.instantiationService.createInstance(TokenLimitsColumnRenderer);
Expand All @@ -945,13 +983,14 @@ export class ChatModelsWidget extends Disposable {
this.searchWidget.focus();
}));

const gutterWidth = isInternal ? 64 : 40;
const columns = [
{
label: '',
tooltip: '',
weight: 0.05,
minimumWidth: 40,
maximumWidth: 40,
minimumWidth: gutterWidth,
maximumWidth: gutterWidth,
templateId: GutterColumnRenderer.TEMPLATE_ID,
project(row: TableEntry): TableEntry { return row; }
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@
display: inherit;
}

/* Show rename icon when model has a custom name */
.models-widget .models-table-container .monaco-list-row .monaco-table-tr.models-model-row .models-gutter-column .monaco-action-bar .model-rename.model-has-custom-name {
display: inherit;
}

/** Model Name column styling **/

.models-widget .models-table-container .monaco-table-td .model-name-container {
Expand Down
58 changes: 53 additions & 5 deletions src/vs/workbench/contrib/chat/common/languageModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,16 @@ export interface ILanguageModelsService {

updateModelPickerPreference(modelIdentifier: string, showInModelPicker: boolean): void;

/**
* Updates the custom display name for a model. Set to undefined to remove the custom name.
*/
updateModelDisplayName(modelIdentifier: string, displayName: string | undefined): void;

/**
* Gets the custom display name for a model, if one is set.
*/
getModelDisplayName(modelIdentifier: string): string | undefined;

getLanguageModelIds(): string[];

getVendors(): IUserFriendlyLanguageModel[];
Expand Down Expand Up @@ -344,6 +354,7 @@ export class LanguageModelsService implements ILanguageModelsService {
private readonly _vendors = new Map<string, IUserFriendlyLanguageModel>();
private readonly _resolveLMSequencer = new SequencerByKey<string>();
private _modelPickerUserPreferences: Record<string, boolean> = {};
private _modelDisplayNames: Record<string, string> = {};
private readonly _hasUserSelectableModels: IContextKey<boolean>;
private readonly _contextKeyService: IContextKeyService;
private readonly _onLanguageModelChange = this._store.add(new Emitter<string>());
Expand All @@ -360,6 +371,7 @@ export class LanguageModelsService implements ILanguageModelsService {
this._hasUserSelectableModels = ChatContextKeys.languageModelsAreUserSelectable.bindTo(_contextKeyService);
this._contextKeyService = _contextKeyService;
this._modelPickerUserPreferences = this._storageService.getObject<Record<string, boolean>>('chatModelPickerPreferences', StorageScope.PROFILE, this._modelPickerUserPreferences);
this._modelDisplayNames = this._storageService.getObject<Record<string, string>>('chatModelDisplayNames', StorageScope.PROFILE, this._modelDisplayNames);
// TODO @lramos15 - Remove after a few releases, as this is just cleaning a bad storage state
const entitlementChangeHandler = () => {
if ((this._chatEntitlementService.entitlement === ChatEntitlement.Business || this._chatEntitlementService.entitlement === ChatEntitlement.Enterprise) && !this._chatEntitlementService.isInternal) {
Expand Down Expand Up @@ -437,6 +449,27 @@ export class LanguageModelsService implements ILanguageModelsService {
this._logService.trace(`[LM] Updated model picker preference for ${modelIdentifier} to ${showInModelPicker}`);
}

updateModelDisplayName(modelIdentifier: string, displayName: string | undefined): void {
const model = this._modelCache.get(modelIdentifier);
if (!model) {
this._logService.warn(`[LM] Cannot update display name for unknown model ${modelIdentifier}`);
return;
}

if (displayName === undefined || displayName === model.name) {
delete this._modelDisplayNames[modelIdentifier];
} else {
this._modelDisplayNames[modelIdentifier] = displayName;
}
this._storageService.store('chatModelDisplayNames', this._modelDisplayNames, StorageScope.PROFILE, StorageTarget.USER);
this._onLanguageModelChange.fire(model.vendor);
this._logService.trace(`[LM] Updated display name for ${modelIdentifier} to ${displayName}`);
}

getModelDisplayName(modelIdentifier: string): string | undefined {
return this._modelDisplayNames[modelIdentifier];
}

getVendors(): IUserFriendlyLanguageModel[] {
return Array.from(this._vendors.values()).filter(vendor => {
if (!vendor.when) {
Expand All @@ -453,13 +486,28 @@ export class LanguageModelsService implements ILanguageModelsService {

lookupLanguageModel(modelIdentifier: string): ILanguageModelChatMetadata | undefined {
const model = this._modelCache.get(modelIdentifier);
if (model && this._configurationService.getValue('chat.experimentalShowAllModels')) {
return { ...model, isUserSelectable: true };
if (!model) {
return undefined;
}
if (model && this._modelPickerUserPreferences[modelIdentifier] !== undefined) {
return { ...model, isUserSelectable: this._modelPickerUserPreferences[modelIdentifier] };

// Apply user preferences (visibility and custom display name)
let result = model;
const customName = this._modelDisplayNames[modelIdentifier];
const visibilityPref = this._modelPickerUserPreferences[modelIdentifier];
const showAllModels = this._configurationService.getValue('chat.experimentalShowAllModels');

if (customName !== undefined || visibilityPref !== undefined || showAllModels) {
result = { ...model };
if (customName !== undefined) {
result = { ...result, name: customName };
}
if (showAllModels) {
result = { ...result, isUserSelectable: true };
} else if (visibilityPref !== undefined) {
result = { ...result, isUserSelectable: visibilityPref };
}
}
return model;
return result;
}

private _clearModelCache(vendor: string): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ class MockLanguageModelsService implements ILanguageModelsService {
}
}

private modelDisplayNames = new Map<string, string>();

updateModelDisplayName(modelIdentifier: string, displayName: string | undefined): void {
const metadata = this.models.get(modelIdentifier);
if (metadata) {
if (displayName === undefined) {
this.modelDisplayNames.delete(modelIdentifier);
} else {
this.modelDisplayNames.set(modelIdentifier, displayName);
this.models.set(modelIdentifier, { ...metadata, name: displayName });
}
}
}

getModelDisplayName(modelIdentifier: string): string | undefined {
return this.modelDisplayNames.get(modelIdentifier);
}

getVendors(): IUserFriendlyLanguageModel[] {
return this.vendors;
}
Expand Down Expand Up @@ -865,4 +883,35 @@ suite('ChatModelsViewModel', () => {
assert.strictEqual(hiddenModel.modelEntry.metadata.isUserSelectable, true);
});

test('should rename model and update display name', async () => {
const model = viewModel.viewModelEntries.find(r => !isVendorEntry(r) && !isGroupEntry(r) && r.modelEntry.identifier === 'copilot-gpt-4') as IModelItemEntry;
assert.ok(model);
const originalName = model.modelEntry.metadata.name;
assert.strictEqual(originalName, 'GPT-4');

// Rename the model
viewModel.renameModel(model, 'Custom GPT-4 Name');

// Verify the name is updated
assert.strictEqual(model.modelEntry.metadata.name, 'Custom GPT-4 Name');

// Verify we can get the custom display name
assert.strictEqual(viewModel.getModelDisplayName(model), 'Custom GPT-4 Name');
});

test('should clear custom name when setting to undefined', async () => {
const model = viewModel.viewModelEntries.find(r => !isVendorEntry(r) && !isGroupEntry(r) && r.modelEntry.identifier === 'copilot-gpt-4') as IModelItemEntry;
assert.ok(model);

// Set a custom name first
viewModel.renameModel(model, 'Custom Name');
assert.strictEqual(viewModel.getModelDisplayName(model), 'Custom Name');

// Clear the custom name by setting it to undefined
viewModel.renameModel(model, undefined);

// Verify the custom name is cleared
assert.strictEqual(viewModel.getModelDisplayName(model), undefined);
});

});
8 changes: 8 additions & 0 deletions src/vs/workbench/contrib/chat/test/common/languageModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ export class NullLanguageModelsService implements ILanguageModelsService {
return;
}

updateModelDisplayName(modelIdentifier: string, displayName: string | undefined): void {
return;
}

getModelDisplayName(modelIdentifier: string): string | undefined {
return undefined;
}

getVendors(): IUserFriendlyLanguageModel[] {
return [];
}
Expand Down