perf: Model config page

This commit is contained in:
fit2cloud-chenyw
2025-07-04 15:21:04 +08:00
parent 3c2c12443b
commit 61404ee8b1
18 changed files with 446 additions and 275 deletions
@@ -0,0 +1,61 @@
"""019_upgrade_model
Revision ID: dcaecd481715
Revises: 863105882eba
Create Date: 2025-07-04 14:45:56.927188
"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'dcaecd481715'
down_revision = '863105882eba'
branch_labels = None
depends_on = None
def upgrade():
op.drop_index(op.f('ix_ai_model_id'), table_name='ai_model')
op.drop_table('ai_model')
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('ai_model',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('api_key', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column('api_domain', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column('protocol', sa.Integer(), nullable=False),
sa.Column('supplier', sa.Integer(), nullable=False),
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column('model_type', sa.Integer(), nullable=False),
sa.Column('base_model', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column('default_model', sa.Boolean(), nullable=False),
sa.Column('config', sa.Text(), nullable=False),
sa.Column('status', sa.Integer(), nullable=False),
sa.Column('create_time', sa.BigInteger(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_ai_model_id'), 'ai_model', ['id'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_ai_model_id'), table_name='ai_model')
op.drop_table('ai_model')
op.create_table('ai_model',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column('type', sa.Integer(), nullable=False),
sa.Column('api_key', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column('endpoint', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column('max_context_window', sa.Integer(), nullable=False),
sa.Column('temperature', sa.Float(), nullable=False),
sa.Column('status', sa.Boolean(), nullable=False),
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column('create_time', sa.BigInteger(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_ai_model_id'), 'ai_model', ['id'], unique=False)
# ### end Alembic commands ###
+33 -2
View File
@@ -1,9 +1,12 @@
import json
from pydantic import BaseModel
from typing import Optional, Dict, Any, Type
from abc import ABC, abstractmethod
from langchain_core.language_models import BaseLLM as LangchainBaseLLM
from langchain_openai import ChatOpenAI
from sqlmodel import Session, select
from common.core.db import engine
from apps.system.models.system_model import AiModelDetail
@@ -11,6 +14,7 @@ from apps.system.models.system_model import AiModelDetail
class LLMConfig(BaseModel):
"""Base configuration class for large language models"""
model_id: Optional[int] = None
model_type: str # Model type: openai/tongyi/vllm etc.
model_name: str # Specific model name
api_key: Optional[str] = None
@@ -93,7 +97,7 @@ class LLMFactory:
# todo
def get_llm_config(aimodel: AiModelDetail) -> LLMConfig:
""" def get_llm_config(aimodel: AiModelDetail) -> LLMConfig:
config = LLMConfig(
model_type="openai",
model_name=aimodel.name,
@@ -101,4 +105,31 @@ def get_llm_config(aimodel: AiModelDetail) -> LLMConfig:
api_base_url=aimodel.endpoint,
additional_params={"temperature": aimodel.temperature}
)
return config
return config """
def get_default_config() -> LLMConfig:
with Session(engine) as session:
db_model = session.exec(
select(AiModelDetail).where(AiModelDetail.default_model == True)
).first()
if not db_model:
raise ValueError("The system default model has not been set")
additional_params = {}
if db_model.config:
try:
config_raw = json.loads(db_model.config)
additional_params = {item["key"]: item["val"] for item in config_raw if "key" in item and "val" in item}
except Exception:
pass
# 构造 LLMConfig
return LLMConfig(
model_id=db_model.id,
model_type="openai",
model_name=db_model.base_model,
api_key=db_model.api_key,
api_base_url=db_model.api_domain,
additional_params=additional_params
)
+4 -11
View File
@@ -11,7 +11,7 @@ from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage, AI
from sqlalchemy import select
from sqlalchemy.orm import load_only
from apps.ai_model.model_factory import LLMConfig, LLMFactory, get_llm_config
from apps.ai_model.model_factory import LLMConfig, LLMFactory, get_default_config
from apps.chat.curd.chat import save_question, save_full_sql_message, save_full_sql_message_and_answer, save_sql, \
save_error_message, save_sql_exec_data, save_full_chart_message, save_full_chart_message_and_answer, save_chart, \
finish_record, save_full_analysis_message_and_answer, save_full_predict_message_and_answer, save_predict_data, \
@@ -21,7 +21,6 @@ from apps.datasource.crud.datasource import get_table_schema
from apps.datasource.models.datasource import CoreDatasource
from apps.db.db import exec_sql
from apps.system.crud.user import get_user_info
from apps.system.models.system_model import AiModelDetail
from common.core.deps import SessionDep, CurrentUser
warnings.filterwarnings("ignore")
@@ -58,13 +57,6 @@ class LLMService:
chat_question.engine = ds.type_name if ds.type != 'excel' else 'PostgreSQL'
# Get available AI model
aimodel = self.session.exec(select(AiModelDetail).where(
AiModelDetail.status == True,
AiModelDetail.api_key.is_not(None)
)).first()
if not aimodel and aimodel[0]:
raise Exception("No available AI model configuration found")
history_records: List[ChatRecord] = list(
map(lambda x: ChatRecord(**x.model_dump()), filter(lambda r: True if r.first_chat != True else False,
@@ -79,8 +71,9 @@ class LLMService:
self.ds = CoreDatasource(**ds.model_dump()) if ds else None
self.chat_question = chat_question
self.chat_question.ai_modal_id = aimodel[0].id
self.config = get_llm_config(aimodel[0])
self.config = get_default_config()
self.chat_question.ai_modal_id = self.config.model_id
# Create LLM instance through factory
llm_instance = LLMFactory.create_llm(self.config)
+61 -61
View File
@@ -1,90 +1,90 @@
from fastapi import APIRouter
from sqlmodel import select
import json
from typing import List, Union
from apps.system.schemas.ai_model_schema import AiModelConfigItem, AiModelCreator, AiModelEditor, AiModelGridItem
from fastapi import APIRouter, Query
from sqlmodel import func, select
from apps.system.models.system_model import AiModelDetail
from apps.system.schemas.system_schema import model_status
from common.core.deps import SessionDep
from common.core.pagination import Paginator
from common.core.schemas import PaginatedResponse, PaginationParams
from common.utils.time import get_timestamp
router = APIRouter(tags=["system/aimodel"], prefix="/system/aimodel")
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[AiModelDetail])
async def pager(
@router.get("", response_model=list[AiModelGridItem])
async def query(
session: SessionDep,
pageNum: int,
pageSize: int
keyword: Union[str, None] = Query(default=None, max_length=255)
):
pagination = PaginationParams(page=pageNum, size=pageSize)
paginator = Paginator(session)
filters = {}
return await paginator.get_paginated_response(
model=AiModelDetail,
pagination=pagination,
**filters)
statement = select(AiModelDetail.id,
AiModelDetail.name,
AiModelDetail.model_type,
AiModelDetail.base_model,
AiModelDetail.supplier,
AiModelDetail.default_model)
if keyword is not None:
statement = statement.where(AiModelDetail.name.like(f"%{keyword}%"))
items = session.exec(statement).all()
return items
@router.get("/{id}", response_model=AiModelDetail)
@router.get("/{id}", response_model=AiModelEditor)
async def get_model_by_id(
session: SessionDep,
id: int
):
term = session.get(AiModelDetail, id)
return term
db_model = session.get(AiModelDetail, id)
if not db_model:
raise ValueError(f"AiModelDetail with id {id} not found")
config_list: List[AiModelConfigItem] = []
if db_model.config:
try:
raw = json.loads(db_model.config)
config_list = [AiModelConfigItem(**item) for item in raw]
except Exception:
pass
data = AiModelDetail.model_validate(db_model).model_dump(exclude_unset=True)
data.pop("config", None)
data["config_list"] = config_list
return AiModelEditor(**data)
@router.post("", response_model=AiModelDetail)
@router.post("")
async def add_model(
session: SessionDep,
creator: AiModelDetail
creator: AiModelCreator
):
data = AiModelDetail.model_validate(creator)
data.create_time = get_timestamp()
session.add(data)
data = creator.model_dump(exclude_unset=True)
data["config"] = json.dumps([item.model_dump(exclude_unset=True) for item in creator.config_list])
data.pop("config_list", None)
detail = AiModelDetail.model_validate(data)
detail.create_time = get_timestamp()
count = session.exec(select(func.count(AiModelDetail.id))).one()
if count == 0:
detail.default_model = True
session.add(detail)
session.commit()
return creator
@router.put("", response_model=AiModelDetail)
async def update_terminology(
@router.put("")
async def update_model(
session: SessionDep,
model: AiModelDetail
editor: AiModelEditor
):
model.id = int(model.id)
term = session.exec(select(AiModelDetail).where(AiModelDetail.id == model.id)).first()
update_data = model.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(term, field, value)
session.add(term)
id = int(editor.id)
data = editor.model_dump(exclude_unset=True)
data["config"] = json.dumps([item.model_dump(exclude_unset=True) for item in editor.config_list])
data.pop("config_list", None)
db_model = session.get(AiModelDetail, id)
update_data = AiModelDetail.model_validate(data)
db_model.sqlmodel_update(update_data)
session.add(db_model)
session.commit()
return model
@router.delete("/{id}", response_model=AiModelDetail)
async def delete_terminology(
@router.delete("/{id}")
async def delete_model(
session: SessionDep,
id: int
):
term = session.exec(select(AiModelDetail).where(AiModelDetail.id == id)).first()
session.delete(term)
item = session.get(AiModelDetail, id)
session.delete(item)
session.commit()
return {
"message": f"AiModel with ID {id} deleted successfully."
}
@router.patch("/status", response_model=dict)
async def status(session: SessionDep, dto: model_status):
ids = dto.ids
status = dto.status
if not ids:
return {"message": "ids is empty"}
statement = select(AiModelDetail).where(AiModelDetail.id.in_(ids))
terms = session.exec(statement).all()
for term in terms:
term.status = status
session.add(term)
session.commit()
return {"message": f"AiModel with IDs {ids} updated successfully."}
+11 -9
View File
@@ -1,18 +1,20 @@
from sqlmodel import BigInteger, Field, SQLModel
from sqlmodel import BigInteger, Field, Text, SQLModel
from common.core.models import SnowflakeBase
class AiModelBase:
supplier: int = Field(nullable=False)
name: str = Field(max_length=255, nullable=False)
type: int = Field(nullable=False)
model_type: int = Field(nullable=False)
base_model: str = Field(max_length = 255, nullable=False)
default_model: bool = Field(default=False, nullable=False)
class AiModelDetail(AiModelBase, SnowflakeBase, table=True):
class AiModelDetail(SnowflakeBase, AiModelBase, table=True):
__tablename__ = "ai_model"
api_key: str | None = Field(max_length=255, nullable=True)
endpoint: str = Field(max_length=255, nullable=False)
max_context_window: int = Field(default=0)
temperature: float = Field(default=0.0)
status: bool = Field(default=True)
description: str | None = Field(max_length=255, nullable=True)
api_domain: str = Field(max_length=255, nullable=False)
protocol: int = Field(nullable=False, default = 1)
config: str = Field(sa_type = Text())
status: int = Field(nullable=False, default = 1)
create_time: int = Field(default=0, sa_type=BigInteger())
@@ -0,0 +1,28 @@
from typing import List
from pydantic import BaseModel
from common.core.schemas import BaseCreatorDTO
class AiModelItem(BaseModel):
name: str
model_type: int
base_model: str
supplier: int
default_model: bool = False
class AiModelGridItem(AiModelItem, BaseCreatorDTO):
pass
class AiModelConfigItem(BaseModel):
key: str
val: object
name: str
class AiModelCreator(AiModelItem):
api_domain: str
api_key: str
config_list: List[AiModelConfigItem]
class AiModelEditor(AiModelCreator, BaseCreatorDTO):
pass
+2 -2
View File
@@ -4,7 +4,7 @@ from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
from starlette.exceptions import HTTPException
from starlette.requests import Request
from common.core.config import settings
class ResponseMiddleware(BaseHTTPMiddleware):
def __init__(self, app):
super().__init__(app)
@@ -12,7 +12,7 @@ class ResponseMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
if isinstance(response, JSONResponse):
if isinstance(response, JSONResponse) or request.url.path == f"{settings.API_V1_STR}/openapi.json":
return response
if response.headers.get("content-type") == "application/json":
+9 -1
View File
@@ -46,4 +46,12 @@ class PaginatedResponse(BaseModel, Generic[T]):
total: int
page: int
size: int
total_pages: int
total_pages: int
class BaseCreatorDTO(BaseModel):
id: int
class Config:
json_encoders = {
int: lambda v: str(v) if isinstance(v, int) and v > (2**53 - 1) else v
}
+2 -2
View File
@@ -1,8 +1,8 @@
import { request } from '@/utils/request'
export const modelApi = {
pager: (pageNumber: number, pageSize: number) =>
request.get(`/system/aimodel/pager/${pageNumber}/${pageSize}`),
queryAll: (keyword?: string) =>
request.get('/system/aimodel', { params: keyword ? { keyword } : {} }),
add: (data: any) => request.post('/system/aimodel', data),
edit: (data: any) => request.put('/system/aimodel', data),
delete: (id: number) => request.delete(`/system/aimodel/${id}`),
+1 -1
View File
@@ -4,7 +4,7 @@ export interface SelectOption {
}
export const modelTypeOptions: SelectOption[] = [
{ label: 'OpenAI', value: 0 },
{ label: '大语言模型', value: 0 },
/* { label: 'Anthropic', value: 1 },
{ label: 'Baidu', value: 2 },
{ label: 'iFLYTEK', value: 3 },
+54
View File
@@ -0,0 +1,54 @@
import icon_alybl_colorful from '@/assets/model/icon_alybl_colorful.png'
import icon_bdyzn_colorful from '@/assets/model/icon_bdyzn_colorful.png'
import icon_deepseek_colorful from '@/assets/model/icon_deepseek_colorful.png'
type ModelConfig = Record<number, { api_domain: string; model_options: string[] }>
export const supplierList: Array<{
id: number
name: string
icon: any
model_config: ModelConfig
}> = [
{
id: 1,
name: '阿里云百炼',
icon: icon_alybl_colorful,
model_config: {
0: {
api_domain: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
model_options: ['qwen-plus'],
},
},
},
{
id: 2,
name: '千帆大模型I',
icon: icon_bdyzn_colorful,
model_config: {
0: {
api_domain: 'https://qianfan.baidubce.com/v2/',
model_options: ['ernie-x1-turbo-32k'],
},
},
},
{
id: 3,
name: 'DeepSeek',
icon: icon_deepseek_colorful,
model_config: {
0: {
api_domain: 'https://api.deepseek.com',
model_options: ['deepseek-chat'],
},
},
},
]
export const base_model_options = (supplier_id: number, model_type?: number) => {
const supplier = get_supplier(supplier_id)
return supplier?.model_config[model_type || 0].model_options
}
export const get_supplier = (supplier_id: number) => {
return supplierList.find((item: any) => item.id === supplier_id)
}
+13 -6
View File
@@ -1,8 +1,8 @@
<script lang="ts" setup>
import azure from '@/assets/model/icon_Azure_OpenAI_colorful.png'
import delIcon from '@/assets/svg/icon_delete.svg'
import edit from '@/assets/svg/icon_edit_outlined.svg'
import { get_supplier } from '@/entity/supplier'
import { computed } from 'vue'
const props = withDefaults(
defineProps<{
name: string
@@ -10,6 +10,7 @@ const props = withDefaults(
baseModle: string
id?: string
isDefault?: boolean
supplier?: number
}>(),
{
name: '-',
@@ -17,9 +18,15 @@ const props = withDefaults(
baseModle: '-',
id: '-',
isDefault: false,
supplier: 0,
}
)
const current_supplier = computed(() => {
if (!props.supplier) {
return null
}
return get_supplier(props.supplier)
})
const emits = defineEmits(['edit', 'del'])
const handleEdit = () => {
@@ -27,16 +34,16 @@ const handleEdit = () => {
}
const handleDel = () => {
emits('del', props.id)
emits('del', { id: props.id, name: props.name, default_model: props.isDefault })
}
</script>
<template>
<div class="card">
<div class="name-icon">
<img :src="azure" width="32px" height="32px" />
<img :src="current_supplier?.icon" width="32px" height="32px" />
<span class="name">{{ name }}</span>
<span class="default">{{ $t('model.default_model') }}</span>
<span v-if="isDefault" class="default">{{ $t('model.default_model') }}</span>
</div>
<div class="type-value">
<span class="type">{{ $t('model.model_type') }}</span>
+90 -79
View File
@@ -5,7 +5,6 @@ import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlin
import icon_admin_outlined from '@/assets/svg/icon_admin_outlined.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
import icon_Azure_OpenAI_colorful from '@/assets/model/icon_Azure_OpenAI_colorful.png'
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
import icon_close_outlined from '@/assets/svg/operate/ope-close.svg'
import ModelList from './ModelList.vue'
@@ -15,12 +14,15 @@ import { modelApi } from '@/api/system'
import Card from './Card.vue'
import { getModelTypeName } from '@/entity/CommonEntity.ts'
import { useI18n } from 'vue-i18n'
import { get_supplier } from '@/entity/supplier'
interface Model {
name: string
type: string
baseModle: string
model_type: string
base_model: string
id?: string
default_model: boolean
supplier: number
}
const { t } = useI18n()
@@ -32,34 +34,18 @@ const activeStep = ref(0)
const activeName = ref('')
const modelFormRef = ref()
const state = reactive({
reactive({
form: {
id: '',
name: '',
type: 0,
model_type: 0,
api_key: '',
endpoint: '',
max_context_window: 0,
temperature: 0,
status: false,
description: '',
},
pageInfo: {
currentPage: 1,
pageSize: 20,
total: 0,
api_domain: '',
},
selectedIds: [],
})
const modelList = shallowRef([] as Model[])
const defaultModelList = shallowRef([
{
img: icon_Azure_OpenAI_colorful,
name: '千帆大模型-chinese',
},
] as (Model & { img: string })[])
const currentDefaultModel = ref('')
const modelListWithSearch = computed(() => {
if (!keywords.value) return modelList.value
return modelList.value.filter((ele) =>
@@ -68,32 +54,37 @@ const modelListWithSearch = computed(() => {
})
const beforeClose = () => {
modelConfigvVisible.value = false
ElMessage.success(t('model.set_successfully'))
// ElMessageBox.confirm(t('model.system_default_model', { msg: msg}), {
// confirmButtonType: 'primary',
// tip: t('model.operate_with_caution'),
// confirmButtonText: t('datasource.confirm'),
// cancelButtonText: t('common.cancel'),
// customClass: 'confirm-no_icon',
// autofocus: false,
// })
// ElMessageBox.confirm(t('model.system_default_model', { msg: msg}), {
// confirmButtonType: 'danger',
// confirmButtonText: t('dashboard.delete'),
// cancelButtonText: t('common.cancel'),
// customClass: 'confirm-no_icon',
// autofocus: false,
// })
}
const defaultModelListWithSearch = computed(() => {
if (!defaultModelKeywords.value) return defaultModelList.value
return defaultModelList.value.filter((ele) =>
ele.name.toLowerCase().includes(defaultModelKeywords.value.toLowerCase())
)
let tempModelList = modelList.value
if (defaultModelKeywords.value) {
tempModelList = tempModelList.filter((ele) =>
ele.name.toLowerCase().includes(defaultModelKeywords.value.toLowerCase())
)
}
return tempModelList.map((item: any) => {
item['supplier_item'] = get_supplier(item.supplier)
return item
})
})
const handleDefaultModelChange = (item: any) => {
currentDefaultModel.value = item.name
ElMessageBox.confirm(`是否设置 ${item.name} 为系统默认模型?`, {
confirmButtonType: 'primary',
tip: '系统默认模型被替换后,智能问数的结果将会受到影响,请谨慎操作。',
confirmButtonText: t('datasource.confirm'),
cancelButtonText: t('common.cancel'),
customClass: 'confirm-no_icon',
autofocus: false,
callback: (val: string) => {
if (val === 'confirm') {
modelList.value.forEach((ele: any) => {
ele.default_model = ele.id === item.id
})
ElMessage.success('设置成功')
}
},
})
}
const formatKeywords = (item: string) => {
@@ -103,47 +94,66 @@ const formatKeywords = (item: string) => {
`<span class="isSearch">${defaultModelKeywords.value}</span>`
)
}
const handleAddModel = () => {
activeStep.value = 0
modelConfigvVisible.value = true
}
const handleEditModel = (id: any) => {
activeStep.value = 1
modelApi.query(id).then((res: any) => {
modelConfigvVisible.value = true
nextTick(() => {
modelFormRef.value.initForm({ ...res, temperature: res.temperature * 100 })
modelFormRef.value.initForm({ ...res })
})
})
}
const deleteHandler = (id: any) => {
ElMessageBox.confirm('Are you sure to delete?', 'Warning', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
})
.then(() => {
modelApi.delete(id).then(() => {
ElMessage({
type: 'success',
message: 'Delete completed',
const deleteHandler = (item: any) => {
if (item.default_model) {
ElMessageBox.confirm(`无法删除模型:${item.name}?`, {
confirmButtonType: 'primary',
tip: '该模型为系统默认模型,请先设置其他模型为系统默认模型,再删除此模型。',
showConfirmButton: false,
confirmButtonText: '确定',
cancelButtonText: '知道了',
customClass: 'confirm-no_icon',
autofocus: false,
callback: (val: string) => {
console.log(val)
},
})
return
}
ElMessageBox.confirm(`是否删除模型:${item.name}?`, {
confirmButtonType: 'danger',
confirmButtonText: '删除',
cancelButtonText: '取消',
customClass: 'confirm-no_icon',
autofocus: false,
callback: (value: string) => {
if (value === 'confirm') {
modelApi.delete(item.id).then(() => {
ElMessage({
type: 'success',
message: '删除成功',
})
search()
})
search()
})
})
.catch(() => {
ElMessage({
type: 'info',
message: 'Delete canceled',
})
})
}
},
})
}
const clickModel = (ele: any) => {
activeStep.value = 1
activeName.value = ele.name
supplierChang(ele)
}
const clickModelSide = (ele: any) => {
const supplierChang = (ele: any) => {
activeName.value = ele.name
nextTick(() => {
modelFormRef.value.supplierChang({ ...ele })
})
}
const cancel = () => {
@@ -155,13 +165,12 @@ const preStep = () => {
}
const saveModel = () => {
modelFormRef.value.submit()
modelFormRef.value.submitModle()
}
const search = () => {
modelApi.pager(state.pageInfo.currentPage, state.pageInfo.pageSize).then((res: any) => {
modelList.value = res.items
state.pageInfo.total = res.total
modelApi.queryAll().then((res: any) => {
modelList.value = res
})
}
search()
@@ -234,10 +243,10 @@ const submit = (item: any) => {
v-for="ele in defaultModelListWithSearch"
:key="ele.name"
class="popover-item"
:class="currentDefaultModel === ele.name && 'isActive'"
:class="ele.default_model && 'isActive'"
@click="handleDefaultModelChange(ele)"
>
<img :src="ele.img" width="24px" height="24px" />
<img :src="ele.supplier_item.icon" width="24px" height="24px" />
<div class="model-name" v-html="formatKeywords(ele.name)"></div>
<el-icon size="16" class="done">
<icon_done_outlined></icon_done_outlined>
@@ -250,7 +259,7 @@ const submit = (item: any) => {
</div>
</el-popover>
<el-button type="primary">
<el-button type="primary" @click="handleAddModel">
<template #icon>
<icon_add_outlined></icon_add_outlined>
</template>
@@ -265,16 +274,18 @@ const submit = (item: any) => {
/>
<div v-else class="card-content">
<Card
<card
v-for="ele in modelListWithSearch"
:id="ele.id"
:key="ele.id"
:name="ele.name"
:modle-type="getModelTypeName(ele.type)"
:base-modle="ele.baseModle"
:supplier="ele.supplier"
:modle-type="getModelTypeName(ele['model_type'])"
:base-modle="ele['base_model']"
:is-default="ele['default_model']"
@edit="handleEditModel"
@del="deleteHandler"
></Card>
></card>
</div>
<el-drawer
v-model="modelConfigvVisible"
@@ -305,7 +316,7 @@ const submit = (item: any) => {
<ModelListSide
v-if="activeStep === 1"
:active-name="activeName"
@click-model="clickModelSide"
@click-model="supplierChang"
></ModelListSide>
<ModelForm
v-if="activeStep === 1"
+52 -43
View File
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ref, reactive, onMounted, nextTick, computed } from 'vue'
import arrow_down from '@/assets/svg/arrow-down.svg'
import dashboard_info from '@/assets/svg/dashboard-info.svg'
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
@@ -7,6 +7,7 @@ import icon_delete from '@/assets/svg/icon_delete.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import ParamsForm from './ParamsForm.vue'
import { modelTypeOptions } from '@/entity/CommonEntity.ts'
import { base_model_options } from '@/entity/supplier'
import { useI18n } from 'vue-i18n'
withDefaults(
@@ -18,36 +19,37 @@ withDefaults(
}
)
interface Options {
label: string
value: string
}
interface Form {
name: string
id: string
interface ParamsFormData {
key?: string
val?: string
name?: string
id?: string
}
const { t } = useI18n()
const modelForm = reactive({
id: '',
supplier: 0,
name: '',
type: 0,
model_type: 0,
base_model: '',
api_key: '',
endpoint: '',
max_context_window: 0,
temperature: 0,
status: false,
description: '',
api_domain: '',
config_list: [],
})
let isCreate = false
const modelRef = ref()
const paramsFormRef = ref()
const advancedSetting = ref([] as Form[])
const modelList = ref([] as Options[])
const advancedSetting = ref([] as ParamsFormData[])
const paramsFormDrawer = ref(false)
const advancedSettingExpand = ref(false)
const configExpand = ref(false)
const modelList = computed(() => {
if (!modelForm.supplier) {
return []
}
return base_model_options(modelForm.supplier, modelForm.model_type)
})
const handleParamsEdite = (ele?: any) => {
isCreate = false
paramsFormDrawer.value = true
@@ -65,19 +67,19 @@ const handleParamsCreate = () => {
}
const handleParamsDel = (item: any) => {
advancedSetting.value = advancedSetting.value.filter((ele) => ele.name !== item.name)
advancedSetting.value = advancedSetting.value.filter((ele) => ele.key !== item.key)
}
const rules = {
type: [
model_type: [
{
required: true,
message: 'type',
trigger: 'change',
},
],
endpoint: [{ required: true, message: 'endpoint', trigger: 'blur' }],
modelName: [{ required: true, message: t('model.the_basic_model_de'), trigger: 'blur' }],
api_domain: [{ required: true, message: 'api_domain', trigger: 'blur' }],
base_model: [{ required: true, message: t('model.the_basic_model_de'), trigger: 'blur' }],
name: [{ required: true, message: t('model.the_basic_model'), trigger: 'blur' }],
api_key: [{ required: true, message: 'api_key', trigger: 'blur' }],
}
@@ -111,11 +113,23 @@ const submit = (item: any) => {
const cancel = () => {
paramsFormDrawer.value = false
}
const initForm = (item: any) => {
const supplierChang = (supplier: any) => {
modelForm.supplier = supplier.id
const config = supplier.model_config[modelForm.model_type || 0]
modelForm.api_domain = config.api_domain
if (!modelForm.id) {
modelForm.name = supplier.name
}
}
const initForm = (item?: any) => {
modelForm.id = ''
modelRef.value.clearValidate()
Object.assign(modelForm, { ...item })
if (item) {
Object.assign(modelForm, { ...item })
if (item?.config_list?.length) {
advancedSetting.value = item.config_list
}
}
}
const emits = defineEmits(['submit'])
@@ -124,8 +138,7 @@ const submitModle = () => {
if (res) {
emits('submit', {
...modelForm,
temperature: modelForm.temperature / 100,
advancedSetting: [...advancedSetting.value],
config_list: [...advancedSetting.value],
})
}
})
@@ -134,6 +147,7 @@ const submitModle = () => {
defineExpose({
initForm,
submitModle,
supplierChang,
})
</script>
@@ -160,7 +174,7 @@ defineExpose({
<el-input v-model="modelForm.name" />
</el-form-item>
<el-form-item prop="type" :label="t('model.model_type')">
<el-select v-model="modelForm.type" style="width: 100%">
<el-select v-model="modelForm.model_type" style="width: 100%">
<el-option
v-for="item in modelTypeOptions"
:key="item.value"
@@ -174,18 +188,13 @@ defineExpose({
><span class="custom-require_danger">{{ t('model.basic_model') }}</span>
<span class="enter">{{ t('model.enter_to_add') }}</span>
</template>
<el-select v-model="modelForm.type" style="width: 100%">
<el-option
v-for="item in modelList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-select v-model="modelForm['base_model']" style="width: 100%">
<el-option v-for="item in modelList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item prop="endpoint" :label="t('model.api_domain_name')">
<el-form-item prop="api_domain" :label="t('model.api_domain_name')">
<el-input
v-model="modelForm.endpoint"
v-model="modelForm.api_domain"
:placeholder="
$t('datasource.please_enter') + $t('common.empty') + $t('model.api_domain_name')
"
@@ -202,15 +211,15 @@ defineExpose({
</el-form>
<div
class="advance-setting"
:class="advancedSettingExpand && 'expand'"
@click="advancedSettingExpand = !advancedSettingExpand"
:class="configExpand && 'expand'"
@click="configExpand = !configExpand"
>
{{ t('model.advanced_settings') }}
<el-icon size="16">
<arrow_down></arrow_down>
</el-icon>
</div>
<div v-if="advancedSettingExpand" class="model-params">
<div v-if="configExpand" class="model-params">
{{ t('model.model_parameters') }}
<span class="add" @click="handleParamsCreate">
<el-icon size="16">
@@ -220,11 +229,11 @@ defineExpose({
</span>
</div>
<div v-if="advancedSettingExpand" class="params-table">
<div v-if="configExpand" class="params-table">
<el-table :data="advancedSetting" style="width: 100%">
<el-table-column prop="params" :label="t('model.parameters')" width="280" />
<el-table-column prop="key" :label="t('model.parameters')" width="280" />
<el-table-column prop="name" :label="t('model.display_name')" width="280" />
<el-table-column prop="value" :label="t('model.parameter_value')" />
<el-table-column prop="val" :label="t('model.parameter_value')" />
<el-table-column
fixed="right"
width="80"
+8 -24
View File
@@ -1,30 +1,14 @@
<script lang="ts" setup>
import { ref, shallowRef, computed } from 'vue'
import { ref, computed } from 'vue'
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
import icon_Azure_OpenAI_colorful from '@/assets/model/icon_Azure_OpenAI_colorful.png'
interface Model {
name: string
modleType: string
baseModle: string
img?: string
}
import { supplierList } from '@/entity/supplier'
const keywords = ref('')
const modelList = shallowRef([
{
img: icon_Azure_OpenAI_colorful,
name: '千帆大模型-chinese',
},
{
img: icon_Azure_OpenAI_colorful,
name: '千帆大模-chinese',
},
] as Model[])
const modelListWithSearch = computed(() => {
if (!keywords.value) return modelList.value
return modelList.value.filter((ele) =>
ele.name.toLowerCase().includes(keywords.value.toLowerCase())
)
if (!keywords.value) return supplierList
return supplierList.filter((ele) => ele.name.toLowerCase().includes(keywords.value.toLowerCase()))
})
const emits = defineEmits(['clickModel'])
const handleModelClick = (item: any) => {
@@ -54,7 +38,7 @@ const handleModelClick = (item: any) => {
class="model"
@click="handleModelClick(ele)"
>
<img width="32px" height="32px" :src="ele.img" />
<img width="32px" height="32px" :src="ele.icon" />
<span class="name">{{ ele.name }}</span>
</div>
<EmptyBackground
@@ -85,7 +69,7 @@ const handleModelClick = (item: any) => {
display: flex;
justify-content: space-between;
height: calc(100% - 40px);
gap: 16px;
.model:nth-child(odd) {
margin-left: 0;
}
@@ -1,14 +1,9 @@
<script lang="ts" setup>
import { ref, shallowRef, computed } from 'vue'
import { ref, computed } from 'vue'
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
import icon_Azure_OpenAI_colorful from '@/assets/model/icon_Azure_OpenAI_colorful.png'
interface Model {
name: string
modleType: string
baseModle: string
img?: string
}
import { supplierList } from '@/entity/supplier'
withDefaults(
defineProps<{
activeName: string
@@ -18,21 +13,10 @@ withDefaults(
}
)
const keywords = ref('')
const modelList = shallowRef([
{
img: icon_Azure_OpenAI_colorful,
name: '千帆大模型-chinese',
},
{
img: icon_Azure_OpenAI_colorful,
name: '千帆大模-chinese',
},
] as Model[])
const modelListWithSearch = computed(() => {
if (!keywords.value) return modelList.value
return modelList.value.filter((ele) =>
ele.name.toLowerCase().includes(keywords.value.toLowerCase())
)
if (!keywords.value) return supplierList
return supplierList.filter((ele) => ele.name.toLowerCase().includes(keywords.value.toLowerCase()))
})
const emits = defineEmits(['clickModel'])
const handleModelClick = (item: any) => {
@@ -62,7 +46,7 @@ const handleModelClick = (item: any) => {
:class="activeName === ele.name && 'isActive'"
@click="handleModelClick(ele)"
>
<img width="32px" height="32px" :src="ele.img" />
<img width="32px" height="32px" :src="ele.icon" />
<span class="name">{{ ele.name }}</span>
</div>
<EmptyBackground
@@ -6,9 +6,8 @@ const { t } = useI18n()
const paramsRef = ref()
const paramsForm = reactive({
name: '',
params: '',
value: null,
type: 0,
key: '',
val: '',
})
const rules = {
@@ -19,14 +18,14 @@ const rules = {
trigger: 'blur',
},
],
params: [
key: [
{
required: true,
message: t('datasource.please_enter') + t('common.empty') + t('model.parameters'),
trigger: 'blur',
},
],
value: [
val: [
{
required: true,
message: t('datasource.please_enter') + t('common.empty') + t('model.parameter_value'),
@@ -66,9 +65,9 @@ defineExpose({
:model="paramsForm"
style="width: 100%"
>
<el-form-item prop="params" :label="$t('model.parameters')">
<el-form-item prop="key" :label="$t('model.parameters')">
<el-input
v-model="paramsForm.params"
v-model="paramsForm.key"
:placeholder="$t('datasource.please_enter') + $t('common.empty') + $t('model.parameters')"
/>
</el-form-item>
@@ -80,9 +79,9 @@ defineExpose({
"
/>
</el-form-item>
<el-form-item prop="value" :label="$t('model.parameter_value')">
<el-form-item prop="val" :label="$t('model.parameter_value')">
<el-input
v-model="paramsForm.value"
v-model="paramsForm.val"
:placeholder="
$t('datasource.please_enter') + $t('common.empty') + $t('model.parameter_value')
"
+2 -2
View File
@@ -265,10 +265,10 @@ const onFormClose = () => {
}
const search = () => {
modelApi.pager(state.pageInfo.currentPage, state.pageInfo.pageSize).then((res: any) => {
/* modelApi.pager(state.pageInfo.currentPage, state.pageInfo.pageSize).then((res: any) => {
state.tableData = res.items
state.pageInfo.total = res.total
})
}) */
}
const addTerm = () => {
const param: any = {