feat: 对话日志支持自定义时间

This commit is contained in:
wxg0103 2024-10-15 18:47:07 +08:00 committed by wxg0103
parent 451be0c81c
commit 3b995d34eb
5 changed files with 136 additions and 31 deletions

View File

@ -97,7 +97,8 @@ class ChatSerializers(serializers.Serializer):
class Query(serializers.Serializer): class Query(serializers.Serializer):
abstract = serializers.CharField(required=False, error_messages=ErrMessage.char("摘要")) abstract = serializers.CharField(required=False, error_messages=ErrMessage.char("摘要"))
history_day = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("历史天数")) start_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date("开始时间"))
end_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date("结束时间"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id")) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id")) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
min_star = serializers.IntegerField(required=False, min_value=0, min_star = serializers.IntegerField(required=False, min_value=0,
@ -110,23 +111,34 @@ class ChatSerializers(serializers.Serializer):
]) ])
def get_end_time(self): def get_end_time(self):
history_day = self.data.get('history_day') return datetime.datetime.combine(
return datetime.datetime.now() - datetime.timedelta(days=history_day) datetime.datetime.strptime(self.data.get('end_time'), '%Y-%m-%d'),
datetime.datetime.max.time())
def get_query_set(self): def get_start_time(self):
return self.data.get('start_time')
def get_query_set(self, select_ids=None):
end_time = self.get_end_time() end_time = self.get_end_time()
start_time = self.get_start_time()
query_set = QuerySet(model=get_dynamics_model( query_set = QuerySet(model=get_dynamics_model(
{'application_chat.application_id': models.CharField(), {'application_chat.application_id': models.CharField(),
'application_chat.abstract': models.CharField(), 'application_chat.abstract': models.CharField(),
"star_num": models.IntegerField(), "star_num": models.IntegerField(),
'trample_num': models.IntegerField(), 'trample_num': models.IntegerField(),
'comparer': models.CharField(), 'comparer': models.CharField(),
'application_chat.create_time': models.DateTimeField()})) 'application_chat.create_time': models.DateTimeField(),
'application_chat.id': models.UUIDField(), }))
base_query_dict = {'application_chat.application_id': self.data.get("application_id"), base_query_dict = {'application_chat.application_id': self.data.get("application_id"),
'application_chat.create_time__gte': end_time} 'application_chat.create_time__gte': start_time,
'application_chat.create_time__lte': end_time,
}
if 'abstract' in self.data and self.data.get('abstract') is not None: if 'abstract' in self.data and self.data.get('abstract') is not None:
base_query_dict['application_chat.abstract__icontains'] = self.data.get('abstract') base_query_dict['application_chat.abstract__icontains'] = self.data.get('abstract')
if select_ids is not None and len(select_ids) > 0:
base_query_dict['application_chat.id__in'] = select_ids
base_condition = Q(**base_query_dict) base_condition = Q(**base_query_dict)
min_star_query = None min_star_query = None
min_trample_query = None min_trample_query = None
@ -176,11 +188,11 @@ class ChatSerializers(serializers.Serializer):
row.get('message_tokens') + row.get('answer_tokens'), row.get('run_time'), row.get('message_tokens') + row.get('answer_tokens'), row.get('run_time'),
str(row.get('create_time'))] str(row.get('create_time'))]
def export(self, with_valid=True): def export(self, data, with_valid=True):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
data_list = native_search(self.get_query_set(), data_list = native_search(self.get_query_set(data.get('select_ids')),
select_string=get_file_content( select_string=get_file_content(
os.path.join(PROJECT_DIR, "apps", "application", 'sql', os.path.join(PROJECT_DIR, "apps", "application", 'sql',
'export_application_chat.sql')), 'export_application_chat.sql')),

View File

@ -54,10 +54,10 @@ class ChatView(APIView):
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
dynamic_tag=keywords.get('application_id'))]) dynamic_tag=keywords.get('application_id'))])
) )
def get(self, request: Request, application_id: str): def post(self, request: Request, application_id: str):
return ChatSerializers.Query( return ChatSerializers.Query(
data={**query_params_to_single_dict(request.query_params), 'application_id': application_id, data={**query_params_to_single_dict(request.query_params), 'application_id': application_id,
'user_id': request.user.id}).export() 'user_id': request.user.id}).export(request.data)
class Open(APIView): class Open(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]

View File

