diff --git a/backend/alembic/versions/019_upgrade_model.py b/backend/alembic/versions/019_upgrade_model.py new file mode 100644 index 00000000..3e55c1ef --- /dev/null +++ b/backend/alembic/versions/019_upgrade_model.py @@ -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 ### diff --git a/backend/apps/ai_model/model_factory.py b/backend/apps/ai_model/model_factory.py index 2aa5ff39..605c1d03 100644 --- a/backend/apps/ai_model/model_factory.py +++ b/backend/apps/ai_model/model_factory.py @@ -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 \ No newline at end of file + 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 + ) + \ No newline at end of file diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index 0a8d3c86..ae3f718e 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -13,7 +13,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, \ @@ -23,7 +23,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.config import settings from common.core.deps import SessionDep, CurrentUser @@ -61,13 +60,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, @@ -83,8 +75,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) diff --git a/backend/apps/system/api/aimodel.py b/backend/apps/system/api/aimodel.py index eabc1fd6..52074a57 100644 --- a/backend/apps/system/api/aimodel.py +++ b/backend/apps/system/api/aimodel.py @@ -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."} + \ No newline at end of file diff --git a/backend/apps/system/models/system_model.py b/backend/apps/system/models/system_model.py index cc35045e..89d7d997 100644 --- a/backend/apps/system/models/system_model.py +++ b/backend/apps/system/models/system_model.py @@ -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()) \ No newline at end of file diff --git a/backend/apps/system/schemas/ai_model_schema.py b/backend/apps/system/schemas/ai_model_schema.py new file mode 100644 index 00000000..5bfd206b --- /dev/null +++ b/backend/apps/system/schemas/ai_model_schema.py @@ -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 \ No newline at end of file diff --git a/backend/common/core/response_middleware.py b/backend/common/core/response_middleware.py index f0979411..114bdf99 100644 --- a/backend/common/core/response_middleware.py +++ b/backend/common/core/response_middleware.py @@ -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": diff --git a/backend/common/core/schemas.py b/backend/common/core/schemas.py index a4637ff8..30746692 100644 --- a/backend/common/core/schemas.py +++ b/backend/common/core/schemas.py @@ -46,4 +46,12 @@ class PaginatedResponse(BaseModel, Generic[T]): total: int page: int size: int - total_pages: int \ No newline at end of file + 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 + } \ No newline at end of file diff --git a/frontend/eslint.config.cjs b/frontend/eslint.config.cjs index 109373cb..9d87cc93 100644 --- a/frontend/eslint.config.cjs +++ b/frontend/eslint.config.cjs @@ -28,6 +28,7 @@ module.exports = typescriptEslint.config( 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'vue/multi-word-component-names': 'off', 'vue/no-required-prop-with-default': 'off', + 'vue/no-v-html': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/ban-ts-comment': 'warn', diff --git a/frontend/src/api/system.ts b/frontend/src/api/system.ts index 63dceff5..0153122c 100644 --- a/frontend/src/api/system.ts +++ b/frontend/src/api/system.ts @@ -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}`), diff --git a/frontend/src/assets/LOGO-fold.svg b/frontend/src/assets/LOGO-fold.svg new file mode 100644 index 00000000..6a58bc90 --- /dev/null +++ b/frontend/src/assets/LOGO-fold.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/LOGO.svg b/frontend/src/assets/LOGO.svg new file mode 100644 index 00000000..06ac01fe --- /dev/null +++ b/frontend/src/assets/LOGO.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/datasource/icon_PostgreSQL.png b/frontend/src/assets/datasource/icon_PostgreSQL.png new file mode 100644 index 00000000..b120f82b Binary files /dev/null and b/frontend/src/assets/datasource/icon_PostgreSQL.png differ diff --git a/frontend/src/assets/datasource/icon_SQL_Server.png b/frontend/src/assets/datasource/icon_SQL_Server.png new file mode 100644 index 00000000..f2b4fcd2 Binary files /dev/null and b/frontend/src/assets/datasource/icon_SQL_Server.png differ diff --git a/frontend/src/assets/datasource/icon_excel.png b/frontend/src/assets/datasource/icon_excel.png new file mode 100644 index 00000000..6fcd47a0 Binary files /dev/null and b/frontend/src/assets/datasource/icon_excel.png differ diff --git a/frontend/src/assets/datasource/icon_mysql.png b/frontend/src/assets/datasource/icon_mysql.png new file mode 100644 index 00000000..3a094258 Binary files /dev/null and b/frontend/src/assets/datasource/icon_mysql.png differ diff --git a/frontend/src/assets/datasource/icon_oracle.png b/frontend/src/assets/datasource/icon_oracle.png new file mode 100644 index 00000000..26b323d1 Binary files /dev/null and b/frontend/src/assets/datasource/icon_oracle.png differ diff --git a/frontend/src/assets/img/no-search_result.png b/frontend/src/assets/img/no-search_result.png new file mode 100644 index 00000000..7bb97e87 Binary files /dev/null and b/frontend/src/assets/img/no-search_result.png differ diff --git a/frontend/src/assets/model/icon_Azure_OpenAI_colorful.png b/frontend/src/assets/model/icon_Azure_OpenAI_colorful.png new file mode 100644 index 00000000..470aec02 Binary files /dev/null and b/frontend/src/assets/model/icon_Azure_OpenAI_colorful.png differ diff --git a/frontend/src/assets/model/icon_alybl_colorful.png b/frontend/src/assets/model/icon_alybl_colorful.png new file mode 100644 index 00000000..5d70e94d Binary files /dev/null and b/frontend/src/assets/model/icon_alybl_colorful.png differ diff --git a/frontend/src/assets/model/icon_bdyzn_colorful.png b/frontend/src/assets/model/icon_bdyzn_colorful.png new file mode 100644 index 00000000..bebaae39 Binary files /dev/null and b/frontend/src/assets/model/icon_bdyzn_colorful.png differ diff --git a/frontend/src/assets/model/icon_deepseek_colorful.png b/frontend/src/assets/model/icon_deepseek_colorful.png new file mode 100644 index 00000000..29b11250 Binary files /dev/null and b/frontend/src/assets/model/icon_deepseek_colorful.png differ diff --git a/frontend/src/assets/model/icon_gemini_colorful.png b/frontend/src/assets/model/icon_gemini_colorful.png new file mode 100644 index 00000000..de4cc23c Binary files /dev/null and b/frontend/src/assets/model/icon_gemini_colorful.png differ diff --git a/frontend/src/assets/model/icon_ollama_gray.png b/frontend/src/assets/model/icon_ollama_gray.png new file mode 100644 index 00000000..08a6e17e Binary files /dev/null and b/frontend/src/assets/model/icon_ollama_gray.png differ diff --git a/frontend/src/assets/model/icon_openai_colorful.png b/frontend/src/assets/model/icon_openai_colorful.png new file mode 100644 index 00000000..e6365e86 Binary files /dev/null and b/frontend/src/assets/model/icon_openai_colorful.png differ diff --git a/frontend/src/assets/model/icon_txy_colorful.png b/frontend/src/assets/model/icon_txy_colorful.png new file mode 100644 index 00000000..a6f17f81 Binary files /dev/null and b/frontend/src/assets/model/icon_txy_colorful.png differ diff --git a/frontend/src/assets/model/icon_tyqw_colorful.png b/frontend/src/assets/model/icon_tyqw_colorful.png new file mode 100644 index 00000000..3cc26b50 Binary files /dev/null and b/frontend/src/assets/model/icon_tyqw_colorful.png differ diff --git a/frontend/src/assets/model/icon_wxyy_colorful.png b/frontend/src/assets/model/icon_wxyy_colorful.png new file mode 100644 index 00000000..6e9d94ab Binary files /dev/null and b/frontend/src/assets/model/icon_wxyy_colorful.png differ diff --git a/frontend/src/assets/model/icon_xfhx_colorful.png b/frontend/src/assets/model/icon_xfhx_colorful.png new file mode 100644 index 00000000..ad396f17 Binary files /dev/null and b/frontend/src/assets/model/icon_xfhx_colorful.png differ diff --git a/frontend/src/assets/svg/icon-key_outlined.svg b/frontend/src/assets/svg/icon-key_outlined.svg new file mode 100644 index 00000000..1045f405 --- /dev/null +++ b/frontend/src/assets/svg/icon-key_outlined.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/svg/icon-maybe_outlined.svg b/frontend/src/assets/svg/icon-maybe_outlined.svg new file mode 100644 index 00000000..5e8a31aa --- /dev/null +++ b/frontend/src/assets/svg/icon-maybe_outlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/svg/icon-member-default.svg b/frontend/src/assets/svg/icon-member-default.svg new file mode 100644 index 00000000..5444910e --- /dev/null +++ b/frontend/src/assets/svg/icon-member-default.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/svg/icon_admin_outlined.svg b/frontend/src/assets/svg/icon_admin_outlined.svg new file mode 100644 index 00000000..08447d7f --- /dev/null +++ b/frontend/src/assets/svg/icon_admin_outlined.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/svg/icon_azure_openAI_colorful.svg b/frontend/src/assets/svg/icon_azure_openAI_colorful.svg new file mode 100644 index 00000000..e5a2f98a --- /dev/null +++ b/frontend/src/assets/svg/icon_azure_openAI_colorful.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/svg/icon_chat_outlined.svg b/frontend/src/assets/svg/icon_chat_outlined.svg new file mode 100644 index 00000000..8027cf8a --- /dev/null +++ b/frontend/src/assets/svg/icon_chat_outlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/svg/icon_dataset_filled.svg b/frontend/src/assets/svg/icon_dataset_filled.svg new file mode 100644 index 00000000..5f15a4de --- /dev/null +++ b/frontend/src/assets/svg/icon_dataset_filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/svg/icon_done_outlined.svg b/frontend/src/assets/svg/icon_done_outlined.svg new file mode 100644 index 00000000..ede53c1e --- /dev/null +++ b/frontend/src/assets/svg/icon_done_outlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/svg/icon_form_outlined.svg b/frontend/src/assets/svg/icon_form_outlined.svg new file mode 100644 index 00000000..644e0c22 --- /dev/null +++ b/frontend/src/assets/svg/icon_form_outlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/svg/icon_logout_outlined.svg b/frontend/src/assets/svg/icon_logout_outlined.svg new file mode 100644 index 00000000..69d2c9d1 --- /dev/null +++ b/frontend/src/assets/svg/icon_logout_outlined.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/svg/icon_moments-categories_outlined.svg b/frontend/src/assets/svg/icon_moments-categories_outlined.svg new file mode 100644 index 00000000..51a89af8 --- /dev/null +++ b/frontend/src/assets/svg/icon_moments-categories_outlined.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/svg/icon_right_outlined.svg b/frontend/src/assets/svg/icon_right_outlined.svg new file mode 100644 index 00000000..e166a0f7 --- /dev/null +++ b/frontend/src/assets/svg/icon_right_outlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/svg/icon_side-expand_outlined.svg b/frontend/src/assets/svg/icon_side-expand_outlined.svg new file mode 100644 index 00000000..f242724e --- /dev/null +++ b/frontend/src/assets/svg/icon_side-expand_outlined.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/svg/icon_side-fold_outlined.svg b/frontend/src/assets/svg/icon_side-fold_outlined.svg new file mode 100644 index 00000000..86832bca --- /dev/null +++ b/frontend/src/assets/svg/icon_side-fold_outlined.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/svg/icon_translate_outlined.svg b/frontend/src/assets/svg/icon_translate_outlined.svg new file mode 100644 index 00000000..a7c7d59e --- /dev/null +++ b/frontend/src/assets/svg/icon_translate_outlined.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontend/src/components/layout/LayoutDsl.vue b/frontend/src/components/layout/LayoutDsl.vue new file mode 100644 index 00000000..aa4942fe --- /dev/null +++ b/frontend/src/components/layout/LayoutDsl.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/frontend/src/components/layout/Menu.vue b/frontend/src/components/layout/Menu.vue new file mode 100644 index 00000000..327d3df7 --- /dev/null +++ b/frontend/src/components/layout/Menu.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/frontend/src/components/layout/MenuItem.vue b/frontend/src/components/layout/MenuItem.vue new file mode 100644 index 00000000..ea938bc1 --- /dev/null +++ b/frontend/src/components/layout/MenuItem.vue @@ -0,0 +1,50 @@ + diff --git a/frontend/src/components/layout/Person.vue b/frontend/src/components/layout/Person.vue new file mode 100644 index 00000000..7f38a854 --- /dev/null +++ b/frontend/src/components/layout/Person.vue @@ -0,0 +1,286 @@ + + + + + + + diff --git a/frontend/src/components/layout/Workspace.vue b/frontend/src/components/layout/Workspace.vue new file mode 100644 index 00000000..31cc242f --- /dev/null +++ b/frontend/src/components/layout/Workspace.vue @@ -0,0 +1,195 @@ + + + + + + + diff --git a/frontend/src/entity/CommonEntity.ts b/frontend/src/entity/CommonEntity.ts index 543a4ca5..a515e943 100644 --- a/frontend/src/entity/CommonEntity.ts +++ b/frontend/src/entity/CommonEntity.ts @@ -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 }, diff --git a/frontend/src/entity/supplier.ts b/frontend/src/entity/supplier.ts new file mode 100644 index 00000000..62a5c819 --- /dev/null +++ b/frontend/src/entity/supplier.ts @@ -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 + +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) +} diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 176fd2a7..3215fea2 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -7,6 +7,7 @@ }, "common": { "input_content": "Please Input Content", + "empty": " ", "back": "Back", "confirm": "Confirm", "cancel": "Cancel", @@ -71,6 +72,7 @@ "type": "Data Source Type", "status": "Status", "actions": "Actions", + "pieces_in_total": "Showing {ms} records out of {msg}", "test_connection": "Test Connection", "connection_success": "Connection Successful", "connection_failed": "Connection Failed, Please Check Configuration", @@ -147,9 +149,64 @@ "success": "Connect success", "failed": "Connect failed" }, - "timeout":"Timeout(second)" + "timeout": "Timeout(second)" } }, + "datasource": { + "search_by_name": "Search by Name", + "search": "Search", + "all_types": "All types", + "new_data_source": "New data source", + "open_query": "Open query", + "edit": "Edit", + "source_connection_failed": "Data source connection failed", + "confirm": "Confirm", + "enabled_status": "Enabled status", + "custom_notes": "Custom notes", + "field_type": "Field type", + "field_name": "Field name", + "relevant_content_found": "No relevant content found", + "please_enter": "Please enter", + "table_notes": "Table notes", + "field_notes": "Field notes", + "select_all": "Select all", + "mysql_data_source": "Modify {msg} data source", + "configuration_information": "Configuration information", + "data_source": "Do you want to delete the data source: {msg}?", + "operate_with_caution": "After being deleted, the data source cannot be intelligently counted. Please operate with caution.", + "data_source_de": "Unable to delete data source: {msg}", + "cannot_be_deleted": "The chart of the dashboard uses this data source and cannot be deleted.", + "got_it": "Got it" + }, + "model": { + "default_model": "Default model", + "model_type": "Model type", + "basic_model": "Basic model", + "set_successfully": "Set successfully", + "operate_with_caution": "After the system default model is replaced, the result of intelligent question will be affected, please operate with caution.", + "system_default_model": "Do you want to set {msg} as the system default model?", + "ai_model_configuration": "AI model configuration", + "system_default_model_de": "System default model", + "relevant_results_found": "No relevant results found", + "add_model": "Add model", + "select_supplier": "Select supplier", + "the_basic_model": "Please set a name for the basic model", + "the_basic_model_de": "Please select the basic model", + "model_name": "Model name", + "custom_model_name": "Custom model name", + "enter_to_add": "For models not listed in the list, just enter the model name and press Enter to add", + "api_domain_name": "API domain name", + "advanced_settings": "Advanced settings", + "model_parameters": "Model parameters", + "add": "Add", + "parameters": "Parameters", + "display_name": "Display name", + "parameter_value": "Parameter value", + "text": "Text", + "number": "Number", + "parameter_type": "Parameter type", + "verification_successful": "Verification successful" + }, "chat": { "chart_type": { "table": "Table", @@ -186,4 +243,4 @@ "back_community": "Restore to Community Edition", "confirm_tips": "Are you sure to restore to Community Edition?" } -} +} \ No newline at end of file diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index 6b6e3001..544163dc 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -7,6 +7,7 @@ }, "common": { "input_content": "请输入内容", + "empty": "", "back": "返回", "confirm": "确认", "cancel": "取消", @@ -71,6 +72,7 @@ "name": "数据源名称", "type": "数据源类型", "status": "状态", + "pieces_in_total": "显示 {ms} 条数据,共 {msg} 条", "actions": "操作", "test_connection": "测试连接", "connection_success": "连接成功", @@ -148,9 +150,64 @@ "success": "连接成功", "failed": "连接失败" }, - "timeout":"查询超时(秒)" + "timeout": "查询超时(秒)" } }, + "datasource": { + "search_by_name": "通过名称搜索", + "search": "搜索", + "all_types": "全部类型", + "new_data_source": "新建数据源", + "open_query": "开启问数", + "edit": "编辑", + "source_connection_failed": "数据源连接失败", + "confirm": "确定", + "enabled_status": "启用状态", + "custom_notes": "自定义备注", + "field_type": "字段类型", + "field_name": "字段名称", + "relevant_content_found": "没有找到相关内容", + "please_enter": "请输入", + "table_notes": "表备注", + "field_notes": "字段备注", + "select_all": "全选", + "mysql_data_source": "修改 {msg} 数据源", + "configuration_information": "配置信息", + "data_source": "是否删除数据源:{msg}?", + "operate_with_caution": "被删除后,将无法对该数据源进行智能问数,请谨慎操作。", + "data_source_de": "无法删除数据源:{msg}", + "cannot_be_deleted": "仪表板的图表使用了该数据源,不可删除。", + "got_it": "知道了" + }, + "model": { + "default_model": "默认模型", + "model_type": "模型类型", + "basic_model": "基础模型", + "set_successfully": "设置成功", + "operate_with_caution": "系统默认模型被替换后,智能问数的结果将会受到影响,请谨慎操作。", + "system_default_model": "是否设置 {msg} 为系统默认模型?", + "ai_model_configuration": "AI 模型配置", + "system_default_model_de": "系统默认模型", + "relevant_results_found": "没有找到相关结果", + "add_model": "添加模型", + "select_supplier": "选择供应商", + "the_basic_model": "请给基础模型设置一个名称", + "the_basic_model_de": "请选择基础模型", + "model_name": "模型名称", + "custom_model_name": "自定义的模型名称", + "enter_to_add": "列表中未列出的模型,直接输入模型名称,回车即可添加", + "api_domain_name": "API 域名", + "advanced_settings": "高级设置", + "model_parameters": "模型参数", + "add": "添加", + "parameters": "参数", + "display_name": "显示名称", + "parameter_value": "参数值", + "text": "文本", + "number": "数值", + "parameter_type": "参数类型", + "verification_successful": "校验成功" + }, "chat": { "chart_type": { "table": "明细表", @@ -187,4 +244,4 @@ "back_community": "还原至社区版", "confirm_tips": "确定还原至社区版?" } -} +} \ No newline at end of file diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index d818ecc4..1839cb34 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,14 +1,16 @@ import { createRouter, createWebHashHistory } from 'vue-router' import Layout from '@/components/layout/index.vue' +import LayoutDsl from '@/components/layout/LayoutDsl.vue' import SinglePage from '@/components/layout/SinglePage.vue' import login from '@/views/login/index.vue' import chat from '@/views/chat/index.vue' -import ds from '@/views/ds/index.vue' +import Datasource from '@/views/ds/Datasource.vue' import DashboardEditor from '@/views/dashboard/editor/index.vue' import DashboardPreview from '@//views/dashboard/preview/SQPreviewSingle.vue' import Dashboard from '@/views/dashboard/index.vue' -import Model from '@/views/system/model/index.vue' +// import Model from '@/views/system/model/index.vue' +import Model from '@/views/system/model/Model.vue' // import User from "@/views/system/user/index.vue"; import { watchRouter } from './watch' const router = createRouter({ @@ -46,13 +48,13 @@ const router = createRouter({ }, { path: '/ds', - component: Layout, + component: LayoutDsl, redirect: '/ds/index', children: [ { path: 'index', name: 'ds', - component: ds, + component: Datasource, meta: { title: 'Data Connections', icon: 'ds' }, }, ], @@ -95,17 +97,30 @@ const router = createRouter({ }, ], }, */ + // { + // path: '/system', + // component: Layout, + // redirect: '/system/model', + // children: [ + // /* { + // path: "user", + // name: "user", + // component: User, + // meta: { title: "User Management", icon: "icon_user" }, + // }, */ + // { + // path: 'model', + // name: 'model', + // component: Model, + // meta: { title: 'AI Model Configuration', icon: 'icon_ai' }, + // }, + // ], + // }, { path: '/system', - component: Layout, + component: LayoutDsl, redirect: '/system/model', children: [ - /* { - path: "user", - name: "user", - component: User, - meta: { title: "User Management", icon: "icon_user" }, - }, */ { path: 'model', name: 'model', diff --git a/frontend/src/style.less b/frontend/src/style.less index 7782fbd2..436f7f21 100644 --- a/frontend/src/style.less +++ b/frontend/src/style.less @@ -3,7 +3,6 @@ line-height: 1.5; font-weight: 400; - font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; @@ -26,7 +25,7 @@ --border-radius: 8px; --shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - --el-color-primary: #1cba90 !important; + --ed-color-primary: #1cba90 !important; } a { @@ -111,7 +110,6 @@ body { border-radius: 8px !important; } - .hover-icon { cursor: pointer; height: 24px !important; @@ -145,3 +143,9 @@ body { top: 0; z-index: 100; } + +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} diff --git a/frontend/src/views/ds/Card.vue b/frontend/src/views/ds/Card.vue new file mode 100644 index 00000000..73fdbf7e --- /dev/null +++ b/frontend/src/views/ds/Card.vue @@ -0,0 +1,278 @@ + + + + + + + diff --git a/frontend/src/views/ds/DataTable.vue b/frontend/src/views/ds/DataTable.vue new file mode 100644 index 00000000..bb2659c3 --- /dev/null +++ b/frontend/src/views/ds/DataTable.vue @@ -0,0 +1,512 @@ + + + + + diff --git a/frontend/src/views/ds/Datasource.vue b/frontend/src/views/ds/Datasource.vue new file mode 100644 index 00000000..b2836684 --- /dev/null +++ b/frontend/src/views/ds/Datasource.vue @@ -0,0 +1,399 @@ + + + + + + + diff --git a/frontend/src/views/ds/DatasourceForm.vue b/frontend/src/views/ds/DatasourceForm.vue new file mode 100644 index 00000000..b813b0d3 --- /dev/null +++ b/frontend/src/views/ds/DatasourceForm.vue @@ -0,0 +1,665 @@ + + + + + diff --git a/frontend/src/views/ds/DatasourceList.vue b/frontend/src/views/ds/DatasourceList.vue new file mode 100644 index 00000000..487b6704 --- /dev/null +++ b/frontend/src/views/ds/DatasourceList.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/frontend/src/views/ds/DatasourceListSide.vue b/frontend/src/views/ds/DatasourceListSide.vue new file mode 100644 index 00000000..0dab68fe --- /dev/null +++ b/frontend/src/views/ds/DatasourceListSide.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/frontend/src/views/ds/ParamsForm.vue b/frontend/src/views/ds/ParamsForm.vue new file mode 100644 index 00000000..add5c82d --- /dev/null +++ b/frontend/src/views/ds/ParamsForm.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/frontend/src/views/ds/TableList.vue b/frontend/src/views/ds/TableList.vue index ae7af207..cc371f0a 100644 --- a/frontend/src/views/ds/TableList.vue +++ b/frontend/src/views/ds/TableList.vue @@ -105,7 +105,7 @@ @closed="closeTable" >
{{ t('ds.edit.table_comment_label') }}
- +
{{ t('common.cancel') }} {{ t('common.confirm') }} @@ -121,7 +121,7 @@ @closed="closeField" >
{{ t('ds.edit.field_comment_label') }}
- +
{{ t('common.cancel') }} {{ t('common.confirm') }} diff --git a/frontend/src/views/ds/form.vue b/frontend/src/views/ds/form.vue index 88e6c12d..195e7024 100644 --- a/frontend/src/views/ds/form.vue +++ b/frontend/src/views/ds/form.vue @@ -332,8 +332,7 @@ const save = async (formEl: FormInstance | undefined) => { if (form.value.id) { if (!isEditTable.value) { // only update datasource config info - datasourceApi.update(requestObj).then((res) => { - console.log(res) + datasourceApi.update(requestObj).then(() => { close() emit('refresh') }) @@ -346,8 +345,7 @@ const save = async (formEl: FormInstance | undefined) => { } } else { requestObj.tables = list - datasourceApi.add(requestObj).then((res: any) => { - console.log(res) + datasourceApi.add(requestObj).then(() => { close() emit('refresh') }) diff --git a/frontend/src/views/ds/js/ds-type.ts b/frontend/src/views/ds/js/ds-type.ts index 2fb2ed86..8a022be9 100644 --- a/frontend/src/views/ds/js/ds-type.ts +++ b/frontend/src/views/ds/js/ds-type.ts @@ -1,3 +1,9 @@ +import mysql_ds from '@/assets/datasource/icon_mysql.png' +import excel from '@/assets/datasource/icon_excel.png' +import oracle from '@/assets/datasource/icon_oracle.png' +import pg from '@/assets/datasource/icon_PostgreSQL.png' +import sqlServer from '@/assets/datasource/icon_SQL_Server.png' + export const dsType = [ { label: 'MySQL', value: 'mysql' }, { label: 'PostgreSQL', value: 'pg' }, @@ -6,4 +12,12 @@ export const dsType = [ { label: 'Excel/CSV', value: 'excel' }, ] +export const dsTypeWithImg = [ + { name: 'MySQL', type: 'mysql', img: mysql_ds }, + { name: 'PostgreSQL', type: 'pg', img: pg }, + { name: 'Microsoft SQL Server', type: 'sqlServer', img: sqlServer }, + { name: 'Oracle', type: 'oracle', img: oracle }, + { name: '本地 Excel/CSV', type: 'excel', img: excel }, +] + export const haveSchema = ['sqlServer', 'pg', 'oracle'] diff --git a/frontend/src/views/system/model/Card.vue b/frontend/src/views/system/model/Card.vue new file mode 100644 index 00000000..516fbef2 --- /dev/null +++ b/frontend/src/views/system/model/Card.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/frontend/src/views/system/model/Model.vue b/frontend/src/views/system/model/Model.vue new file mode 100644 index 00000000..ee4eb4db --- /dev/null +++ b/frontend/src/views/system/model/Model.vue @@ -0,0 +1,439 @@ + + + + + + + diff --git a/frontend/src/views/system/model/ModelForm.vue b/frontend/src/views/system/model/ModelForm.vue new file mode 100644 index 00000000..99807762 --- /dev/null +++ b/frontend/src/views/system/model/ModelForm.vue @@ -0,0 +1,390 @@ + + + + + diff --git a/frontend/src/views/system/model/ModelList.vue b/frontend/src/views/system/model/ModelList.vue new file mode 100644 index 00000000..0fba6a3a --- /dev/null +++ b/frontend/src/views/system/model/ModelList.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/frontend/src/views/system/model/ModelListSide.vue b/frontend/src/views/system/model/ModelListSide.vue new file mode 100644 index 00000000..d1e7756f --- /dev/null +++ b/frontend/src/views/system/model/ModelListSide.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/frontend/src/views/system/model/ParamsForm.vue b/frontend/src/views/system/model/ParamsForm.vue new file mode 100644 index 00000000..f6edbc54 --- /dev/null +++ b/frontend/src/views/system/model/ParamsForm.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/frontend/src/views/system/model/index.vue b/frontend/src/views/system/model/index.vue index fe71fd6c..457db734 100644 --- a/frontend/src/views/system/model/index.vue +++ b/frontend/src/views/system/model/index.vue @@ -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 = { diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json index bf358d84..59f79286 100644 --- a/frontend/tsconfig.app.json +++ b/frontend/tsconfig.app.json @@ -1,6 +1,8 @@ { "extends": "@vue/tsconfig/tsconfig.dom.json", "compilerOptions": { + "target": "es2021", + "lib": ["es2021", "dom"], "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "baseUrl": ".", "paths": { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 4749a7e2..89a1405a 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -46,6 +46,16 @@ export default defineConfig(({ mode }) => { }, }, }, + // server: { + // proxy: { + // '/api': { + // target: 'https://sqlbot.fit2cloud.cn', + // changeOrigin: true, + // rewrite: path => path.replace(/^\/api/, 'api') + // } + // }, + // port: 8080 + // }, esbuild: { jsxFactory: 'h', jsxFragment: 'Fragment',