feat:Recommended questions support user configuration. (#487)

This commit is contained in:
王嘉豪
2025-11-25 13:54:40 +08:00
committed by GitHub
parent 85b19fb40b
commit 9d494345b2
13 changed files with 264 additions and 4 deletions
@@ -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')
+3 -1
View File
@@ -3,7 +3,7 @@ from fastapi import APIRouter
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(chat.router)
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)
@@ -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)
@@ -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
@@ -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
+7
View File
@@ -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),
}
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1763971949643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8822" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M0 0v1024h1024V0H0z m907.636364 907.636364H116.363636V116.363636h791.272728v791.272728z" p-id="8823"></path><path d="M449.163636 572.509091h116.363637v109.381818h-116.363637zM509.672727 351.418182c25.6 2.327273 37.236364 11.636364 39.563637 32.581818 0 13.963636-11.636364 27.927273-34.909091 37.236364-44.218182 20.945455-62.836364 55.854545-58.181818 109.381818H558.545455c-4.654545-20.945455 11.636364-41.890909 46.545454-60.509091 39.563636-23.272727 62.836364-53.527273 62.836364-88.436364-4.654545-72.145455-53.527273-109.381818-151.272728-114.036363-100.072727 4.654545-151.272727 46.545455-158.254545 128h116.363636c-2.327273-27.927273 9.309091-41.890909 34.909091-44.218182z" p-id="8824"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+6
View File
@@ -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",
+6
View File
@@ -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": "검색",
+6
View File
@@ -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": "搜索",
+19 -1
View File
@@ -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 = () => {
</el-icon>
{{ $t('datasource.edit') }}
</div>
<div class="item" style="display: none" @click.stop="handleRecommendation">
<el-icon size="16">
<icon_recommended_problem></icon_recommended_problem>
</el-icon>
{{ $t('datasource.recommended_problem_configuration') }}
</div>
<div class="item" @click.stop="handleDel">
<el-icon size="16">
<delIcon></delIcon>
+13 -2
View File
@@ -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)"
></Card>
@@ -317,7 +326,9 @@ useEmitt({
</el-button>
</div>
</template>
<RecommendedProblemConfigDialog
ref="recommendedProblemConfigRef"
></RecommendedProblemConfigDialog>
<AddDrawer ref="addDrawerRef" @search="search"></AddDrawer>
</div>
<DataTable
@@ -0,0 +1,130 @@
<script setup lang="ts">
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<any>({
id: null,
question: '',
sort: null,
})
defineExpose({
init,
})
</script>
<template>
<el-dialog
v-model="dialogShow"
:title="$t('datasource.recommended_problem_configuration')"
width="600"
modal-class="add-question_dialog"
destroy-on-close
:close-on-click-modal="false"
@before-closed="closeDialog"
>
<el-form-item :label="$t('datasource.problem_generation_method')" prop="mode">
<el-radio-group v-model="form.mode">
<el-radio value="1">{{ $t('datasource.ai_automatic_generation') }}</el-radio>
<el-radio value="2">{{ $t('datasource.user_defined') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-for="(recommendedItem, index) in state.recommended.recommendedProblemList"
:key="index"
prop="mode"
>
<el-input
v-model="recommendedItem.question"
clearable
:placeholder="$t('datasource.question_tips')"
>
</el-input>
</el-form-item>
<div>
<el-button text @click="addRecommendedProblem">
{{ $t('datasource.add_question') }}</el-button
>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
<el-button secondary @click="closeDialog">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="save">{{ $t('model.add') }}</el-button>
</div>
</el-dialog>
</template>
<style scoped lang="less">
.add-question_dialog {
.ed-input-group__append {
background-color: #fff;
padding: 0 12px;
}
.value-input {
.ed-input-group__append {
color: #1f2329;
position: relative;
&:hover {
&::after {
content: '';
position: absolute;
left: -1px;
top: 0;
width: calc(100% - 1px);
height: calc(100% - 2px);
background: var(--ed-color-primary-1a, #1cba901a);
border: 1px solid var(--ed-color-primary);
border-bottom-right-radius: 6px;
border-top-right-radius: 6px;
pointer-events: none;
}
}
}
}
}
</style>