@ -1,5 +1,5 @@
import { Result } from '@/request/Result' import { Result } from '@/request/Result'
import { get, del, put, exportExcel } from '@/request/index' import { get, del, put, exportExcel, exportExcelPost } from '@/request/index'
import type { pageRequest } from '@/api/type/common' import type { pageRequest } from '@/api/type/common'
import { type Ref } from 'vue' import { type Ref } from 'vue'
@ -34,9 +34,10 @@ const exportChatLog: (
application_id: string, application_id: string,
application_name: string, application_name: string,
param: any, param: any,
data: any,
loading?: Ref<boolean> loading?: Ref<boolean>
) => void = (application_id, application_name, param, loading) => { ) => void = (application_id, application_name, param, data, loading) => {
exportExcel(application_name, `${prefix}/${application_id}/chat/export`, param, loading) exportExcelPost(application_name, `${prefix}/${application_id}/chat/export`, param, data, loading)
} }
/** /**

View File

@ -240,6 +240,45 @@ export const exportExcel: (
.catch((e) => {}) .catch((e) => {})
} }
export const exportExcelPost: (
fileName: string,
url: string,
params: any,
data: any,
loading?: NProgress | Ref<boolean>
) => Promise<any> = (
fileName: string,
url: string,
params: any,
data: any,
loading?: NProgress | Ref<boolean>
) => {
return promise(
request({
url: url,
method: 'post',
params, // 查询字符串参数
data, // 请求体数据
responseType: 'blob'
}),
loading
)
.then((res: any) => {
if (res) {
const blob = new Blob([res], {
type: 'application/vnd.ms-excel'
})
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = fileName
link.click()
// 释放内存
window.URL.revokeObjectURL(link.href)
}
return true
})
.catch((e) => {})
}
export const download: ( export const download: (
url: string, url: string,

View File

@ -2,7 +2,7 @@
<LayoutContainer header="对话日志"> <LayoutContainer header="对话日志">
<div class="p-24"> <div class="p-24">
<div class="mb-16"> <div class="mb-16">
<el-select v-model="history_day" class="mr-12 w-240" @change="changeHandle"> <el-select v-model="history_day" class="mr-12 w-120" @change="changeDayHandle">
<el-option <el-option
v-for="item in dayOptions" v-for="item in dayOptions"
:key="item.value" :key="item.value"
@ -10,12 +10,23 @@
:value="item.value" :value="item.value"
/> />
</el-select> </el-select>
<el-date-picker
v-if="history_day === 'other'"
v-model="daterangeValue"
type="daterange"
:start-placeholder="$t('views.applicationOverview.monitor.startDatePlaceholder')"
:end-placeholder="$t('views.applicationOverview.monitor.endDatePlaceholder')"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
@change="changeDayRangeHandle"
/>
<el-input <el-input
v-model="search" v-model="search"
@change="getList" @change="getList"
placeholder="搜索" placeholder="搜索"
prefix-icon="Search" prefix-icon="Search"
class="w-240" class="w-240"
style="margin-left: 10px"
clearable clearable
/> />
<el-button class="float-right" @click="exportLog">导出</el-button> <el-button class="float-right" @click="exportLog">导出</el-button>
@ -29,8 +40,10 @@
@row-click="rowClickHandle" @row-click="rowClickHandle"
v-loading="loading" v-loading="loading"
:row-class-name="setRowClass" :row-class-name="setRowClass"
@selection-change="handleSelectionChange"
class="log-table" class="log-table"
> >
<el-table-column type="selection" width="55" />
<el-table-column prop="abstract" label="摘要" show-overflow-tooltip /> <el-table-column prop="abstract" label="摘要" show-overflow-tooltip />
<el-table-column prop="chat_record_count" label="对话提问数" align="right" /> <el-table-column prop="chat_record_count" label="对话提问数" align="right" />
<el-table-column prop="star_num" align="right"> <el-table-column prop="star_num" align="right">
@ -45,7 +58,9 @@
link link
@click="popoverVisible = !popoverVisible" @click="popoverVisible = !popoverVisible"
> >
<el-icon><Filter /></el-icon> <el-icon>
<Filter />
</el-icon>
</el-button> </el-button>
</template> </template>
<div class="filter"> <div class="filter">
@ -139,9 +154,11 @@ import { cloneDeep } from 'lodash'
import ChatRecordDrawer from './component/ChatRecordDrawer.vue' import ChatRecordDrawer from './component/ChatRecordDrawer.vue'
import { MsgSuccess, MsgConfirm } from '@/utils/message' import { MsgSuccess, MsgConfirm } from '@/utils/message'
import logApi from '@/api/log' import logApi from '@/api/log'
import { datetimeFormat } from '@/utils/time' import { beforeDay, datetimeFormat, nowDate } from '@/utils/time'
import useStore from '@/stores' import useStore from '@/stores'
import type { Dict } from '@/api/type/common' import type { Dict } from '@/api/type/common'
import { t } from '@/locales'
const { application, log } = useStore() const { application, log } = useStore()
const route = useRoute() const route = useRoute()
const { const {
@ -151,21 +168,33 @@ const {
const dayOptions = [ const dayOptions = [
{ {
value: 7, value: 7,
label: '过去7天' // @ts-ignore
label: t('views.applicationOverview.monitor.pastDayOptions.past7Days') // 使 t
}, },
{ {
value: 30, value: 30,
label: '过去30天' label: t('views.applicationOverview.monitor.pastDayOptions.past30Days')
}, },
{ {
value: 90, value: 90,
label: '过去90天' label: t('views.applicationOverview.monitor.pastDayOptions.past90Days')
}, },
{ {
value: 183, value: 183,
label: '过去半年' label: t('views.applicationOverview.monitor.pastDayOptions.past183Days')
},
{
value: 'other',
label: t('views.applicationOverview.monitor.pastDayOptions.other')
} }
] ]
const daterangeValue = ref('')
//
const daterange = ref({
start_time: '',
end_time: ''
})
const multipleSelection = ref<any[]>([])
const ChatRecordRef = ref() const ChatRecordRef = ref()
const loading = ref(false) const loading = ref(false)
@ -182,7 +211,8 @@ const tableIndexMap = computed<Dict<number>>(() => {
})) }))
.reduce((pre, next) => ({ ...pre, ...next }), {}) .reduce((pre, next) => ({ ...pre, ...next }), {})
}) })
const history_day = ref(7) const history_day = ref<number | string>(7)
const search = ref('') const search = ref('')
const detail = ref<any>(null) const detail = ref<any>(null)
@ -279,6 +309,10 @@ const setRowClass = ({ row }: any) => {
return currentChatId.value === row?.id ? 'highlight' : '' return currentChatId.value === row?.id ? 'highlight' : ''
} }
const handleSelectionChange = (val: any[]) => {
multipleSelection.value = val
}
function deleteLog(row: any) { function deleteLog(row: any) {
MsgConfirm(`是否删除对话:${row.abstract} ?`, `删除后无法恢复,请谨慎操作。`, { MsgConfirm(`是否删除对话:${row.abstract} ?`, `删除后无法恢复,请谨慎操作。`, {
confirmButtonText: '删除', confirmButtonText: '删除',
@ -299,15 +333,11 @@ function handleSizeChange() {
getList() getList()
} }
function changeHandle(val: number) {
history_day.value = val
paginationConfig.current_page = 1
getList()
}
function getList() { function getList() {
paginationConfig.current_page = 1
let obj: any = { let obj: any = {
history_day: history_day.value, start_time: daterange.value.start_time,
end_time: daterange.value.end_time,
...filter.value ...filter.value
} }
if (search.value) { if (search.value) {
@ -329,23 +359,46 @@ function getDetail() {
} }
const exportLog = () => { const exportLog = () => {
const arr: string[] = []
multipleSelection.value.map((v) => {
if (v) {
arr.push(v.id)
}
})
if (detail.value) { if (detail.value) {
let obj: any = { let obj: any = {
history_day: history_day.value, start_time: daterange.value.start_time,
end_time: daterange.value.end_time,
...filter.value ...filter.value
} }
if (search.value) { if (search.value) {
obj = { ...obj, abstract: search.value } obj = { ...obj, abstract: search.value }
} }
logApi.exportChatLog(detail.value.id, detail.value.name, obj, loading)
logApi.exportChatLog(detail.value.id, detail.value.name, obj, { select_ids: arr }, loading)
} }
} }
function refresh() { function refresh() {
getList() getList()
} }
onMounted(() => { function changeDayRangeHandle(val: string) {
daterange.value.start_time = val[0]
daterange.value.end_time = val[1]
getList() getList()
}
function changeDayHandle(val: number | string) {
if (val !== 'other') {
daterange.value.start_time = beforeDay(val)
daterange.value.end_time = nowDate
getList()
}
}
onMounted(() => {
changeDayHandle(history_day.value)
getDetail() getDetail()
}) })
</script> </script>