This commit is contained in:
dataeaseShu
2025-08-06 11:19:21 +08:00
17 changed files with 195 additions and 40 deletions
+15 -2
View File
@@ -8,7 +8,7 @@ from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from apps.chat.curd.chat import list_chats, get_chat_with_records, create_chat, rename_chat, \
delete_chat, get_chat_chart_data, get_chat_predict_data
delete_chat, get_chat_chart_data, get_chat_predict_data, get_chat_with_records_with_data
from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, ChatQuestion, ExcelData
from apps.chat.task.llm import LLMService
from common.core.deps import CurrentAssistant, SessionDep, CurrentUser
@@ -33,6 +33,18 @@ async def get_chat(session: SessionDep, current_user: CurrentUser, chart_id: int
)
@router.get("/get/with_data/{chart_id}")
async def get_chat_with_data(session: SessionDep, current_user: CurrentUser, chart_id: int, current_assistant: CurrentAssistant):
try:
return get_chat_with_records_with_data(chart_id=chart_id, session=session, current_user=current_user,
current_assistant=current_assistant)
except Exception as e:
raise HTTPException(
status_code=500,
detail=str(e)
)
@router.get("/record/get/{chart_record_id}/data")
async def chat_record_data(session: SessionDep, chart_record_id: int):
try:
@@ -205,7 +217,8 @@ async def export_excel(excel_data: ExcelData):
buffer = io.BytesIO()
with pd.ExcelWriter(buffer, engine='xlsxwriter', engine_kwargs={'options': {'strings_to_numbers': True}}) as writer:
with pd.ExcelWriter(buffer, engine='xlsxwriter',
engine_kwargs={'options': {'strings_to_numbers': True}}) as writer:
df.to_excel(writer, sheet_name='Sheet1', index=False)
buffer.seek(0)
+36 -10
View File
@@ -67,8 +67,13 @@ def get_chat_predict_data(session: SessionDep, chart_record_id: int):
return {}
def get_chat_with_records_with_data(session: SessionDep, chart_id: int, current_user: CurrentUser,
current_assistant: CurrentAssistant) -> ChatInfo:
return get_chat_with_records(session, chart_id, current_user, current_assistant, True)
def get_chat_with_records(session: SessionDep, chart_id: int, current_user: CurrentUser,
current_assistant: CurrentAssistant) -> ChatInfo:
current_assistant: CurrentAssistant, with_data: bool = False) -> ChatInfo:
chat = session.get(Chat, chart_id)
if not chat:
raise Exception(f"Chat with id {chart_id} not found")
@@ -96,18 +101,39 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
ChatRecord.recommended_question, ChatRecord.first_chat,
ChatRecord.finish, ChatRecord.error).where(
and_(ChatRecord.create_by == current_user.id, ChatRecord.chat_id == chart_id)).order_by(ChatRecord.create_time)
if with_data:
stmt = select(ChatRecord.id, ChatRecord.chat_id, ChatRecord.create_time, ChatRecord.finish_time,
ChatRecord.question, ChatRecord.sql_answer, ChatRecord.sql,
ChatRecord.chart_answer, ChatRecord.chart, ChatRecord.analysis, ChatRecord.predict,
ChatRecord.datasource_select_answer, ChatRecord.analysis_record_id, ChatRecord.predict_record_id,
ChatRecord.recommended_question, ChatRecord.first_chat,
ChatRecord.finish, ChatRecord.error, ChatRecord.data, ChatRecord.predict_data).where(
and_(ChatRecord.create_by == current_user.id, ChatRecord.chat_id == chart_id)).order_by(
ChatRecord.create_time)
result = session.execute(stmt).all()
record_list: list[ChatRecord] = []
for row in result:
record_list.append(
ChatRecord(id=row.id, chat_id=row.chat_id, create_time=row.create_time, finish_time=row.finish_time,
question=row.question, sql_answer=row.sql_answer, sql=row.sql,
chart_answer=row.chart_answer, chart=row.chart,
analysis=row.analysis, predict=row.predict,
datasource_select_answer=row.datasource_select_answer,
analysis_record_id=row.analysis_record_id, predict_record_id=row.predict_record_id,
recommended_question=row.recommended_question, first_chat=row.first_chat,
finish=row.finish, error=row.error))
if not with_data:
record_list.append(
ChatRecord(id=row.id, chat_id=row.chat_id, create_time=row.create_time, finish_time=row.finish_time,
question=row.question, sql_answer=row.sql_answer, sql=row.sql,
chart_answer=row.chart_answer, chart=row.chart,
analysis=row.analysis, predict=row.predict,
datasource_select_answer=row.datasource_select_answer,
analysis_record_id=row.analysis_record_id, predict_record_id=row.predict_record_id,
recommended_question=row.recommended_question, first_chat=row.first_chat,
finish=row.finish, error=row.error))
else:
record_list.append(
ChatRecord(id=row.id, chat_id=row.chat_id, create_time=row.create_time, finish_time=row.finish_time,
question=row.question, sql_answer=row.sql_answer, sql=row.sql,
chart_answer=row.chart_answer, chart=row.chart,
analysis=row.analysis, predict=row.predict,
datasource_select_answer=row.datasource_select_answer,
analysis_record_id=row.analysis_record_id, predict_record_id=row.predict_record_id,
recommended_question=row.recommended_question, first_chat=row.first_chat,
finish=row.finish, error=row.error, data=row.data, predict_data=row.predict_data))
result = list(map(format_record, record_list))
+1 -1
View File
@@ -78,7 +78,7 @@ async def query(
AiModelDetail.default_model)
if keyword is not None:
statement = statement.where(AiModelDetail.name.like(f"%{keyword}%"))
statement = statement.order_by(AiModelDetail.create_time.asc())
statement = statement.order_by(AiModelDetail.default_model.desc(), AiModelDetail.name, AiModelDetail.create_time)
items = session.exec(statement).all()
return items
+1 -1
View File
@@ -50,7 +50,7 @@ async def validator(session: SessionDep, id: int, virtual: Optional[int] = Query
@router.get("", response_model=list[AssistantModel])
async def query(session: SessionDep):
list_result = session.exec(select(AssistantModel).order_by(AssistantModel.create_time.asc())).all()
list_result = session.exec(select(AssistantModel).order_by(AssistantModel.name, AssistantModel.create_time)).all()
return list_result
@router.post("")
+1 -1
View File
@@ -59,7 +59,7 @@ async def pager(
select(UserModel, UserWsModel.oid.label('ws_oid'))
.join(UserWsModel, UserModel.id == UserWsModel.uid, isouter=True)
.where(UserModel.id.in_(uid_list))
.order_by(UserModel.create_time)
.order_by(UserModel.account, UserModel.create_time)
)
user_workspaces = session.exec(stmt).all()
merged = defaultdict(list)
+2 -2
View File
@@ -32,7 +32,7 @@ async def option_pager(
stmt = select(UserModel.id, UserModel.account, UserModel.name).where(
~exists().where(UserWsModel.uid == UserModel.id, UserWsModel.oid == oid),
UserModel.id != 1
).order_by(UserModel.create_time.asc())
).order_by(UserModel.account, UserModel.create_time)
if keyword:
keyword_pattern = f"%{keyword}%"
@@ -97,7 +97,7 @@ async def pager(
).where(
UserWsModel.oid == workspace_id,
UserModel.id != 1
).order_by(UserModel.create_time.asc())
).order_by(UserModel.account, UserModel.create_time)
if keyword:
keyword_pattern = f"%{keyword}%"
+2 -2
View File
@@ -46,13 +46,13 @@ def authenticate(*, session: Session, account: str, password: str) -> BaseUserDT
async def user_ws_options(session: Session, uid: int, trans: Optional[I18n] = None) -> list[UserWs]:
if uid == 1:
stmt = select(WorkspaceModel.id, WorkspaceModel.name).order_by(WorkspaceModel.create_time)
stmt = select(WorkspaceModel.id, WorkspaceModel.name).order_by(WorkspaceModel.name, WorkspaceModel.create_time)
else:
stmt = select(WorkspaceModel.id, WorkspaceModel.name).join(
UserWsModel, UserWsModel.oid == WorkspaceModel.id
).where(
UserWsModel.uid == uid,
).order_by(WorkspaceModel.create_time)
).order_by(WorkspaceModel.name, WorkspaceModel.create_time)
result = session.exec(stmt)
if not trans:
return result.all()
+1
View File
@@ -6,6 +6,7 @@
// biome-ignore lint: disable
export {}
declare global {
const ElButton: typeof import('element-plus-secondary/es')['ElButton']
const ElMessage: typeof import('element-plus-secondary/es')['ElMessage']
const ElMessageBox: typeof import('element-plus-secondary/es')['ElMessageBox']
const LicenseGenerator: any
+3
View File
@@ -301,6 +301,9 @@ export const chatApi = {
get: (id: number): Promise<ChatInfo> => {
return request.get(`/chat/get/${id}`)
},
get_with_Data: (id: number): Promise<ChatInfo> => {
return request.get(`/chat/get/with_data/${id}`)
},
get_chart_data: (record_id?: number): Promise<any> => {
return request.get(`/chat/record/get/${record_id}/data`)
},
+16 -10
View File
@@ -35,7 +35,7 @@ export const supplierList: Array<{
],
model_options: [
{ name: 'qwen-plus' },
{ name: 'qwen-plus-latest' },
/* { name: 'qwen-plus-latest' }, */
{ name: 'qwen-max' },
{ name: 'qwen-max-latest' },
{ name: 'qwen-turbo' },
@@ -47,10 +47,10 @@ export const supplierList: Array<{
{ name: 'qwen-omni-turbo' },
/* { name: 'qwen-omni-turbo-realtime' },
{ name: 'qwen-omni-turbo-realtime-latest' }, */
{ name: 'qvq-max' },
/* { name: 'qvq-max' },
{ name: 'qvq-max-latest' },
{ name: 'qvq-plus' },
{ name: 'qvq-plus-latest' },
{ name: 'qvq-plus-latest' }, */
],
},
},
@@ -73,7 +73,10 @@ export const supplierList: Array<{
model_config: {
0: {
api_domain: 'https://api.deepseek.com',
model_options: [{ name: 'deepseek-chat' }, { name: 'deepseek-reasoner' }],
model_options: [
{ name: 'deepseek-chat' },
/* { name: 'deepseek-reasoner' } */
],
},
},
},
@@ -87,11 +90,11 @@ export const supplierList: Array<{
common_args: [{ key: 'temperature', val: 1.0, type: 'number', range: '[0, 2]' }],
model_options: [
{ name: 'hunyuan-turbos-latest' },
{ name: 'hunyuan-turbos-longtext-128k-20250325' },
{ name: 'hunyuan-large' },
/* { name: 'hunyuan-turbos-longtext-128k-20250325' },
{ name: 'hunyuan-large' }, */
{ name: 'hunyuan-standard-256K' },
{ name: 'hunyuan-standard' },
{ name: 'hunyuan-lite' },
/* { name: 'hunyuan-lite' }, */
],
},
},
@@ -103,13 +106,16 @@ export const supplierList: Array<{
model_config: {
0: {
api_domain: 'https://spark-api-open.xf-yun.com/v1/',
common_args: [{ key: 'temperature', val: 1.0, type: 'number', range: '[0, 2]' }],
common_args: [
{ key: 'temperature', val: 1.0, type: 'number', range: '[0, 2]' },
{ key: 'max_tokens', val: 4096, type: 'number', range: '[1, 32768]' },
],
model_options: [
{
name: '4.0Ultra',
args: [{ key: 'max_tokens', val: 32768, type: 'number', range: '[1, 32768]' }],
},
{
/* {
name: 'generalv3.5',
args: [{ key: 'max_tokens', val: 4096, type: 'number', range: '[1, 8192]' }],
},
@@ -128,7 +134,7 @@ export const supplierList: Array<{
{
name: 'lite',
args: [{ key: 'max_tokens', val: 4096, type: 'number', range: '[1, 4096]' }],
},
}, */
{
name: 'x1',
args: [
+2
View File
@@ -48,6 +48,7 @@
"or": "Or"
},
"dashboard": {
"open_dashboard": "Open Dashboard",
"add_dashboard_name_tips": "Please Input Dashboard Name",
"existing_dashboard": "Existing Dashboard",
"add_success": "Add Success",
@@ -537,6 +538,7 @@
"data_predict": "Data Prediction",
"ds_is_invalid": "Datasource is invalid",
"error": "Error",
"no_data": "No Data",
"show_error_detail": "Show error info"
},
"about": {
+2
View File
@@ -48,6 +48,7 @@
"or": "或者"
},
"dashboard": {
"open_dashboard": "打开仪表板",
"add_dashboard_name_tips": "请输入仪表板名称",
"existing_dashboard": "存量仪表板",
"add_success": "添加成功",
@@ -537,6 +538,7 @@
"data_predict": "数据预测",
"ds_is_invalid": "数据源无效",
"error": "错误",
"no_data": "暂无数据",
"show_error_detail": "查看具体信息"
},
"about": {
+59
View File
@@ -230,3 +230,62 @@ strong {
height: 1px !important;
}
}
.sq-message-success {
border: 1px solid #34c724 !important;
background: #f0fbef !important;
.ed-message__icon {
color: #34c724
}
}
.sq-message-export {
min-width: 20px !important;
padding: 16px 20px !important;
display: flex;
align-items: center;
box-shadow: 0px 4px 8px 0px #1f23291a;
& > p {
font-family: var(--de-custom_font, 'PingFang');
font-size: 14px;
font-weight: 500;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
color: #1f2329;
display: flex;
align-items: center;
padding-right: 24px;
}
.m50-export {
max-width: 200px;
}
.btn-text {
padding: 2px 4px;
&:hover {
background: var(--primary10, #3370ff1a);
}
}
.ed-message__closeBtn {
margin-left: 28px;
position: relative;
margin-right: 0;
top: 0;
right: 0;
transform: translateY(0);
color: #646a73;
}
.ed-message__icon {
height: 16px;
width: 16px;
margin-right: 8px;
}
}
@@ -3,6 +3,7 @@ import ChartComponent from '@/views/chat/component/ChartComponent.vue'
import type { ChatMessage } from '@/api/chat.ts'
import { computed, nextTick, ref } from 'vue'
import type { ChartTypes } from '@/views/chat/component/BaseChart.ts'
import { useI18n } from 'vue-i18n'
const props = defineProps<{
id?: number | string
@@ -11,6 +12,8 @@ const props = defineProps<{
data: Array<{ [key: string]: any }>
}>()
const { t } = useI18n()
const chartObject = computed<{
type: ChartTypes
title: string
@@ -91,7 +94,7 @@ defineExpose({
:series="series"
:data="data"
/>
<el-empty v-else description="No Data" />
<el-empty v-else :description="t('chat.no_data')" />
</div>
</template>
@@ -1,5 +1,6 @@
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue'
import { onMounted, reactive, ref, h } from 'vue'
import { ElButton, ElMessage } from 'element-plus-secondary'
import {
findNextComponentIndex,
saveDashboardResourceTarget,
@@ -18,7 +19,7 @@ const optInit = (viewInfo: any) => {
resourceDialogShow.value = true
state.viewInfo = viewInfo
}
const curOptDashboardId = ref(null)
const state = reactive({
dashboardList: [] as SQTreeNode[],
viewInfo: null,
@@ -123,16 +124,55 @@ const saveResourcePrepare = () => {
}
const saveResource = (params: any, commonParams: any) => {
saveDashboardResourceTarget(params, commonParams, function () {
saveDashboardResourceTarget(params, commonParams, (res: any) => {
const messageTips = t('dashboard.add_success')
ElMessage({
type: 'success',
message: messageTips,
})
curOptDashboardId.value = res?.id
openMessageLoading(messageTips, 'success', callbackExportSuc)
resetForm()
})
}
const callbackExportSuc = () => {
// do open dashboard
const url = `#/canvas?resourceId=${curOptDashboardId.value}`
window.open(url, '_self')
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
const openMessageLoading = (text: string, type = 'success', cb: Function) => {
// success error loading
const customClass = `sq-message-${type || 'success'} sq-message-export`
ElMessage({
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
message: h('p', null, [
h(
'span',
{
title: t(text),
class: 'ellipsis m50-export',
},
t(text)
),
h(
ElButton,
{
text: true,
size: 'small',
class: 'btn-text',
onClick: () => {
cb()
},
},
t('dashboard.open_dashboard')
),
]),
type,
showClose: true,
duration: 0,
customClass,
})
}
const initDashboardList = () => {
state.dashboardList = []
const params = {}
@@ -169,7 +169,7 @@ function onClickHistory(chat: Chat) {
currentChatId.value = chat.id
loading.value = true
chatApi
.get(chat.id)
.get_with_Data(chat.id)
.then((res) => {
const info = chatApi.toChatInfo(res)
if (info) {
@@ -116,8 +116,8 @@ export const saveDashboardResourceTarget = (params: any, commonParams: any, call
canvas_style_data: JSON.stringify(commonParams.canvasStyleData),
canvas_view_info: JSON.stringify(commonParams.canvasViewInfo),
}
dashboardApi.update_canvas(requestParams).then(() => {
callBack()
dashboardApi.update_canvas(requestParams).then((res: any) => {
callBack(res)
})
}
} else {