fix: Apply model name change correctly

# Conflicts:
#	web-app/src/lib/utils.ts
This commit is contained in:
Vanalite 2025-09-29 16:31:18 +07:00
parent 75dee86375
commit 03ee9c14a3
9 changed files with 67 additions and 48 deletions

1
src-tauri/Cargo.lock generated
View File

@ -5323,6 +5323,7 @@ dependencies = [
"sysinfo",
"tauri",
"tauri-plugin",
"tauri-plugin-hardware",
"thiserror 2.0.12",
"tokio",
]

View File

@ -6,7 +6,7 @@ import {
PopoverTrigger,
} from '@/components/ui/popover'
import { useModelProvider } from '@/hooks/useModelProvider'
import { cn, getProviderTitle } from '@/lib/utils'
import { cn, getProviderTitle, getModelDisplayName } from '@/lib/utils'
import { highlightFzfMatch } from '@/utils/highlight'
import Capabilities from './Capabilities'
import { IconSettings, IconX } from '@tabler/icons-react'
@ -240,7 +240,7 @@ const DropdownModelProvider = ({
// Update display model when selection changes
useEffect(() => {
if (selectedProvider && selectedModel) {
setDisplayModel(selectedModel.id)
setDisplayModel(getModelDisplayName(selectedModel))
} else {
setDisplayModel(t('common:selectAModel'))
}
@ -326,7 +326,7 @@ const DropdownModelProvider = ({
// Create Fzf instance for fuzzy search
const fzfInstance = useMemo(() => {
return new Fzf(searchableItems, {
selector: (item) => item.model.id.toLowerCase(),
selector: (item) => `${getModelDisplayName(item.model)} ${item.model.id}`.toLowerCase(),
})
}, [searchableItems])
@ -390,7 +390,7 @@ const DropdownModelProvider = ({
const handleSelect = useCallback(
async (searchableModel: SearchableModel) => {
// Immediately update display to prevent double-click issues
setDisplayModel(searchableModel.model.id)
setDisplayModel(getModelDisplayName(searchableModel.model))
setSearchValue('')
setOpen(false)
@ -576,7 +576,7 @@ const DropdownModelProvider = ({
/>
</div>
<span className="text-main-view-fg/80 text-sm">
{searchableModel.model.id}
{getModelDisplayName(searchableModel.model)}
</span>
<div className="flex-1"></div>
{capabilities.length > 0 && (
@ -669,7 +669,7 @@ const DropdownModelProvider = ({
className="text-main-view-fg/80 text-sm"
title={searchableModel.model.id}
>
{searchableModel.model.id}
{getModelDisplayName(searchableModel.model)}
</span>
<div className="flex-1"></div>
{capabilities.length > 0 && (

View File

@ -14,7 +14,7 @@ import { Button } from '@/components/ui/button'
import { DynamicControllerSetting } from '@/containers/dynamicControllerSetting'
import { useModelProvider } from '@/hooks/useModelProvider'
import { useServiceHub } from '@/hooks/useServiceHub'
import { cn } from '@/lib/utils'
import { cn, getModelDisplayName } from '@/lib/utils'
import { useTranslation } from '@/i18n/react-i18next-compat'
type ModelSettingProps = {
@ -261,7 +261,7 @@ export function ModelSetting({
<SheetContent className="h-[calc(100%-8px)] top-1 right-1 rounded-e-md overflow-y-auto">
<SheetHeader>
<SheetTitle>
{t('common:modelSettings.title', { modelId: model.id })}
{t('common:modelSettings.title', { modelId: getModelDisplayName(model) })}
</SheetTitle>
<SheetDescription>
{t('common:modelSettings.description')}

View File

@ -39,8 +39,8 @@ export const DialogEditModel = ({
const { t } = useTranslation()
const { updateProvider, setProviders } = useModelProvider()
const [selectedModelId, setSelectedModelId] = useState<string>('')
const [modelName, setModelName] = useState<string>('')
const [originalModelName, setOriginalModelName] = useState<string>('')
const [displayName, setDisplayName] = useState<string>('')
const [originalDisplayName, setOriginalDisplayName] = useState<string>('')
const [originalCapabilities, setOriginalCapabilities] = useState<
Record<string, boolean>
>({})
@ -86,7 +86,7 @@ export const DialogEditModel = ({
(m: any) => m.id === selectedModelId
)
// Initialize capabilities and model name from selected model
// Initialize capabilities and display name from selected model
useEffect(() => {
if (selectedModel) {
const modelCapabilities = selectedModel.capabilities || []
@ -98,9 +98,10 @@ export const DialogEditModel = ({
web_search: modelCapabilities.includes('web_search'),
reasoning: modelCapabilities.includes('reasoning'),
})
const modelNameValue = selectedModel.id
setModelName(modelNameValue)
setOriginalModelName(modelNameValue)
// Use existing displayName if available, otherwise fall back to model ID
const displayNameValue = (selectedModel as any).displayName || selectedModel.id
setDisplayName(displayNameValue)
setOriginalDisplayName(displayNameValue)
const originalCaps = {
completion: modelCapabilities.includes('completion'),
@ -122,14 +123,14 @@ export const DialogEditModel = ({
}))
}
// Handle model name change
const handleModelNameChange = (newName: string) => {
setModelName(newName)
// Handle display name change
const handleDisplayNameChange = (newName: string) => {
setDisplayName(newName)
}
// Check if there are unsaved changes
const hasUnsavedChanges = () => {
const nameChanged = modelName !== originalModelName
const nameChanged = displayName !== originalDisplayName
const capabilitiesChanged =
JSON.stringify(capabilities) !== JSON.stringify(originalCapabilities)
return nameChanged || capabilitiesChanged
@ -141,13 +142,21 @@ export const DialogEditModel = ({
setIsLoading(true)
try {
// Update model name if changed
if (modelName !== originalModelName) {
await serviceHub
.models()
.updateModel(selectedModel.id, { id: modelName })
setOriginalModelName(modelName)
await serviceHub.providers().getProviders().then(setProviders)
let updatedModels = provider.models
// Update display name if changed
if (displayName !== originalDisplayName) {
// Update the model in the provider models array with displayName
updatedModels = updatedModels.map((m: any) => {
if (m.id === selectedModelId) {
return {
...m,
displayName: displayName,
}
}
return m
})
setOriginalDisplayName(displayName)
}
// Update capabilities if changed
@ -159,8 +168,7 @@ export const DialogEditModel = ({
.map(([capName]) => capName)
// Find and update the model in the provider
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updatedModels = provider.models.map((m: any) => {
updatedModels = updatedModels.map((m: any) => {
if (m.id === selectedModelId) {
return {
...m,
@ -172,15 +180,15 @@ export const DialogEditModel = ({
return m
})
setOriginalCapabilities(capabilities)
}
// Update the provider with the updated models
updateProvider(provider.provider, {
...provider,
models: updatedModels,
})
setOriginalCapabilities(capabilities)
}
// Show success toast and close dialog
toast.success('Model updated successfully')
setIsOpen(false)
@ -213,22 +221,25 @@ export const DialogEditModel = ({
</DialogDescription>
</DialogHeader>
{/* Model Name Section */}
{/* Model Display Name Section */}
<div className="py-1">
<label
htmlFor="model-name"
htmlFor="display-name"
className="text-sm font-medium mb-3 block"
>
Model Name
Display Name
</label>
<Input
id="model-name"
value={modelName}
onChange={(e) => handleModelNameChange(e.target.value)}
placeholder="Enter model name"
id="display-name"
value={displayName}
onChange={(e) => handleDisplayNameChange(e.target.value)}
placeholder="Enter display name"
className="w-full"
disabled={isLoading}
/>
<p className="text-xs text-main-view-fg/60 mt-1">
This is the name that will be shown in the interface. The original model file remains unchanged.
</p>
</div>
{/* Warning Banner */}

View File

@ -104,6 +104,7 @@ export const useModelProvider = create<ModelProviderState>()(
...model,
settings: settings,
capabilities: existingModel?.capabilities || model.capabilities,
displayName: existingModel?.displayName || model.displayName,
}
})

View File

@ -7,7 +7,6 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function basenameNoExt(filePath: string): string {
const base = path.basename(filePath);
const VALID_EXTENSIONS = [".tar.gz", ".zip"];
@ -23,6 +22,13 @@ export function basenameNoExt(filePath: string): string {
return base.slice(0, -path.extname(base).length);
}
/**
* Get the display name for a model, falling back to the model ID if no display name is set
*/
export function getModelDisplayName(model: Model): string {
return model.displayName || model.id
}
export function getProviderLogo(provider: string) {
switch (provider) {
case 'jan':

View File

@ -3,7 +3,7 @@ import { Card, CardItem } from '@/containers/Card'
import HeaderPage from '@/containers/HeaderPage'
import SettingsMenu from '@/containers/SettingsMenu'
import { useModelProvider } from '@/hooks/useModelProvider'
import { cn, getProviderTitle } from '@/lib/utils'
import { cn, getProviderTitle, getModelDisplayName } from '@/lib/utils'
import {
createFileRoute,
Link,
@ -777,7 +777,7 @@ function ProviderDetail() {
className="font-medium line-clamp-1"
title={model.id}
>
{model.id}
{getModelDisplayName(model)}
</h1>
<Capabilities capabilities={capabilities} />
</div>

View File

@ -163,15 +163,14 @@ export class DefaultModelsService implements ModelsService {
}
async updateModel(modelId: string, model: Partial<CoreModel>): Promise<void> {
if (model.settings)
if (model.settings) {
this.getEngine()?.updateSettings(
model.settings as SettingComponentProps[]
)
if (modelId !== model.id) {
await this.getEngine()
?.update(modelId, model)
.then(() => console.log('Model updated successfully'))
}
// Note: Model name/ID updates are handled at the provider level in the frontend
// The engine doesn't have an update method for model metadata
console.log('Model update request processed for modelId:', modelId)
}
async pullModel(

View File

@ -28,6 +28,7 @@ type Model = {
id: string
model?: string
name?: string
displayName?: string
version?: number | string
description?: string
format?: string