feat: add token usage and top questions statistics retrieval

v3.2
wxg0103 2025-11-13 14:55:47 +08:00
parent 719ba08c6e
commit 74b1bce315
4 changed files with 127 additions and 63 deletions

View File

@ -192,6 +192,26 @@ const getStatistics: (
) => Promise<Result<any>> = (application_id, data, loading) => { ) => Promise<Result<any>> = (application_id, data, loading) => {
return get(`${prefix.value}/${application_id}/application_stats`, data, loading) return get(`${prefix.value}/${application_id}/application_stats`, data, loading)
} }
/**
* token
*/
const getTokenUsage: (
application_id: string,
data: any,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (application_id, data, loading) => {
return get(`${prefix.value}/${application_id}/application_token_usage`, data, loading)
}
/**
*
*/
const topQuestions: (
application_id: string,
data: any,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (application_id, data, loading) => {
return get(`${prefix.value}/${application_id}/top_questions`, data, loading)
}
/** /**
* id * id
* @param application_id id * @param application_id id
@ -207,11 +227,11 @@ const open: (application_id: string, loading?: Ref<boolean>) => Promise<Result<s
/** /**
* *
* @param workspace_id * @param workspace_id
* @param model_id * @param model_id
* @param application_id * @param application_id
* @param data * @param data
* @returns * @returns
*/ */
const generate_prompt: (workspace_id:string ,model_id:string, application_id:string,data: any) => Promise<any> = ( const generate_prompt: (workspace_id:string ,model_id:string, application_id:string,data: any) => Promise<any> = (
workspace_id, workspace_id,
@ -408,5 +428,7 @@ export default {
speechToText, speechToText,
getMcpTools, getMcpTools,
postUploadFile, postUploadFile,
generate_prompt generate_prompt,
getTokenUsage,
topQuestions
} }

View File

@ -111,6 +111,23 @@ const getStatistics: (
) => Promise<Result<any>> = (application_id, data, loading) => { ) => Promise<Result<any>> = (application_id, data, loading) => {
return get(`${prefix}/${application_id}/application_stats`, data, loading) return get(`${prefix}/${application_id}/application_stats`, data, loading)
} }
/**
* token
*/
const getTokenUsage: (
application_id: string,
data: any,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (application_id, data, loading) => {
return get(`${prefix}/${application_id}/application_token_usage`, data, loading)
}
const topQuestions: (
application_id: string,
data: any,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (application_id, data, loading) => {
return get(`${prefix}/${application_id}/top_questions`, data, loading)
}
/** /**
* id * id
* @param application_id id * @param application_id id
@ -126,10 +143,10 @@ const open: (application_id: string, loading?: Ref<boolean>) => Promise<Result<s
/** /**
* *
* @param application_id * @param application_id
* @param model_id * @param model_id
* @param data * @param data
* @returns * @returns
*/ */
const generate_prompt: (application_id:string, model_id:string, data: any) => Promise<any> = ( const generate_prompt: (application_id:string, model_id:string, data: any) => Promise<any> = (
application_id, application_id,
@ -174,7 +191,7 @@ const playDemoText: (application_id: string, data: any, loading?: Ref<boolean>)
* *
*/ */
const postTextToSpeech: ( const postTextToSpeech: (
application_id: String, application_id: string,
data: any, data: any,
loading?: Ref<boolean>, loading?: Ref<boolean>,
) => Promise<Result<any>> = (application_id, data, loading) => { ) => Promise<Result<any>> = (application_id, data, loading) => {
@ -184,7 +201,7 @@ const postTextToSpeech: (
* *
*/ */
const speechToText: ( const speechToText: (
application_id: String, application_id: string,
data: any, data: any,
loading?: Ref<boolean>, loading?: Ref<boolean>,
) => Promise<Result<any>> = (application_id, data, loading) => { ) => Promise<Result<any>> = (application_id, data, loading) => {
@ -289,7 +306,7 @@ const updatePlatformConfig: (
/** /**
* mcp * mcp
*/ */
const getMcpTools: (application_id: String, loading?: Ref<boolean>) => Promise<Result<any>> = ( const getMcpTools: (application_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
application_id, application_id,
loading, loading,
) => { ) => {
@ -320,5 +337,7 @@ export default {
speechToText, speechToText,
getMcpTools, getMcpTools,
putXpackAccessToken, putXpackAccessToken,
generate_prompt generate_prompt,
getTokenUsage,
topQuestions
} }

View File

@ -13,14 +13,14 @@
<el-card shadow="never"> <el-card shadow="never">
<div class="flex align-center ml-8 mr-8"> <div class="flex align-center ml-8 mr-8">
<el-avatar :size="40" shape="square" :style="{ background: item.background }"> <el-avatar :size="40" shape="square" :style="{ background: item.background }">
<appIcon :iconName="item.icon" :style="{ fontSize: '24px', color: item.color }" /> <appIcon :iconName="item.icon" :style="{ fontSize: '24px', color: item.color }"/>
</el-avatar> </el-avatar>
<div class="ml-12"> <div class="ml-12">
<p class="color-secondary lighter mb-4">{{ item.name }}</p> <p class="color-secondary lighter mb-4">{{ item.name }}</p>
<div v-if="item.id !== 'starCharts'" class="flex align-baseline"> <div v-if="item.id !== 'starCharts'" class="flex align-baseline">
<h2>{{ numberFormat(item.sum?.[0]) }}</h2> <h2>{{ numberFormat(item.sum?.[0]) }}</h2>
<span v-if="item.sum.length > 1" class="ml-12" style="color: #f54a45" <span v-if="item.sum.length > 1" class="ml-12" style="color: #f54a45"
>+{{ numberFormat(item.sum?.[1]) }}</span >+{{ numberFormat(item.sum?.[1]) }}</span
> >
</div> </div>
<div v-else class="flex align-center mr-8"> <div v-else class="flex align-center mr-8">
@ -47,23 +47,32 @@
> >
<el-card shadow="never"> <el-card shadow="never">
<div class="p-8"> <div class="p-8">
<AppCharts height="316px" :id="item.id" type="line" :option="item.option" /> <AppCharts height="316px" :id="item.id" type="line" :option="item.option"/>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue' import {ref, computed, onMounted} from 'vue'
import AppCharts from '@/components/app-charts/index.vue' import AppCharts from '@/components/app-charts/index.vue'
import { getAttrsArray, getSum } from '@/utils/array' import {getAttrsArray, getSum} from '@/utils/array'
import { numberFormat } from '@/utils/common' import {numberFormat} from '@/utils/common'
import { t } from '@/locales' import {t} from '@/locales'
const props = defineProps({ const props = defineProps({
data: { data: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
tokenUsage: {
type: Array,
default: () => [],
},
topQuestions: {
type: Array,
default: () => [],
}
}) })
const statisticsType = computed(() => [ const statisticsType = computed(() => [
{ {

View File

@ -11,7 +11,7 @@
<div class="title flex align-center"> <div class="title flex align-center">
<div class="edit-avatar mr-12"> <div class="edit-avatar mr-12">
<el-avatar shape="square" :size="32" style="background: none"> <el-avatar shape="square" :size="32" style="background: none">
<img :src="resetUrl(detail?.icon, resetUrl('./favicon.ico'))" alt="" /> <img :src="resetUrl(detail?.icon, resetUrl('./favicon.ico'))" alt=""/>
</el-avatar> </el-avatar>
</div> </div>
@ -22,7 +22,7 @@
<el-col :span="12" class="mt-16"> <el-col :span="12" class="mt-16">
<div class="flex"> <div class="flex">
<el-text type="info" <el-text type="info"
>{{ $t('views.applicationOverview.appInfo.publicAccessLink') }} >{{ $t('views.applicationOverview.appInfo.publicAccessLink') }}
</el-text> </el-text>
<el-switch <el-switch
v-model="accessToken.is_active" v-model="accessToken.is_active"
@ -52,7 +52,7 @@
style="margin-left: 1px" style="margin-left: 1px"
> >
<el-icon> <el-icon>
<RefreshRight /> <RefreshRight/>
</el-icon> </el-icon>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
@ -98,12 +98,12 @@
<el-col :span="12" class="mt-16"> <el-col :span="12" class="mt-16">
<div class="flex"> <div class="flex">
<el-text type="info" <el-text type="info"
>{{ $t('views.applicationOverview.appInfo.apiAccessCredentials') }} >{{ $t('views.applicationOverview.appInfo.apiAccessCredentials') }}
</el-text> </el-text>
</div> </div>
<div class="mt-4 mb-16 url-height"> <div class="mt-4 mb-16 url-height">
<div> <div>
<el-text>API {{ $t('common.fileUpload.document') }} </el-text> <el-text>API {{ $t('common.fileUpload.document') }}</el-text>
<el-button <el-button
type="primary" type="primary"
link link
@ -119,8 +119,8 @@
</span> </span>
<span class="vertical-middle lighter break-all ellipsis-1">{{ <span class="vertical-middle lighter break-all ellipsis-1">{{
baseUrl + id baseUrl + id
}}</span> }}</span>
<el-tooltip effect="dark" :content="$t('common.copy')" placement="top"> <el-tooltip effect="dark" :content="$t('common.copy')" placement="top">
<el-button type="primary" text @click="copyClick(baseUrl + id)"> <el-button type="primary" text @click="copyClick(baseUrl + id)">
<AppIcon iconName="app-copy"></AppIcon> <AppIcon iconName="app-copy"></AppIcon>
@ -134,7 +134,7 @@
v-if="permissionPrecise.overview_api_key(id)" v-if="permissionPrecise.overview_api_key(id)"
> >
<el-icon class="mr-4"> <el-icon class="mr-4">
<Key /> <Key/>
</el-icon> </el-icon>
{{ $t('views.applicationOverview.appInfo.apiKey') }} {{ $t('views.applicationOverview.appInfo.apiKey') }}
</el-button> </el-button>
@ -173,7 +173,8 @@
/> />
</div> </div>
<div v-loading="statisticsLoading"> <div v-loading="statisticsLoading">
<StatisticsCharts :data="statisticsData" /> <StatisticsCharts :data="statisticsData" :token-usage="tokenUsage"
:top-questions="topQuestions"/>
</div> </div>
</el-card> </el-card>
</div> </div>
@ -184,17 +185,17 @@
:data="detail" :data="detail"
:api-input-params="mapToUrlParams(apiInputParams)" :api-input-params="mapToUrlParams(apiInputParams)"
/> />
<APIKeyDialog ref="APIKeyDialogRef" /> <APIKeyDialog ref="APIKeyDialogRef"/>
<!-- 社区版访问限制 --> <!-- 社区版访问限制 -->
<component :is="currentLimitDialog" ref="LimitDialogRef" @refresh="refresh" /> <component :is="currentLimitDialog" ref="LimitDialogRef" @refresh="refresh"/>
<!-- 显示设置 --> <!-- 显示设置 -->
<component :is="currentDisplaySettingDialog" ref="DisplaySettingDialogRef" @refresh="refresh" /> <component :is="currentDisplaySettingDialog" ref="DisplaySettingDialogRef" @refresh="refresh"/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, shallowRef, nextTick } from 'vue' import {ref, computed, onMounted, shallowRef, nextTick} from 'vue'
import { useRoute } from 'vue-router' import {useRoute} from 'vue-router'
import EmbedDialog from './component/EmbedDialog.vue' import EmbedDialog from './component/EmbedDialog.vue'
import APIKeyDialog from './component/APIKeyDialog.vue' import APIKeyDialog from './component/APIKeyDialog.vue'
import LimitDialog from './component/LimitDialog.vue' import LimitDialog from './component/LimitDialog.vue'
@ -202,20 +203,20 @@ import XPackLimitDrawer from './xpack-component/XPackLimitDrawer.vue'
import DisplaySettingDialog from './component/DisplaySettingDialog.vue' import DisplaySettingDialog from './component/DisplaySettingDialog.vue'
import XPackDisplaySettingDialog from './xpack-component/XPackDisplaySettingDialog.vue' import XPackDisplaySettingDialog from './xpack-component/XPackDisplaySettingDialog.vue'
import StatisticsCharts from './component/StatisticsCharts.vue' import StatisticsCharts from './component/StatisticsCharts.vue'
import { nowDate, beforeDay } from '@/utils/time' import {nowDate, beforeDay} from '@/utils/time'
import { MsgSuccess, MsgConfirm } from '@/utils/message' import {MsgSuccess, MsgConfirm} from '@/utils/message'
import { copyClick } from '@/utils/clipboard' import {copyClick} from '@/utils/clipboard'
import { resetUrl } from '@/utils/common' import {resetUrl} from '@/utils/common'
import { mapToUrlParams } from '@/utils/application' import {mapToUrlParams} from '@/utils/application'
import { t } from '@/locales' import {t} from '@/locales'
import { EditionConst } from '@/utils/permission/data' import {EditionConst} from '@/utils/permission/data'
import { hasPermission } from '@/utils/permission/index' import {hasPermission} from '@/utils/permission/index'
import permissionMap from '@/permission' import permissionMap from '@/permission'
import { loadSharedApi } from '@/utils/dynamics-api/shared-api' import {loadSharedApi} from '@/utils/dynamics-api/shared-api'
const route = useRoute() const route = useRoute()
const { const {
params: { id }, params: {id},
} = route as any } = route as any
const apiType = computed(() => { const apiType = computed(() => {
@ -287,6 +288,8 @@ const daterange = ref({
const statisticsLoading = ref(false) const statisticsLoading = ref(false)
const statisticsData = ref([]) const statisticsData = ref([])
const tokenUsage = ref([])
const topQuestions = ref([])
const apiInputParams = ref([]) const apiInputParams = ref([])
@ -308,7 +311,7 @@ function openDisplaySettingDialog() {
} }
nextTick(() => { nextTick(() => {
if (currentDisplaySettingDialog.value == XPackDisplaySettingDialog) { if (currentDisplaySettingDialog.value == XPackDisplaySettingDialog) {
loadSharedApi({ type: 'application', systemType: apiType.value }) loadSharedApi({type: 'application', systemType: apiType.value})
.getApplicationSetting(id) .getApplicationSetting(id)
.then((ok: any) => { .then((ok: any) => {
DisplaySettingDialogRef.value?.open(ok.data, detail.value) DisplaySettingDialogRef.value?.open(ok.data, detail.value)
@ -351,11 +354,21 @@ function changeDayRangeHandle(val: string) {
} }
function getAppStatistics() { function getAppStatistics() {
loadSharedApi({ type: 'application', systemType: apiType.value }) loadSharedApi({type: 'application', systemType: apiType.value})
.getStatistics(id, daterange.value, statisticsLoading) .getStatistics(id, daterange.value, statisticsLoading)
.then((res: any) => { .then((res: any) => {
statisticsData.value = res.data statisticsData.value = res.data
}) })
loadSharedApi({type: 'application', systemType: apiType.value})
.getTokenUsage(id, daterange.value, statisticsLoading)
.then((res: any) => {
tokenUsage.value = res.data
})
loadSharedApi({type: 'application', systemType: apiType.value})
.topQuestions(id, daterange.value, statisticsLoading)
.then((res: any) => {
topQuestions.value = res.data
})
} }
function refreshAccessToken() { function refreshAccessToken() {
@ -374,7 +387,8 @@ function refreshAccessToken() {
const str = t('views.applicationOverview.appInfo.refreshToken.refreshSuccess') const str = t('views.applicationOverview.appInfo.refreshToken.refreshSuccess')
updateAccessToken(obj, str) updateAccessToken(obj, str)
}) })
.catch(() => {}) .catch(() => {
})
} }
async function changeState(bool: boolean) { async function changeState(bool: boolean) {
@ -392,7 +406,7 @@ async function changeState(bool: boolean) {
} }
async function updateAccessToken(obj: any, str: string) { async function updateAccessToken(obj: any, str: string) {
loadSharedApi({ type: 'application', systemType: apiType.value }) loadSharedApi({type: 'application', systemType: apiType.value})
.putAccessToken(id as string, obj, loading) .putAccessToken(id as string, obj, loading)
.then((res: any) => { .then((res: any) => {
accessToken.value = res?.data accessToken.value = res?.data
@ -409,7 +423,7 @@ function openDialog() {
} }
function getAccessToken() { function getAccessToken() {
loadSharedApi({ type: 'application', systemType: apiType.value }) loadSharedApi({type: 'application', systemType: apiType.value})
.getAccessToken(id, loading) .getAccessToken(id, loading)
.then((res: any) => { .then((res: any) => {
accessToken.value = res?.data accessToken.value = res?.data
@ -417,7 +431,7 @@ function getAccessToken() {
} }
function getDetail() { function getDetail() {
loadSharedApi({ type: 'application', systemType: apiType.value }) loadSharedApi({type: 'application', systemType: apiType.value})
.getApplicationDetail(id, loading) .getApplicationDetail(id, loading)
.then((res: any) => { .then((res: any) => {
detail.value = res.data detail.value = res.data
@ -426,20 +440,20 @@ function getDetail() {
.map((v: any) => { .map((v: any) => {
apiInputParams.value = v.properties.api_input_field_list apiInputParams.value = v.properties.api_input_field_list
? v.properties.api_input_field_list.map((v: any) => { ? v.properties.api_input_field_list.map((v: any) => {
return { return {
name: v.variable, name: v.variable,
value: v.default_value, value: v.default_value,
} }
}) })
: v.properties.input_field_list : v.properties.input_field_list
? v.properties.input_field_list ? v.properties.input_field_list
.filter((v: any) => v.assignment_method === 'api_input') .filter((v: any) => v.assignment_method === 'api_input')
.map((v: any) => { .map((v: any) => {
return { return {
name: v.variable, name: v.variable,
value: v.default_value, value: v.default_value,
} }
}) })
: [] : []
}) })
}) })