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", "sysinfo",
"tauri", "tauri",
"tauri-plugin", "tauri-plugin",
"tauri-plugin-hardware",
"thiserror 2.0.12", "thiserror 2.0.12",
"tokio", "tokio",
] ]

View File

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

View File

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

View File

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

View File

@ -104,6 +104,7 @@ export const useModelProvider = create<ModelProviderState>()(
...model, ...model,
settings: settings, settings: settings,
capabilities: existingModel?.capabilities || model.capabilities, 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)) return twMerge(clsx(inputs))
} }
export function basenameNoExt(filePath: string): string { export function basenameNoExt(filePath: string): string {
const base = path.basename(filePath); const base = path.basename(filePath);
const VALID_EXTENSIONS = [".tar.gz", ".zip"]; const VALID_EXTENSIONS = [".tar.gz", ".zip"];
@ -23,6 +22,13 @@ export function basenameNoExt(filePath: string): string {
return base.slice(0, -path.extname(base).length); 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) { export function getProviderLogo(provider: string) {
switch (provider) { switch (provider) {
case 'jan': case 'jan':

View File

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

View File

@ -163,15 +163,14 @@ export class DefaultModelsService implements ModelsService {
} }
async updateModel(modelId: string, model: Partial<CoreModel>): Promise<void> { async updateModel(modelId: string, model: Partial<CoreModel>): Promise<void> {
if (model.settings) if (model.settings) {
this.getEngine()?.updateSettings( this.getEngine()?.updateSettings(
model.settings as SettingComponentProps[] 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( async pullModel(

View File

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