diff --git a/backend/alembic/versions/052_add_recommended_problem.py b/backend/alembic/versions/052_add_recommended_problem.py new file mode 100644 index 00000000..9718a7c5 --- /dev/null +++ b/backend/alembic/versions/052_add_recommended_problem.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: e408f8766753 +Revises: cb12c4238120 +Create Date: 2025-11-24 17:34:04.436927 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'e408f8766753' +down_revision = 'cb12c4238120' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table('ds_recommended_problem', + sa.Column('id', sa.BIGINT(), + sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=9999999999, + cycle=False, cache=1), autoincrement=True, nullable=False), + sa.Column('datasource_id', sa.BIGINT(), autoincrement=False, nullable=True), + sa.Column('question', sa.TEXT(), autoincrement=False, nullable=True), + sa.Column('remark', sa.TEXT(), autoincrement=False, nullable=True), + sa.Column('sort', sa.BIGINT(), autoincrement=False, nullable=True), + sa.Column('create_time', postgresql.TIMESTAMP(precision=6), autoincrement=False, nullable=True), + sa.Column('create_by', sa.BIGINT(), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint('id', name=op.f('ds_recommended_problem_pkey')) + ) + op.add_column('core_datasource', sa.Column('recommended_config', sa.BigInteger(),default=0, nullable=True)) + +def downgrade(): + op.drop_table('ds_recommended_problem') + op.drop_column('core_datasource', 'recommended_config') + diff --git a/backend/apps/api.py b/backend/apps/api.py index eac8c39e..9cdb8315 100644 --- a/backend/apps/api.py +++ b/backend/apps/api.py @@ -3,7 +3,7 @@ from apps.chat.api import chat from apps.dashboard.api import dashboard_api from apps.data_training.api import data_training -from apps.datasource.api import datasource, table_relation +from apps.datasource.api import datasource, table_relation, recommended_problem from apps.mcp import mcp from apps.system.api import login, user, aimodel, workspace, assistant from apps.terminology.api import terminology @@ -23,3 +23,5 @@ api_router.include_router(dashboard_api.router) api_router.include_router(mcp.router) api_router.include_router(table_relation.router) + +api_router.include_router(recommended_problem.router) diff --git a/backend/apps/datasource/api/recommended_problem.py b/backend/apps/datasource/api/recommended_problem.py new file mode 100644 index 00000000..29fac41c --- /dev/null +++ b/backend/apps/datasource/api/recommended_problem.py @@ -0,0 +1,12 @@ +from fastapi import APIRouter + +from apps.datasource.crud.recommended_problem import get_datasource_recommended +from common.core.deps import SessionDep + +router = APIRouter(tags=["recommended_problem"], prefix="/recommended_problem") + + +@router.get("/get_datasource_recommended/{ds_id}") +async def datasource_recommended(session: SessionDep, ds_id: int): + return get_datasource_recommended(session, ds_id) + diff --git a/backend/apps/datasource/crud/recommended_problem.py b/backend/apps/datasource/crud/recommended_problem.py new file mode 100644 index 00000000..e3f8ab67 --- /dev/null +++ b/backend/apps/datasource/crud/recommended_problem.py @@ -0,0 +1,11 @@ +from sqlmodel import select + +from common.core.deps import SessionDep +from ..models.datasource import DsRecommendedProblem + + +def get_datasource_recommended(session: SessionDep, ds_id: int): + statement = select(DsRecommendedProblem).where(DsRecommendedProblem.datasource_id == ds_id) + dsRecommendedProblem = session.exec(statement) + return dsRecommendedProblem + diff --git a/backend/apps/datasource/models/datasource.py b/backend/apps/datasource/models/datasource.py index a86eb7ca..2cdfed5a 100644 --- a/backend/apps/datasource/models/datasource.py +++ b/backend/apps/datasource/models/datasource.py @@ -22,6 +22,7 @@ class CoreDatasource(SQLModel, table=True): oid: int = Field(sa_column=Column(BigInteger())) table_relation: List = Field(sa_column=Column(JSONB, nullable=True)) embedding: str = Field(sa_column=Column(Text, nullable=True)) + recommended_config: int = Field(sa_column=Column(BigInteger())) class CoreTable(SQLModel, table=True): @@ -34,6 +35,16 @@ class CoreTable(SQLModel, table=True): custom_comment: str = Field(sa_column=Column(Text)) embedding: str = Field(sa_column=Column(Text, nullable=True)) +class DsRecommendedProblem(SQLModel, table=True): + __tablename__ = "ds_recommended_problem" + id: int = Field(sa_column=Column(BigInteger, Identity(always=True), nullable=False, primary_key=True)) + datasource_id: int = Field(sa_column=Column(BigInteger())) + question: str = Field(sa_column=Column(Text)) + remark: str = Field(sa_column=Column(Text)) + sort: int = Field(sa_column=Column(BigInteger())) + create_time: datetime = Field(sa_column=Column(DateTime(timezone=False), nullable=True)) + create_by: int = Field(sa_column=Column(BigInteger())) + class CoreField(SQLModel, table=True): __tablename__ = "core_field" @@ -61,6 +72,7 @@ class CreateDatasource(BaseModel): num: str = '' oid: int = 1 tables: List[CoreTable] = [] + recommended_config: int = 1 # edit local saved table and fields diff --git a/frontend/src/api/recommendedApi.ts b/frontend/src/api/recommendedApi.ts new file mode 100644 index 00000000..64f83538 --- /dev/null +++ b/frontend/src/api/recommendedApi.ts @@ -0,0 +1,7 @@ +import { request } from '@/utils/request' + +export const recommendedApi = { + get_recommended_problem: (dsId: any) => + request.get(`/recommended_problem/get_datasource_recommended/${dsId}`), + save_recommended_problem: (data: any) => request.post(`/recommended_problem/save`, data), +} diff --git a/frontend/src/assets/svg/icon_recommended_problem.svg b/frontend/src/assets/svg/icon_recommended_problem.svg new file mode 100644 index 00000000..5fb83ba9 --- /dev/null +++ b/frontend/src/assets/svg/icon_recommended_problem.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 3ca260fd..774f3692 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -308,6 +308,12 @@ } }, "datasource": { + "recommended_problem_configuration": "Recommended Problem Configuration", + "problem_generation_method": "Problem Generation Method", + "ai_automatic_generation": "AI Automatic Generation", + "user_defined": "User Defined", + "question_tips": "Please enter recommended questions", + "add_question": "Add Question", "data_source_yet": "No data source yet", "search_by_name": "Search by Name", "search": "Search", diff --git a/frontend/src/i18n/ko-KR.json b/frontend/src/i18n/ko-KR.json index 0f5a552f..f6d2b55d 100644 --- a/frontend/src/i18n/ko-KR.json +++ b/frontend/src/i18n/ko-KR.json @@ -308,6 +308,12 @@ } }, "datasource": { + "recommended_problem_configuration": "추천 문제 구성", + "problem_generation_method": "문제 생성 방식", + "ai_automatic_generation": "AI 자동 생성", + "user_defined": "사용자 정의", + "question_tips": "추천 문제를 입력해 주세요", + "add_question": "문제 추가", "data_source_yet": "데이터 소스가 없습니다", "search_by_name": "이름으로 검색", "search": "검색", diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index 46648adb..21bbc93a 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -308,6 +308,12 @@ } }, "datasource": { + "recommended_problem_configuration": "推荐问题配置", + "problem_generation_method": "问题生成方式", + "ai_automatic_generation": "AI 自动生成", + "user_defined": "用户自定义", + "question_tips": "请输入推荐问题", + "add_question": "添加问题", "data_source_yet": "暂无数据源", "search_by_name": "通过名称搜索", "search": "搜索", diff --git a/frontend/src/views/ds/Card.vue b/frontend/src/views/ds/Card.vue index 138619f2..1a9759e8 100644 --- a/frontend/src/views/ds/Card.vue +++ b/frontend/src/views/ds/Card.vue @@ -7,6 +7,7 @@ import { computed, ref, unref } from 'vue' import { ClickOutside as vClickOutside } from 'element-plus-secondary' import { dsTypeWithImg } from './js/ds-type' import edit from '@/assets/svg/icon_edit_outlined.svg' +import icon_recommended_problem from '@/assets/svg/icon_recommended_problem.svg' import { datasourceApi } from '@/api/datasource.ts' const props = withDefaults( @@ -27,7 +28,14 @@ const props = withDefaults( } ) -const emits = defineEmits(['edit', 'del', 'question', 'dataTableDetail', 'showTable']) +const emits = defineEmits([ + 'edit', + 'del', + 'question', + 'dataTableDetail', + 'showTable', + 'recommendation', +]) const icon = computed(() => { return (dsTypeWithImg.find((ele) => props.type === ele.type) || {}).img }) @@ -35,6 +43,10 @@ const handleEdit = () => { emits('edit') } +const handleRecommendation = () => { + emits('recommendation') +} + const handleDel = () => { emits('del') } @@ -124,6 +136,12 @@ const onClickOutside = () => { {{ $t('datasource.edit') }} +
diff --git a/frontend/src/views/ds/Datasource.vue b/frontend/src/views/ds/Datasource.vue index 462a378f..4804ff1c 100644 --- a/frontend/src/views/ds/Datasource.vue +++ b/frontend/src/views/ds/Datasource.vue @@ -17,8 +17,11 @@ import { dsTypeWithImg } from './js/ds-type' import { useI18n } from 'vue-i18n' import { useUserStore } from '@/stores/user' import { chatApi } from '@/api/chat' +import RecommendedProblemConfigDialog from '@/views/ds/RecommendedProblemConfigDialog.vue' const userStore = useUserStore() -interface Datasource { +const recommendedProblemConfigRef = ref() + +export interface Datasource { name: string num: string type_name: string @@ -26,6 +29,7 @@ interface Datasource { img: string description: string id?: string + recommended_config?: string } const router = useRouter() @@ -77,6 +81,10 @@ const handleEditDatasource = (res: any) => { addDrawerRef.value.handleEditDatasource(res) } +const handleRecommendation = (res: Datasource) => { + recommendedProblemConfigRef.value?.init(res) +} + const handleQuestion = async (id: string) => { try { await chatApi.checkLLMModel() @@ -295,6 +303,7 @@ useEmitt({ :description="ele.description" @question="handleQuestion" @edit="handleEditDatasource(ele)" + @recommendation="handleRecommendation(ele)" @del="deleteHandler(ele)" @data-table-detail="dataTableDetail(ele)" > @@ -317,7 +326,9 @@ useEmitt({
- + +import { reactive, ref } from 'vue' +import { recommendedApi } from '@/api/recommendedApi.ts' + +interface RecommendedProblem { + id?: number + question: string + datasourceId?: any | undefined + sort: number +} + +const dialogShow = ref(false) + +const state = reactive({ + dsId: null, + recommended: { + recommendedConfig: 1, + recommendedProblemList: [] as RecommendedProblem[], + }, +}) + +const init = (params: any) => { + dialogShow.value = true + state.recommended.recommendedConfig = params.recommendedConfig + state.dsId = params.id + recommendedApi.get_recommended_problem(state.dsId).then((res: any) => { + state.recommended.recommendedProblemList = res + }) +} + +const addRecommendedProblem = (): void => { + state.recommended.recommendedProblemList.push({ + question: '', + datasourceId: state.dsId, + } as RecommendedProblem) +} + +const closeDialog = () => { + dialogShow.value = false + state.recommended = { + recommendedConfig: 1, + recommendedProblemList: [] as RecommendedProblem[], + } +} +const save = () => { + recommendedApi.save_recommended_problem(state.recommended.recommendedProblemList) + closeDialog() +} + +const form = ref({ + id: null, + question: '', + sort: null, +}) + +defineExpose({ + init, +}) + + + + +