Skip to content
Merged
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
47 changes: 22 additions & 25 deletions backend/apps/model_managment_app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import logging
from typing import List, Optional

from fastapi import APIRouter, Header, Query

from consts.model import (
BatchCreateModelsRequest,
Expand All @@ -19,6 +16,10 @@
get_models_by_tenant_factory_type,
update_model_record,
)
from fastapi import APIRouter, Header, Query, HTTPException
from fastapi.responses import JSONResponse
from http import HTTPStatus
from typing import List, Optional
from services.model_health_service import check_model_connectivity, embedding_dimension_check
from services.model_provider_service import prepare_model_dict, merge_existing_model_tokens, get_provider_models
from utils.auth_utils import get_current_user_id
Expand Down Expand Up @@ -215,31 +216,27 @@ async def update_single_model(request: dict, authorization: Optional[str] = Head
try:
user_id, tenant_id = get_current_user_id(authorization)
model_data = request
if not model_data.get("display_name"):
model_data["display_name"] = split_display_name(
model_data["model_name"])
# Check if display_name conflicts
existing_model_by_display = get_model_by_display_name(
model_data["display_name"], tenant_id)
if existing_model_by_display and existing_model_by_display["model_id"] != model_data["model_id"]:
return ModelResponse(
code=409,
message=f"Name {model_data['display_name']} is already in use, please choose another display name",
data=None
)
model_data["model_repo"], model_data["model_name"] = split_repo_name(
model_data["model_name"])
existing_model_by_display = get_model_by_display_name(
model_data["display_name"], tenant_id)
if existing_model_by_display and existing_model_by_display["model_id"] != model_data["model_id"]:
raise HTTPException(
status_code=int(HTTPStatus.CONFLICT),
detail=f"Name {model_data['display_name']} is already in use, please choose another display name"
)
# model_data["model_repo"], model_data["model_name"] = split_repo_name(model_data["model_name"])
update_model_record(model_data["model_id"], model_data, user_id)
return ModelResponse(
code=200,
message=f"Model {model_data['model_name']} updated successfully",
data=None
return JSONResponse(
status_code=int(HTTPStatus.OK),
content={
"code": int(HTTPStatus.OK),
"message": f"Model {model_data['display_name']} updated successfully",
"data": None
}
)
except Exception as e:
return ModelResponse(
code=500,
message=f"Failed to update model: {str(e)}",
data=None
raise HTTPException(
status_code=int(HTTPStatus.INTERNAL_SERVER_ERROR),
detail=f"Failed to update model: {str(e)}"
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'

import { Modal, Select, Input, Button, Switch, Tooltip, App } from 'antd'
import { InfoCircleFilled, CheckCircleOutlined, CloseCircleOutlined, LoadingOutlined, RightOutlined, DownOutlined, SettingOutlined } from '@ant-design/icons'
import { InfoCircleFilled, LoadingOutlined, RightOutlined, DownOutlined, SettingOutlined } from '@ant-design/icons'

import { useConfig } from '@/hooks/useConfig'
import { getConnectivityIcon, getConnectivityColor, getConnectivityMeta, ConnectivityStatusType } from '@/lib/utils'
import { modelService } from '@/services/modelService'
import { ModelType, SingleModelConfig } from '@/types/config'


const { Option } = Select

// Define the return type after adding a model
Expand All @@ -22,8 +24,7 @@ interface ModelAddDialogProps {
onSuccess: (model?: AddedModel) => Promise<void>
}

// Add type definition for connectivity status
type ConnectivityStatusType = "checking" | "available" | "unavailable" | null;
// Connectivity status type comes from utils

export const ModelAddDialog = ({ isOpen, onClose, onSuccess }: ModelAddDialogProps) => {
const { t } = useTranslation()
Expand Down Expand Up @@ -154,62 +155,27 @@ export const ModelAddDialog = ({ isOpen, onClose, onSuccess }: ModelAddDialogPro
const result = await modelService.verifyModelConfigConnectivity(config)

// Set connectivity status
let connectivityMessage = ''
if (result.connectivity) {
connectivityMessage = t('model.dialog.connectivity.status.available')
} else {
connectivityMessage = t('model.dialog.connectivity.status.unavailable')
}
setConnectivityStatus({
status: result.connectivity ? "available" : "unavailable",
// Use translated error code if available
message: result.error_code ? t(`model.validation.${result.error_code}`) : (result.message || '')
message: connectivityMessage
})

// Display appropriate message based on result
if (result.connectivity) {
message.success(t('model.dialog.success.connectivityVerified'))
} else {
message.error(
result.error_code
? t(`model.validation.${result.error_code}`)
: t('model.dialog.error.connectivityFailed', { message: result.message })
)
}
} catch (error) {
setConnectivityStatus({
status: "unavailable",
message: t('model.dialog.error.verificationFailed', { error })
message: t('model.dialog.connectivity.status.unavailable')
})
message.error(t('model.dialog.error.verificationError', { error }))
} finally {
setVerifyingConnectivity(false)
}
}

// Get the connectivity status icon
const getConnectivityIcon = () => {
switch (connectivityStatus.status) {
case "checking":
return <LoadingOutlined style={{ color: '#1890ff' }} />
case "available":
return <CheckCircleOutlined style={{ color: '#52c41a' }} />
case "unavailable":
return <CloseCircleOutlined style={{ color: '#ff4d4f' }} />
default:
return null
}
}

// Get the connectivity status color
const getConnectivityColor = () => {
switch (connectivityStatus.status) {
case "checking":
return '#1890ff'
case "available":
return '#52c41a'
case "unavailable":
return '#ff4d4f'
default:
return '#d9d9d9'
}
}


const getModelList = async () => {
setShowModelList(true)
setLoadingModelList(true)
Expand Down Expand Up @@ -604,12 +570,14 @@ export const ModelAddDialog = ({ isOpen, onClose, onSuccess }: ModelAddDialogPro
<span className="text-sm font-medium text-gray-700">{t('model.dialog.connectivity.title')}</span>
{connectivityStatus.status && (
<div className="ml-2 flex items-center">
{getConnectivityIcon()}
{getConnectivityMeta(connectivityStatus.status).icon}
<span
className="ml-1 text-xs"
style={{ color: getConnectivityColor() }}
style={{ color: getConnectivityMeta(connectivityStatus.status).color }}
>
{t(`model.dialog.connectivity.status.${connectivityStatus.status}`)}
{connectivityStatus.status === 'available' && t('model.dialog.connectivity.status.available')}
{connectivityStatus.status === 'unavailable' && t('model.dialog.connectivity.status.unavailable')}
{connectivityStatus.status === 'checking' && t('model.dialog.status.verifying')}
</span>
</div>
)}
Expand All @@ -624,11 +592,6 @@ export const ModelAddDialog = ({ isOpen, onClose, onSuccess }: ModelAddDialogPro
{verifyingConnectivity ? t('model.dialog.button.verifying') : t('model.dialog.button.verify')}
</Button>
</div>
{connectivityStatus.message && (
<div className="text-xs text-gray-600">
{connectivityStatus.message}
</div>
)}
</div>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Modal, Input, Button, App } from 'antd'
import { useConfig } from '@/hooks/useConfig'
import { modelService } from '@/services/modelService'
import { ModelOption, ModelType } from '@/types/config'

import { getConnectivityIcon, getConnectivityColor, getConnectivityMeta, ConnectivityStatusType } from '@/lib/utils'

interface ModelEditDialogProps {
isOpen: boolean
Expand All @@ -29,6 +29,14 @@ export const ModelEditDialog = ({ isOpen, model, onClose, onSuccess }: ModelEdit
vectorDimension: "1024"
})
const [loading, setLoading] = useState(false)
const [verifyingConnectivity, setVerifyingConnectivity] = useState(false)
const [connectivityStatus, setConnectivityStatus] = useState<{
status: ConnectivityStatusType
message: string
}>({
status: null,
message: ""
})

useEffect(() => {
if (model) {
Expand All @@ -46,6 +54,10 @@ export const ModelEditDialog = ({ isOpen, model, onClose, onSuccess }: ModelEdit

const handleFormChange = (field: string, value: string) => {
setForm(prev => ({ ...prev, [field]: value }))
// If the key configuration item changes, clear the verification status
if (['url', 'apiKey', 'maxTokens', 'vectorDimension'].includes(field)) {
setConnectivityStatus({ status: null, message: "" })
}
}

const isEmbeddingModel = form.type === "embedding" || form.type === "multi_embedding"
Expand All @@ -54,6 +66,52 @@ export const ModelEditDialog = ({ isOpen, model, onClose, onSuccess }: ModelEdit
return form.name.trim() !== "" && form.url.trim() !== ""
}

// Verify model connectivity
const handleVerifyConnectivity = async () => {
if (!isFormValid()) {
message.warning(t('model.dialog.warning.incompleteForm'))
return
}

setVerifyingConnectivity(true)
setConnectivityStatus({ status: "checking", message: t('model.dialog.status.verifying') })

try {
const modelType = form.type as ModelType

const config = {
modelName: form.name,
modelType: modelType,
baseUrl: form.url,
apiKey: form.apiKey.trim() === "" ? "sk-no-api-key" : form.apiKey,
maxTokens: form.type === "embedding" ? parseInt(form.vectorDimension) : parseInt(form.maxTokens),
Comment thread
Jasonxia007 marked this conversation as resolved.
embeddingDim: form.type === "embedding" ? parseInt(form.vectorDimension) : undefined
}

const result = await modelService.verifyModelConfigConnectivity(config)

// Set connectivity status
let connectivityMessage = ''
if (result.connectivity) {
connectivityMessage = t('model.dialog.connectivity.status.available')
} else {
connectivityMessage = t('model.dialog.connectivity.status.unavailable')
}
setConnectivityStatus({
status: result.connectivity ? "available" : "unavailable",
message: connectivityMessage
})

} catch (error) {
setConnectivityStatus({
status: "unavailable",
message: t('model.dialog.connectivity.status.unavailable')
})
} finally {
setVerifyingConnectivity(false)
}
}

const handleSave = async () => {
if (!model) return
setLoading(true)
Expand All @@ -66,10 +124,10 @@ export const ModelEditDialog = ({ isOpen, model, onClose, onSuccess }: ModelEdit

await modelService.updateSingleModel({
model_id: model.id, // 浣跨敤妯″瀷鍚嶇О浣滀负ID
name: form.name,
displayName: form.displayName,
url: form.url,
apiKey: form.apiKey.trim() === "" ? "sk-no-api-key" : form.apiKey,
maxTokens: maxTokensValue,
...(maxTokensValue !== 0 ? { maxTokens: maxTokensValue } : {}),
source: model.source
})

Expand Down Expand Up @@ -128,11 +186,11 @@ export const ModelEditDialog = ({ isOpen, model, onClose, onSuccess }: ModelEdit
{/* Model Name */}
<div>
<label className="block mb-1 text-sm font-medium text-gray-700">
{t('model.dialog.label.name')}
{t('model.dialog.label.displayName')}
</label>
<Input
value={form.name}
onChange={(e) => handleFormChange('name', e.target.value)}
value={form.displayName}
onChange={(e) => handleFormChange('displayName', e.target.value)}
/>
</div>

Expand Down Expand Up @@ -171,6 +229,37 @@ export const ModelEditDialog = ({ isOpen, model, onClose, onSuccess }: ModelEdit
</div>
)}

{/* Connectivity verification area */}
<div className="p-3 bg-gray-50 border border-gray-200 rounded-md">
<div className="flex items-center justify-between mb-1">
<div className="flex items-center">
<span className="text-sm font-medium text-gray-700">{t('model.dialog.connectivity.title')}</span>
{connectivityStatus.status && (
<div className="ml-2 flex items-center">
{getConnectivityMeta(connectivityStatus.status).icon}
<span
className="ml-1 text-xs"
style={{ color: getConnectivityMeta(connectivityStatus.status).color }}
>
{connectivityStatus.status === 'available' && t('model.dialog.connectivity.status.available')}
{connectivityStatus.status === 'unavailable' && t('model.dialog.connectivity.status.unavailable')}
{connectivityStatus.status === 'checking' && t('model.dialog.status.verifying')}
</span>
</div>
)}
</div>
<Button
size="small"
type="default"
onClick={handleVerifyConnectivity}
loading={verifyingConnectivity}
disabled={!isFormValid() || verifyingConnectivity}
>
{verifyingConnectivity ? t('model.dialog.button.verifying') : t('model.dialog.button.verify')}
</Button>
</div>
</div>

<div className="flex justify-end space-x-3">
<Button onClick={onClose}>{t('common.button.cancel')}</Button>
<Button type="primary" onClick={handleSave} loading={loading} disabled={!isFormValid()}>
Expand Down
Loading