fix: reverse setting local provider (#5140)
* fix: reverse setting local provider * fix conflict
This commit is contained in:
parent
e9c9205544
commit
27c2a360f0
@ -240,264 +240,293 @@ function ProviderDetail() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Settings */}
|
<div
|
||||||
<Card>
|
className={cn(
|
||||||
{provider?.settings.map((setting, settingIndex) => {
|
'flex flex-col gap-3',
|
||||||
// Use the DynamicController component
|
provider &&
|
||||||
const actionComponent = (
|
provider.provider === 'llama.cpp' &&
|
||||||
<div className="mt-2">
|
'flex-col-reverse'
|
||||||
<DynamicControllerSetting
|
)}
|
||||||
controllerType={setting.controller_type}
|
>
|
||||||
controllerProps={setting.controller_props}
|
{/* Settings */}
|
||||||
className={cn(
|
<Card>
|
||||||
setting.key === 'api-key' &&
|
{provider?.settings.map((setting, settingIndex) => {
|
||||||
'third-step-setup-remote-provider'
|
// Use the DynamicController component
|
||||||
)}
|
const actionComponent = (
|
||||||
onChange={(newValue) => {
|
<div className="mt-2">
|
||||||
if (provider) {
|
<DynamicControllerSetting
|
||||||
const newSettings = [...provider.settings]
|
controllerType={setting.controller_type}
|
||||||
// Handle different value types by forcing the type
|
controllerProps={setting.controller_props}
|
||||||
// Use type assertion to bypass type checking
|
className={cn(
|
||||||
|
setting.key === 'api-key' &&
|
||||||
|
'third-step-setup-remote-provider'
|
||||||
|
)}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
if (provider) {
|
||||||
|
const newSettings = [...provider.settings]
|
||||||
|
// Handle different value types by forcing the type
|
||||||
|
// Use type assertion to bypass type checking
|
||||||
|
|
||||||
;(
|
;(
|
||||||
newSettings[settingIndex].controller_props as {
|
newSettings[settingIndex].controller_props as {
|
||||||
value: string | boolean | number
|
value: string | boolean | number
|
||||||
|
}
|
||||||
|
).value = newValue
|
||||||
|
|
||||||
|
// Create update object with updated settings
|
||||||
|
const updateObj: Partial<ModelProvider> = {
|
||||||
|
settings: newSettings,
|
||||||
}
|
}
|
||||||
).value = newValue
|
// Check if this is an API key or base URL setting and update the corresponding top-level field
|
||||||
|
const settingKey = setting.key
|
||||||
// Create update object with updated settings
|
if (
|
||||||
const updateObj: Partial<ModelProvider> = {
|
settingKey === 'api-key' &&
|
||||||
settings: newSettings,
|
typeof newValue === 'string'
|
||||||
}
|
) {
|
||||||
// Check if this is an API key or base URL setting and update the corresponding top-level field
|
updateObj.api_key = newValue
|
||||||
const settingKey = setting.key
|
} else if (
|
||||||
if (
|
settingKey === 'base-url' &&
|
||||||
settingKey === 'api-key' &&
|
typeof newValue === 'string'
|
||||||
typeof newValue === 'string'
|
) {
|
||||||
) {
|
updateObj.base_url = newValue
|
||||||
updateObj.api_key = newValue
|
}
|
||||||
} else if (
|
updateSettings(
|
||||||
settingKey === 'base-url' &&
|
providerName,
|
||||||
typeof newValue === 'string'
|
updateObj.settings ?? []
|
||||||
) {
|
|
||||||
updateObj.base_url = newValue
|
|
||||||
}
|
|
||||||
updateSettings(
|
|
||||||
providerName,
|
|
||||||
updateObj.settings ?? []
|
|
||||||
)
|
|
||||||
updateProvider(providerName, {
|
|
||||||
...provider,
|
|
||||||
...updateObj,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CardItem
|
|
||||||
key={settingIndex}
|
|
||||||
title={setting.title}
|
|
||||||
column={
|
|
||||||
setting.controller_type === 'input' &&
|
|
||||||
setting.controller_props.type !== 'number'
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
description={
|
|
||||||
<RenderMarkdown
|
|
||||||
className="![>p]:text-main-view-fg/70 select-none"
|
|
||||||
content={setting.description}
|
|
||||||
components={{
|
|
||||||
// Make links open in a new tab
|
|
||||||
a: ({ ...props }) => {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
{...props}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className={cn(
|
|
||||||
setting.key === 'api-key' &&
|
|
||||||
'second-step-setup-remote-provider'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
},
|
updateProvider(providerName, {
|
||||||
p: ({ ...props }) => (
|
...provider,
|
||||||
<p {...props} className="!mb-0" />
|
...updateObj,
|
||||||
),
|
})
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
</div>
|
||||||
actions={actionComponent}
|
)
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
|
|
||||||
<DeleteProvider provider={provider} />
|
return (
|
||||||
</Card>
|
<CardItem
|
||||||
|
key={settingIndex}
|
||||||
|
title={setting.title}
|
||||||
|
column={
|
||||||
|
setting.controller_type === 'input' &&
|
||||||
|
setting.controller_props.type !== 'number'
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
<RenderMarkdown
|
||||||
|
className="![>p]:text-main-view-fg/70 select-none"
|
||||||
|
content={setting.description}
|
||||||
|
components={{
|
||||||
|
// Make links open in a new tab
|
||||||
|
a: ({ ...props }) => {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
{...props}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className={cn(
|
||||||
|
setting.key === 'api-key' &&
|
||||||
|
'second-step-setup-remote-provider'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
p: ({ ...props }) => (
|
||||||
|
<p {...props} className="!mb-0" />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
actions={actionComponent}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
{/* Models */}
|
<DeleteProvider provider={provider} />
|
||||||
<Card
|
</Card>
|
||||||
header={
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
{/* Models */}
|
||||||
<h1 className="text-main-view-fg font-medium text-base">
|
<Card
|
||||||
Models
|
header={
|
||||||
</h1>
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="flex items-center gap-2">
|
<h1 className="text-main-view-fg font-medium text-base">
|
||||||
{provider && provider.provider !== 'llama.cpp' && (
|
Models
|
||||||
<>
|
</h1>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{provider && provider.provider !== 'llama.cpp' && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
size="sm"
|
||||||
|
className="hover:no-underline"
|
||||||
|
onClick={handleRefreshModels}
|
||||||
|
disabled={refreshingModels}
|
||||||
|
>
|
||||||
|
<div className="cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-1.5 py-1 gap-1">
|
||||||
|
{refreshingModels ? (
|
||||||
|
<IconLoader
|
||||||
|
size={18}
|
||||||
|
className="text-main-view-fg/50 animate-spin"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IconRefresh
|
||||||
|
size={18}
|
||||||
|
className="text-main-view-fg/50"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className="text-main-view-fg/70">
|
||||||
|
{refreshingModels
|
||||||
|
? 'Refreshing...'
|
||||||
|
: 'Refresh'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
<DialogAddModel provider={provider} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{provider && provider.provider === 'llama.cpp' && (
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="hover:no-underline"
|
className="hover:no-underline"
|
||||||
onClick={handleRefreshModels}
|
onClick={async () => {
|
||||||
disabled={refreshingModels}
|
const selectedFile = await open({
|
||||||
|
multiple: false,
|
||||||
|
directory: false,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: 'GGUF',
|
||||||
|
extensions: ['gguf'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (selectedFile) {
|
||||||
|
try {
|
||||||
|
await importModel(selectedFile)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'Failed to import model:',
|
||||||
|
error
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
// Refresh the provider to update the models list
|
||||||
|
getProviders().then(setProviders)
|
||||||
|
toast.success('Import Model', {
|
||||||
|
id: `import-model-${provider.provider}`,
|
||||||
|
description: `Model ${provider.provider} has been imported successfully.`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-1.5 py-1 gap-1">
|
<div className="cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out p-1.5 py-1 gap-1 -mr-2">
|
||||||
{refreshingModels ? (
|
<IconFolderPlus
|
||||||
<IconLoader
|
size={18}
|
||||||
size={18}
|
className="text-main-view-fg/50"
|
||||||
className="text-main-view-fg/50 animate-spin"
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<IconRefresh
|
|
||||||
size={18}
|
|
||||||
className="text-main-view-fg/50"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<span className="text-main-view-fg/70">
|
<span className="text-main-view-fg/70">
|
||||||
{refreshingModels ? 'Refreshing...' : 'Refresh'}
|
Import
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
<DialogAddModel provider={provider} />
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
|
||||||
{provider && provider.provider === 'llama.cpp' && (
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
size="sm"
|
|
||||||
className="hover:no-underline"
|
|
||||||
onClick={async () => {
|
|
||||||
const selectedFile = await open({
|
|
||||||
multiple: false,
|
|
||||||
directory: false,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: 'GGUF',
|
|
||||||
extensions: ['gguf'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (selectedFile) {
|
|
||||||
try {
|
|
||||||
await importModel(selectedFile)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to import model:', error)
|
|
||||||
} finally {
|
|
||||||
// Refresh the provider to update the models list
|
|
||||||
getProviders().then(setProviders)
|
|
||||||
toast.success('Import Model', {
|
|
||||||
id: `import-model-${provider.provider}`,
|
|
||||||
description: `Model ${provider.provider} has been imported successfully.`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-1.5 py-1 gap-1">
|
|
||||||
<IconFolderPlus
|
|
||||||
size={18}
|
|
||||||
className="text-main-view-fg/50"
|
|
||||||
/>
|
|
||||||
<span className="text-main-view-fg/70">Import</span>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
>
|
||||||
>
|
{provider?.models.length ? (
|
||||||
{provider?.models.length ? (
|
provider?.models.map((model, modelIndex) => {
|
||||||
provider?.models.map((model, modelIndex) => {
|
const capabilities = model.capabilities || []
|
||||||
const capabilities = model.capabilities || []
|
return (
|
||||||
return (
|
<CardItem
|
||||||
<CardItem
|
key={modelIndex}
|
||||||
key={modelIndex}
|
title={
|
||||||
title={
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<h1 className="font-medium">{model.id}</h1>
|
||||||
<h1 className="font-medium">{model.id}</h1>
|
<Capabilities capabilities={capabilities} />
|
||||||
<Capabilities capabilities={capabilities} />
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
actions={
|
||||||
actions={
|
<div className="flex items-center gap-1">
|
||||||
<div className="flex items-center gap-1">
|
<DialogEditModel
|
||||||
{provider && provider.provider === 'llama.cpp' && (
|
provider={provider}
|
||||||
<div className="mr-1">
|
modelId={model.id}
|
||||||
{activeModels.some(
|
/>
|
||||||
(activeModel) => activeModel.id === model.id
|
{model.settings && (
|
||||||
) ? (
|
<ModelSetting
|
||||||
<Button
|
provider={provider}
|
||||||
size="sm"
|
model={model}
|
||||||
variant="destructive"
|
/>
|
||||||
onClick={() => handleStopModel(model.id)}
|
)}
|
||||||
>
|
<DialogDeleteModel
|
||||||
Stop
|
provider={provider}
|
||||||
</Button>
|
modelId={model.id}
|
||||||
) : (
|
/>
|
||||||
<Button
|
{provider &&
|
||||||
size="sm"
|
provider.provider === 'llama.cpp' && (
|
||||||
disabled={loadingModels.includes(model.id)}
|
<div className="ml-2">
|
||||||
onClick={() => handleStartModel(model.id)}
|
{activeModels.some(
|
||||||
>
|
(activeModel) =>
|
||||||
{loadingModels.includes(model.id) ? (
|
activeModel.id === model.id
|
||||||
<div className="flex items-center gap-2">
|
) ? (
|
||||||
<IconLoader
|
<Button
|
||||||
size={16}
|
size="sm"
|
||||||
className="animate-spin"
|
variant="destructive"
|
||||||
/>
|
onClick={() =>
|
||||||
</div>
|
handleStopModel(model.id)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Stop
|
||||||
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
'Start'
|
<Button
|
||||||
|
size="sm"
|
||||||
|
disabled={loadingModels.includes(
|
||||||
|
model.id
|
||||||
|
)}
|
||||||
|
onClick={() =>
|
||||||
|
handleStartModel(model.id)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{loadingModels.includes(model.id) ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<IconLoader
|
||||||
|
size={16}
|
||||||
|
className="animate-spin"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
'Start'
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
}
|
||||||
<DialogEditModel
|
/>
|
||||||
provider={provider}
|
)
|
||||||
modelId={model.id}
|
})
|
||||||
/>
|
) : (
|
||||||
{model.settings && (
|
<div className="-mt-2">
|
||||||
<ModelSetting provider={provider} model={model} />
|
<div className="flex items-center gap-2 text-left-panel-fg/80">
|
||||||
)}
|
<h6 className="font-medium text-base">
|
||||||
<DialogDeleteModel
|
No model found
|
||||||
provider={provider}
|
</h6>
|
||||||
modelId={model.id}
|
</div>
|
||||||
/>
|
<p className="text-left-panel-fg/60 mt-1 text-xs leading-relaxed">
|
||||||
</div>
|
Available models will be listed here. If you don't have
|
||||||
}
|
any models yet, visit the
|
||||||
/>
|
<Link to={route.hub}>Hub</Link>
|
||||||
)
|
to download.
|
||||||
})
|
</p>
|
||||||
) : (
|
|
||||||
<div className="-mt-2">
|
|
||||||
<div className="flex items-center gap-2 text-left-panel-fg/80">
|
|
||||||
<h6 className="font-medium text-base">No model found</h6>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-left-panel-fg/60 mt-1 text-xs leading-relaxed">
|
)}
|
||||||
Available models will be listed here. If you don't have
|
</Card>
|
||||||
any models yet, visit the
|
</div>
|
||||||
<Link to={route.hub}>Hub</Link>
|
|
||||||
to download.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user