fix: add model display name

This commit is contained in:
wolfkill
2026-05-28 18:36:38 +08:00
committed by lyingbug
parent 4e58dd42cc
commit 43565c5d1b
21 changed files with 187 additions and 46 deletions

View File

@@ -52,6 +52,7 @@ type Model struct {
ID string `json:"id"`
TenantID uint `json:"tenant_id"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
Type ModelType `json:"type"`
Source ModelSource `json:"source"`
Description string `json:"description"`
@@ -64,6 +65,7 @@ type Model struct {
// CreateModelRequest model creation request
type CreateModelRequest struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Type ModelType `json:"type"`
Source ModelSource `json:"source"`
Description string `json:"description"`
@@ -74,6 +76,7 @@ type CreateModelRequest struct {
// UpdateModelRequest model update request
type UpdateModelRequest struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Parameters ModelParameters `json:"parameters"`
IsDefault bool `json:"is_default"`

View File

@@ -8,6 +8,7 @@ export interface ModelConfig {
id?: string;
tenant_id?: number;
name: string;
display_name?: string;
type: 'KnowledgeQA' | 'Embedding' | 'Rerank' | 'VLLM' | 'ASR';
source: 'local' | 'remote';
description?: string;
@@ -211,4 +212,3 @@ export function getWeKnoraCloudStatus(): Promise<WeKnoraCloudStatusResult> {
})
})
}

View File

@@ -867,7 +867,7 @@ const selectedModel = computed(() => {
// 模型展示名:本租户列表中有则用名称;若为共享智能体且其 model_id 不在本租户列表中则显示“共享智能体配置的模型”
const selectedModelDisplayName = computed(() => {
if (selectedModel.value) return selectedModel.value.name;
if (selectedModel.value) return modelDisplayName(selectedModel.value);
if (!selectedModelId.value) return t('input.notConfigured');
const isSharedAgent = !!settingsStore.selectedAgentSourceTenantId;
const modelFromAgent = agentModelId.value && agentModelId.value === selectedModelId.value;
@@ -875,6 +875,11 @@ const selectedModelDisplayName = computed(() => {
return t('input.notConfigured');
});
const modelDisplayName = (model: ModelConfig) => {
const displayName = model.display_name?.trim();
return displayName || model.name;
};
const updateModelDropdownPosition = () => {
const anchor = modelButtonRef.value;
if (!anchor) {
@@ -2296,7 +2301,8 @@ defineExpose({
<div v-for="model in availableModels" :key="model.id" class="model-option"
:class="{ selected: model.id === selectedModelId }" @click="handleModelChange(model.id || '')">
<div class="model-option-main">
<span class="model-option-name">{{ model.name }}</span>
<span class="model-option-name">{{ modelDisplayName(model) }}</span>
<span v-if="model.display_name" class="model-option-raw-name">{{ model.name }}</span>
<span v-if="model.source === 'remote'" class="model-badge-remote">{{ $t('input.remote') }}</span>
<span v-else-if="model.parameters?.parameter_size" class="model-badge-local">
{{ model.parameters.parameter_size }}
@@ -2404,7 +2410,8 @@ const getImgSrc = (url: string) => {
display: inline-flex;
width: 16px;
height: 16px;
flex-shrink: 0;
flex: 0 1 auto;
min-width: 0;
align-items: center;
justify-content: center;
border-radius: 3px;
@@ -3198,13 +3205,23 @@ const getImgSrc = (url: string) => {
.model-option-name {
font-size: 12px;
color: var(--td-text-color-primary, #222);
flex: 1;
flex-shrink: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.4;
}
.model-option-raw-name {
flex: 1;
min-width: 0;
font-size: 11px;
color: var(--td-text-color-placeholder, #b0b6bd);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.model-option-desc {
font-size: 11px;
color: var(--td-text-color-secondary, #8b9196);

View File

@@ -143,6 +143,12 @@
:disabled="formData.provider === 'weknoracloud' && wkcCredentialState !== 'configured'" />
</div>
<div class="form-item">
<label class="form-label">{{ $t('model.editor.displayNameLabel') }}</label>
<t-input v-model="formData.displayName" :placeholder="$t('model.editor.displayNamePlaceholder')" />
<p class="form-desc">{{ $t('model.editor.displayNameDesc') }}</p>
</div>
<div v-if="formData.provider !== 'weknoracloud'" class="form-item">
<label class="form-label required">{{ $t('model.editor.baseUrlLabel') }}</label>
<t-input v-model="formData.baseUrl" :placeholder="getBaseUrlPlaceholder()" />
@@ -268,6 +274,7 @@ interface ModelFormData {
source: 'local' | 'remote'
provider?: string // Provider identifier: openai, aliyun, zhipu, generic, etc.
modelName: string
displayName?: string
baseUrl?: string
apiKey?: string
dimension?: number
@@ -556,6 +563,7 @@ const formData = ref<ModelFormData>({
source: 'local',
provider: 'openai',
modelName: '',
displayName: '',
baseUrl: '',
apiKey: '',
dimension: undefined,
@@ -740,6 +748,7 @@ const resetForm = () => {
source: 'local',
provider: 'generic',
modelName: '',
displayName: '',
baseUrl: '',
apiKey: '',
dimension: undefined, // 默认不填,让用户手动输入或通过检测按钮获取

View File

@@ -14,11 +14,12 @@
v-for="model in models"
:key="model.id"
:value="model.id"
:label="model.name"
:label="modelDisplayName(model)"
>
<div class="model-option">
<t-icon name="check-circle-filled" class="model-icon" />
<span class="model-name">{{ model.name }}</span>
<span class="model-name">{{ modelDisplayName(model) }}</span>
<span v-if="model.display_name" class="model-raw-name">{{ model.name }}</span>
<t-tag v-if="model.is_builtin" size="small" theme="primary">{{ $t('model.builtinTag') }}</t-tag>
<t-tag v-if="model.is_default" size="small" theme="success">{{ $t('model.defaultTag') }}</t-tag>
</div>
@@ -72,6 +73,11 @@ const placeholderText = computed(() => {
return props.placeholder || t('model.selectModelPlaceholder')
})
const modelDisplayName = (model: ModelConfig) => {
const displayName = model.display_name?.trim()
return displayName || model.name
}
// 监听 allModels 变化,自动过滤当前类型的模型
watch(() => props.allModels, (newModels) => {
if (newModels && Array.isArray(newModels)) {
@@ -153,8 +159,22 @@ onMounted(() => {
}
.model-name {
flex: 1;
flex: 0 1 auto;
min-width: 0;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.model-raw-name {
flex: 1;
min-width: 0;
font-size: 12px;
color: var(--td-text-color-placeholder);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.add {
@@ -165,4 +185,3 @@ onMounted(() => {
}
}
</style>

View File

@@ -2974,6 +2974,9 @@ export default {
remoteAsr: 'e.g. whisper-1'
},
baseUrlLabel: 'Base URL',
displayNameLabel: 'Display name (optional)',
displayNamePlaceholder: 'e.g. Support QA model',
displayNameDesc: 'Used only in the UI. Runtime calls still use the model name above.',
baseUrlPlaceholder: 'e.g. https://api.openai.com/v1',
baseUrlPlaceholderVllm: 'e.g. http://localhost:11434/v1',
baseUrlPlaceholderAsr: 'e.g. https://api.openai.com/v1',
@@ -3517,6 +3520,7 @@ export default {
remote: 'Remote',
openaiCompatible: 'OpenAI-compatible'
},
rawModelName: 'Model name',
chat: {
title: 'Chat Models',
desc: 'Configure large language models for chatting',
@@ -3545,6 +3549,7 @@ export default {
toasts: {
nameRequired: 'Model name cannot be empty',
nameTooLong: 'Model name cannot exceed 100 characters',
displayNameTooLong: 'Display name cannot exceed 100 characters',
baseUrlRequired: 'Base URL is required for remote APIs',
baseUrlInvalid: 'Invalid Base URL, please enter a valid URL',
dimensionInvalid: 'Embedding dimension must be between 128 and 4096',

View File

@@ -2214,6 +2214,9 @@ export default {
remoteAsr: "예: whisper-1",
},
baseUrlLabel: "Base URL",
displayNameLabel: "표시 이름 (선택)",
displayNamePlaceholder: "예: 고객지원 QA 모델",
displayNameDesc: "UI 표시용으로만 사용되며 실제 호출은 위의 모델 이름을 사용합니다.",
baseUrlPlaceholder: "예: https://api.openai.com/v1",
baseUrlPlaceholderVllm: "예: http://localhost:11434/v1",
baseUrlPlaceholderAsr: "예: https://api.openai.com/v1",
@@ -3558,6 +3561,7 @@ export default {
remote: "Remote",
openaiCompatible: "OpenAI 호환",
},
rawModelName: "모델 이름",
chat: {
title: "대화 모델",
desc: "대화용 대규모 언어 모델 설정",
@@ -3586,6 +3590,7 @@ export default {
toasts: {
nameRequired: "모델 이름은 비워둘 수 없습니다",
nameTooLong: "모델 이름은 100자를 초과할 수 없습니다",
displayNameTooLong: "표시 이름은 100자를 초과할 수 없습니다",
baseUrlRequired: "Remote API 유형은 Base URL이 필수입니다",
baseUrlInvalid: "Base URL 형식이 올바르지 않습니다. 유효한 URL을 입력해주세요",
dimensionInvalid: "Embedding 모델은 유효한 벡터 차원(128-4096)을 입력해야 합니다",

View File

@@ -1933,6 +1933,9 @@ export default {
remoteAsr: 'например: whisper-1'
},
baseUrlLabel: 'Base URL',
displayNameLabel: 'Отображаемое имя (опционально)',
displayNamePlaceholder: 'например: модель поддержки',
displayNameDesc: 'Используется только в интерфейсе. Для вызовов по-прежнему используется имя модели выше.',
baseUrlPlaceholder: 'например: https://api.openai.com/v1',
baseUrlPlaceholderVllm: 'например: http://localhost:11434/v1',
baseUrlPlaceholderAsr: 'например: https://api.openai.com/v1',
@@ -3223,6 +3226,7 @@ export default {
remote: 'Удалённая',
openaiCompatible: 'Совместимо с OpenAI'
},
rawModelName: 'Имя модели',
embedding: {
title: 'Модели встраивания',
desc: 'Модели для векторизации текста',
@@ -3246,6 +3250,7 @@ export default {
toasts: {
nameRequired: 'Название модели не может быть пустым',
nameTooLong: 'Название модели не может превышать 100 символов',
displayNameTooLong: 'Отображаемое имя не может превышать 100 символов',
baseUrlRequired: 'Для удалённых API требуется Base URL',
baseUrlInvalid: 'Некорректный Base URL, укажите правильный адрес',
dimensionInvalid: 'Размерность встраивания должна быть 1284096',

View File

@@ -2193,6 +2193,9 @@ export default {
remoteAsr: "例如whisper-1",
},
baseUrlLabel: "Base URL",
displayNameLabel: "显示名称(可选)",
displayNamePlaceholder: "例如:客服问答模型",
displayNameDesc: "仅用于界面展示,实际调用仍使用上面的模型名称。",
baseUrlPlaceholder: "例如https://api.openai.com/v1",
baseUrlPlaceholderVllm: "例如http://localhost:11434/v1",
baseUrlPlaceholderAsr: "例如https://api.openai.com/v1",
@@ -3511,6 +3514,7 @@ export default {
remote: "Remote",
openaiCompatible: "OpenAI兼容",
},
rawModelName: "模型名称",
chat: {
title: "对话模型",
desc: "配置用于对话的大语言模型",
@@ -3539,6 +3543,7 @@ export default {
toasts: {
nameRequired: "模型名称不能为空",
nameTooLong: "模型名称不能超过100个字符",
displayNameTooLong: "显示名称不能超过100个字符",
baseUrlRequired: "Remote API 类型必须填写 Base URL",
baseUrlInvalid: "Base URL 格式不正确,请输入有效的 URL",
dimensionInvalid: "Embedding 模型必须填写有效的向量维度128-4096",

View File

@@ -57,7 +57,7 @@
</div>
<div class="model-card__body">
<div class="model-card__header">
<h3 class="model-card__title" :title="model.name">{{ model.name }}</h3>
<h3 class="model-card__title" :title="model.name">{{ modelDisplayName(model) }}</h3>
<span v-if="model.isBuiltin" class="model-card__pill">
{{ $t('modelSettings.builtinTag') }}
</span>
@@ -85,6 +85,9 @@
<span>{{ $t('model.editor.dimensionLabel') }} {{ model.dimension }}</span>
</template>
</div>
<div v-if="model.displayName" class="model-card__raw-name" :title="model.name">
{{ $t('modelSettings.rawModelName') }}: {{ model.name }}
</div>
<div v-if="model.baseUrl" class="model-card__url" :title="model.baseUrl">
{{ model.baseUrl }}
</div>
@@ -161,6 +164,7 @@ function convertToLegacyFormat(model: ModelConfig) {
return {
id: model.id!,
name: model.name,
displayName: model.display_name || '',
source: model.source,
modelName: model.name,
baseUrl: model.parameters.base_url || '',
@@ -228,6 +232,11 @@ const sourceLabel = (type: ModelType) => {
return t('modelSettings.source.remote')
}
const modelDisplayName = (model: any) => {
const displayName = typeof model.displayName === 'string' ? model.displayName.trim() : ''
return displayName || model.name
}
const emptyHint = computed(() => {
if (activeTypeFilter.value === 'all') return t('modelSettings.chat.empty')
const map: Record<ModelType, string> = {
@@ -285,6 +294,11 @@ const handleModelSave = async (modelData: any) => {
return
}
if (modelData.displayName && modelData.displayName.trim().length > 100) {
MessagePlugin.warning(t('modelSettings.toasts.displayNameTooLong'))
return
}
if (modelData.source === 'remote') {
if (!modelData.baseUrl || !modelData.baseUrl.trim()) {
MessagePlugin.warning(t('modelSettings.toasts.baseUrlRequired'))
@@ -326,6 +340,7 @@ const handleModelSave = async (modelData: any) => {
const apiModelData: ModelConfig = {
name: modelData.modelName.trim(),
display_name: modelData.displayName?.trim() || '',
type: getModelType(currentModelType.value),
source: modelData.source,
description: '',
@@ -460,6 +475,7 @@ const copyModel = async (_type: ModelType, modelId: string) => {
try {
const newModel: ModelConfig = {
name: generateCopyName(source.name),
display_name: source.display_name || '',
type: source.type,
source: source.source,
description: source.description || '',
@@ -737,6 +753,16 @@ onMounted(() => {
min-width: 0;
}
.model-card__raw-name {
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
font-size: 12px;
line-height: 1.4;
color: var(--td-text-color-placeholder);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.model-card__type {
font-weight: 500;
color: var(--td-text-color-secondary);

View File

@@ -15,18 +15,19 @@ import (
// stripped along with every other field that could leak how a particular
// tenant configured the upstream provider.
type ModelResponse struct {
ID string `json:"id"`
TenantID uint64 `json:"tenant_id"`
Name string `json:"name"`
Type types.ModelType `json:"type"`
Source types.ModelSource `json:"source"`
Description string `json:"description"`
Parameters ModelParametersDTO `json:"parameters"`
IsDefault bool `json:"is_default"`
IsBuiltin bool `json:"is_builtin"`
Status types.ModelStatus `json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID string `json:"id"`
TenantID uint64 `json:"tenant_id"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
Type types.ModelType `json:"type"`
Source types.ModelSource `json:"source"`
Description string `json:"description"`
Parameters ModelParametersDTO `json:"parameters"`
IsDefault bool `json:"is_default"`
IsBuiltin bool `json:"is_builtin"`
Status types.ModelStatus `json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Per-field "configured?" map. Omitted for builtin models (no
// per-tenant credentials). See MCPServiceResponse.Credentials.
Credentials map[string]CredentialFieldMetadata `json:"credentials,omitempty"`
@@ -44,7 +45,7 @@ type ModelParametersDTO struct {
Provider string `json:"provider"`
ExtraConfig map[string]string `json:"extra_config,omitempty"`
CustomHeaders map[string]string `json:"custom_headers,omitempty"`
SupportsVision bool `json:"supports_vision"`
SupportsVision bool `json:"supports_vision"`
AppID string `json:"app_id,omitempty"`
}
@@ -88,6 +89,7 @@ func NewModelResponse(m *types.Model) *ModelResponse {
ID: m.ID,
TenantID: m.TenantID,
Name: m.Name,
DisplayName: m.DisplayName,
Type: m.Type,
Source: m.Source,
Description: m.Description,

View File

@@ -11,8 +11,9 @@ import (
func TestModelResponse_OmitsSecrets(t *testing.T) {
m := &types.Model{
ID: "m-1",
Name: "gpt-x",
ID: "m-1",
Name: "gpt-x",
DisplayName: "Support QA",
Parameters: types.ModelParameters{
APIKey: "sk-real-api-key-do-not-leak",
AppSecret: "app-real-secret-do-not-leak",
@@ -39,6 +40,7 @@ func TestModelResponse_OmitsSecrets(t *testing.T) {
// Non-secret fields pass through.
assert.Contains(t, s, "appid-public-ok-to-show")
assert.Contains(t, s, "api.example.com")
assert.Contains(t, s, `"display_name":"Support QA"`)
}
func TestModelResponse_BuiltinStripsTenantConfig(t *testing.T) {

View File

@@ -38,6 +38,7 @@ func NewModelHandler(service interfaces.ModelService) *ModelHandler {
// Contains all fields required to create a new model in the system
type CreateModelRequest struct {
Name string `json:"name" binding:"required"`
DisplayName string `json:"display_name"`
Type types.ModelType `json:"type" binding:"required"`
Source types.ModelSource `json:"source" binding:"required"`
Description string `json:"description"`
@@ -89,6 +90,7 @@ func (h *ModelHandler) CreateModel(c *gin.Context) {
model := &types.Model{
TenantID: tenantID,
Name: secutils.SanitizeForLog(req.Name),
DisplayName: secutils.SanitizeForLog(req.DisplayName),
Type: types.ModelType(secutils.SanitizeForLog(string(req.Type))),
Source: req.Source,
Description: secutils.SanitizeForLog(req.Description),
@@ -201,6 +203,7 @@ func (h *ModelHandler) ListModels(c *gin.Context) {
// Contains fields that can be updated for an existing model
type UpdateModelRequest struct {
Name string `json:"name"`
DisplayName *string `json:"display_name"`
Description string `json:"description"`
Parameters types.ModelParameters `json:"parameters"`
Source types.ModelSource `json:"source"`
@@ -256,6 +259,9 @@ func (h *ModelHandler) UpdateModel(c *gin.Context) {
if req.Name != "" {
model.Name = req.Name
}
if req.DisplayName != nil {
model.DisplayName = secutils.SanitizeForLog(*req.DisplayName)
}
model.Description = req.Description
// SSRF validation for updated model BaseURL

View File

@@ -0,0 +1,20 @@
package handler
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestModelUpdateRequestDisplayNamePresence(t *testing.T) {
var omitted UpdateModelRequest
require.NoError(t, json.Unmarshal([]byte(`{"name":"gpt-4o"}`), &omitted))
assert.Nil(t, omitted.DisplayName)
var cleared UpdateModelRequest
require.NoError(t, json.Unmarshal([]byte(`{"display_name":""}`), &cleared))
require.NotNil(t, cleared.DisplayName)
assert.Equal(t, "", *cleared.DisplayName)
}

View File

@@ -35,22 +35,22 @@ const (
type ModelSource string
const (
ModelSourceLocal ModelSource = "local" // Local model
ModelSourceRemote ModelSource = "remote" // Remote model
ModelSourceAliyun ModelSource = "aliyun" // Aliyun DashScope model
ModelSourceZhipu ModelSource = "zhipu" // Zhipu model
ModelSourceVolcengine ModelSource = "volcengine" // Volcengine model
ModelSourceDeepseek ModelSource = "deepseek" // Deepseek model
ModelSourceHunyuan ModelSource = "hunyuan" // Hunyuan model
ModelSourceMinimax ModelSource = "minimax" // Minimax mode
ModelSourceOpenAI ModelSource = "openai" // OpenAI model
ModelSourceGemini ModelSource = "gemini" // Gemini model
ModelSourceMimo ModelSource = "mimo" // Mimo model
ModelSourceSiliconFlow ModelSource = "siliconflow" // SiliconFlow model
ModelSourceJina ModelSource = "jina" // Jina AI model
ModelSourceOpenRouter ModelSource = "openrouter" // OpenRouter model
ModelSourceNvidia ModelSource = "nvidia" // NVIDIA model
ModelSourceNovita ModelSource = "novita" // Novita AI model
ModelSourceLocal ModelSource = "local" // Local model
ModelSourceRemote ModelSource = "remote" // Remote model
ModelSourceAliyun ModelSource = "aliyun" // Aliyun DashScope model
ModelSourceZhipu ModelSource = "zhipu" // Zhipu model
ModelSourceVolcengine ModelSource = "volcengine" // Volcengine model
ModelSourceDeepseek ModelSource = "deepseek" // Deepseek model
ModelSourceHunyuan ModelSource = "hunyuan" // Hunyuan model
ModelSourceMinimax ModelSource = "minimax" // Minimax mode
ModelSourceOpenAI ModelSource = "openai" // OpenAI model
ModelSourceGemini ModelSource = "gemini" // Gemini model
ModelSourceMimo ModelSource = "mimo" // Mimo model
ModelSourceSiliconFlow ModelSource = "siliconflow" // SiliconFlow model
ModelSourceJina ModelSource = "jina" // Jina AI model
ModelSourceOpenRouter ModelSource = "openrouter" // OpenRouter model
ModelSourceNvidia ModelSource = "nvidia" // NVIDIA model
ModelSourceNovita ModelSource = "novita" // Novita AI model
ModelSourceAzureOpenAI ModelSource = "azure_openai" // Azure OpenAI model
)
@@ -65,9 +65,9 @@ type ModelParameters struct {
APIKey string `yaml:"api_key" json:"api_key"`
InterfaceType string `yaml:"interface_type" json:"interface_type"`
EmbeddingParameters EmbeddingParameters `yaml:"embedding_parameters" json:"embedding_parameters"`
ParameterSize string `yaml:"parameter_size" json:"parameter_size"` // Ollama model parameter size (e.g., "7B", "13B", "70B")
Provider string `yaml:"provider" json:"provider"` // Provider identifier: openai, aliyun, zhipu, generic
ExtraConfig map[string]string `yaml:"extra_config" json:"extra_config"` // Provider-specific configuration
ParameterSize string `yaml:"parameter_size" json:"parameter_size"` // Ollama model parameter size (e.g., "7B", "13B", "70B")
Provider string `yaml:"provider" json:"provider"` // Provider identifier: openai, aliyun, zhipu, generic
ExtraConfig map[string]string `yaml:"extra_config" json:"extra_config"` // Provider-specific configuration
// CustomHeaders 允许在调用远程模型 API 时附加自定义 HTTP 请求头,
// 用途类似 Python OpenAI SDK 的 extra_headers 参数,
// 常见场景包括透传企业网关鉴权信息、追踪 ID、路由标识等。
@@ -111,6 +111,8 @@ type Model struct {
TenantID uint64 `yaml:"tenant_id" json:"tenant_id"`
// Name of the model
Name string `yaml:"name" json:"name"`
// Optional user-facing display name. Runtime calls still use Name.
DisplayName string `yaml:"display_name" json:"display_name" gorm:"type:varchar(255);default:''"`
// Type of the model
Type ModelType `yaml:"type" json:"type"`
// Source of the model

View File

@@ -26,6 +26,7 @@ CREATE TABLE models (
id VARCHAR(64) PRIMARY KEY,
tenant_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
display_name VARCHAR(255) NOT NULL DEFAULT '',
type VARCHAR(50) NOT NULL,
source VARCHAR(50) NOT NULL,
description TEXT,

View File

@@ -36,6 +36,7 @@ CREATE TABLE IF NOT EXISTS models (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
display_name VARCHAR(255) NOT NULL DEFAULT '',
type VARCHAR(50) NOT NULL,
source VARCHAR(50) NOT NULL,
description TEXT,
@@ -212,4 +213,4 @@ WITH (
}'
);
CREATE INDEX ON embeddings USING hnsw ((embedding::halfvec(3584)) halfvec_cosine_ops) WITH (m = 16, ef_construction = 64) WHERE (dimension = 3584);
CREATE INDEX ON embeddings USING hnsw ((embedding::halfvec(798)) halfvec_cosine_ops) WITH (m = 16, ef_construction = 64) WHERE (dimension = 798);
CREATE INDEX ON embeddings USING hnsw ((embedding::halfvec(798)) halfvec_cosine_ops) WITH (m = 16, ef_construction = 64) WHERE (dimension = 798);

View File

@@ -31,6 +31,7 @@ CREATE TABLE IF NOT EXISTS models (
id VARCHAR(64) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
display_name VARCHAR(255) NOT NULL DEFAULT '',
type VARCHAR(50) NOT NULL,
source VARCHAR(50) NOT NULL,
description TEXT,

View File

@@ -52,6 +52,7 @@ CREATE TABLE IF NOT EXISTS models (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
display_name VARCHAR(255) NOT NULL DEFAULT '',
type VARCHAR(50) NOT NULL,
source VARCHAR(50) NOT NULL,
description TEXT,
@@ -201,4 +202,4 @@ CREATE INDEX IF NOT EXISTS idx_chunks_tenant_kg ON chunks(tenant_id, knowledge_i
CREATE INDEX IF NOT EXISTS idx_chunks_parent_id ON chunks(parent_chunk_id);
CREATE INDEX IF NOT EXISTS idx_chunks_chunk_type ON chunks(chunk_type);
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Initial database setup completed successfully!'; END $$;
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Initial database setup completed successfully!'; END $$;

View File

@@ -0,0 +1 @@
ALTER TABLE models DROP COLUMN IF EXISTS display_name;

View File

@@ -0,0 +1,10 @@
-- Migration: 000056_models_display_name
-- Add an optional user-facing display name for model rows. Runtime model
-- calls continue to use models.name; this field is presentation-only.
DO $$ BEGIN RAISE NOTICE '[Migration 000056] Adding models.display_name column'; END $$;
ALTER TABLE models
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '';
DO $$ BEGIN RAISE NOTICE '[Migration 000056] Done'; END $$;