1.5.8前端

AI平台完善
AI Token使用记录完成
master
xs 3 months ago
parent 637223517a
commit f0ad440029

@ -0,0 +1,91 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { TokenUsageVO, TokenUsageForm, TokenUsageQuery } from '@/api/ai/tokenUsage/types';
/**
* token使
* @param query
* @returns {*}
*/
export const listTokenUsage = (query?: TokenUsageQuery): AxiosPromise<TokenUsageVO[]> => {
return request({
url: '/ai/tokenUsage/list',
method: 'get',
params: query
});
};
/**
* token使
* @param tokenUsageId
*/
export const getTokenUsage = (tokenUsageId: string | number): AxiosPromise<TokenUsageVO> => {
return request({
url: '/ai/tokenUsage/' + tokenUsageId,
method: 'get'
});
};
/**
* token使
* @param data
*/
export const addTokenUsage = (data: TokenUsageForm) => {
return request({
url: '/ai/tokenUsage',
method: 'post',
data: data
});
};
/**
* token使
* @param data
*/
export const updateTokenUsage = (data: TokenUsageForm) => {
return request({
url: '/ai/tokenUsage',
method: 'put',
data: data
});
};
/**
* token使
* @param tokenUsageId
*/
export const delTokenUsage = (tokenUsageId: string | number | Array<string | number>) => {
return request({
url: '/ai/tokenUsage/' + tokenUsageId,
method: 'delete'
});
};
/**
* token使
* @param query
* @returns {*}
*/
export function getAiTokenUsageList (query) {
return request({
url: '/ai/tokenUsage/getAiTokenUsageList',
method: 'get',
params: query
});
};
/**
* AI
* @param query
* @returns {*}
*/
export function getAiChatMessageDetailList (query) {
return request({
url: '/ai/aiChatMessage/listDetail',
method: 'get',
params: query
});
};

@ -0,0 +1,121 @@
export interface TokenUsageVO {
/**
*
*/
tokenUsageId: string | number;
/**
*
*/
userId: string | number;
/**
* token
*/
token: number;
/**
* IDai_model
*/
modelId: string | number;
/**
* token
*/
promptToken: number;
/**
* token
*/
completionToken: number;
/**
* 使token
*/
totalToken: number;
}
export interface TokenUsageForm extends BaseEntity {
/**
*
*/
tokenUsageId?: string | number;
/**
*
*/
userId?: string | number;
/**
* token
*/
token?: number;
/**
* IDai_model
*/
modelId?: string | number;
/**
* token
*/
promptToken?: number;
/**
* token
*/
completionToken?: number;
/**
* 使token
*/
totalToken?: number;
}
export interface TokenUsageQuery extends PageQuery {
/**
*
*/
tokenUsageId?: string | number;
/**
*
*/
userId?: string | number;
/**
* token
*/
token?: number;
/**
* IDai_model
*/
modelId?: string | number;
/**
* token
*/
promptToken?: number;
/**
* token
*/
completionToken?: number;
/**
* 使token
*/
totalToken?: number;
/**
*
*/
params?: any;
}

@ -845,7 +845,7 @@ onMounted(() => {
}
.platform-card {
min-height: 400px;
min-height: 260px;
}
.platform-item {
@ -867,8 +867,9 @@ onMounted(() => {
.platform-header {
display: flex;
align-items: flex-start;
gap: 12px;
gap: 6px;
margin-bottom: 16px;
height:100px;
}
.platform-icon {
@ -933,7 +934,7 @@ onMounted(() => {
font-size: 18px;
font-weight: 600;
color: #1f2937;
line-height: 3.2;
line-height: 1.6;
}
.platform-description {
@ -1001,12 +1002,7 @@ onMounted(() => {
color: #409eff;
}
.platform-name {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #303133;
}
.platform-description {
font-size: 16px;

@ -12,69 +12,49 @@
<div v-show="!isLeftPanelCollapsed">
<!-- 搜索区域 -->
<el-form :model="userQueryParams" ref="userQueryFormRef" :inline="true" label-width="80px">
<el-form-item label="用户姓名" prop="userName">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px">
<el-form-item label="用户昵称" prop="nickName">
<el-input
v-model="userQueryParams.userName"
placeholder="请输入用户姓名进行模糊搜索"
v-model="queryParams.nickName"
placeholder="请输入用户昵称"
clearable
style="width: 200px"
@keyup.enter="handleUserQuery"
@keyup.enter="getList"
/>
</el-form-item>
<el-form-item label="AI模型" prop="aiModel">
<el-select v-model="userQueryParams.aiModel" placeholder="请选择AI模型" clearable style="width: 200px">
<el-option label="GPT-3.5" value="gpt-3.5" />
<el-option label="GPT-4" value="gpt-4" />
<el-option label="Claude-3" value="claude-3" />
<el-option label="DeepSeek" value="deepseek" />
<el-option label="通义千问" value="qwen" />
<el-form-item label="AI模型" prop="modelId">
<el-select v-model="queryParams.modelId" placeholder="请选择AI模型" clearable>
<el-option
v-for="dict in aiModelList"
:key="dict.modelId"
:label="dict.modelName"
:value="dict.modelId"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleUserQuery"></el-button>
<el-button icon="Refresh" @click="resetUserQuery"></el-button>
<el-button type="primary" icon="Search" @click="getList"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<!-- 用户列表 -->
<el-table
v-loading="userLoading"
:data="userPaginatedList"
v-loading="loading"
:data="tokenUsageList"
@row-click="handleUserSelect"
highlight-current-row
:row-class-name="getUserRowClass"
>
<el-table-column label="用户姓名" align="center" prop="userName" />
<el-table-column label="AI模型" align="center" prop="aiModel" />
<el-table-column label="提问Token" align="center" prop="promptTokens">
<template #default="scope">
<span class="token-count">{{ scope.row.promptTokens.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column label="回答Token" align="center" prop="completionTokens">
<template #default="scope">
<span class="token-count">{{ scope.row.completionTokens.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column label="总Token" align="center" prop="totalTokens">
<template #default="scope">
<span class="token-count total">{{ scope.row.totalTokens.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column label="用户" align="center" prop="nickName" v-if="columns[2].visible"/>
<el-table-column label="AI模型" align="center" prop="modelName" v-if="columns[4].visible"/>
<el-table-column label="提问Token" align="center" prop="promptToken" v-if="columns[5].visible"/>
<el-table-column label="回复Token" align="center" prop="completionToken" v-if="columns[6].visible"/>
<el-table-column label="总Token" align="center" prop="totalToken" v-if="columns[7].visible"/>
</el-table>
<!-- 用户分页 -->
<el-pagination
v-show="userTotal > 0"
class="mt-4"
:total="userTotal"
v-model:current-page="userQueryParams.pageNum"
v-model:page-size="userQueryParams.pageSize"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleUserQuery"
@current-change="handleUserQuery"
/>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList"/>
</div>
</el-card>
</div>
@ -94,74 +74,256 @@
<template #header>
<div class="card-header">
<span>Token使用详情记录</span>
<span v-if="selectedUser" class="selected-user">{{ selectedUser.userName }}</span>
<span v-if="selectedUser" class="selected-user">{{
selectedUser.nickName
}}当前模型{{ selectedUser.modelName }}</span>
</div>
</template>
<!-- 搜索区域 -->
<el-form :model="detailQueryParams" ref="detailQueryFormRef" :inline="true" label-width="80px">
<el-form-item label="使用类型" prop="usageType">
<el-select v-model="detailQueryParams.usageType" placeholder="请选择使用类型" clearable style="width: 200px">
<el-option label="AI问答" value="ai_chat" />
<el-option label="AI知识库" value="ai_knowledge" />
<el-option label="AI生成SQL" value="ai_sql" />
<el-option label="AI智能填报" value="ai_form" />
<el-form-item label="使用类型" prop="messageDetailType">
<el-select v-model='detailQueryParams.messageDetailType' placeholder='请选择使用类型' clearable>
<el-option v-for='dict in ai_chat_message_detail_type' :key='dict.value' :label='dict.label' :value='dict.value'/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleDetailQuery"></el-button>
<el-button type="primary" icon="Search" @click="getDetailList"></el-button>
<el-button icon="Refresh" @click="resetDetailQuery"></el-button>
</el-form-item>
</el-form>
<!-- 详情列表 -->
<el-table v-loading="detailLoading" :data="detailPaginatedList">
<el-table-column label="用户姓名" align="center" prop="userName" />
<el-table-column label="使用类型" align="center" prop="usageType">
<el-table v-loading="detailLoading" :data="detailList">
<el-table-column label="用户姓名" align="center" prop="nickName"/>
<el-table-column label="使用类型" align="center" prop="messageDetailType">
<template #default="scope">
<el-tag :type="getUsageTypeTagType(scope.row.usageType)">
{{ getUsageTypeLabel(scope.row.usageType) }}
</el-tag>
<dict-tag :options='ai_chat_message_detail_type' :value='scope.row.messageDetailType'/>
</template>
</el-table-column>
<el-table-column label="AI模型" align="center" prop="aiModel" />
<el-table-column label="会话名称" align="center" prop="sessionName" :show-overflow-tooltip="true" />
<el-table-column label="提问Token" align="center" prop="promptTokens">
<el-table-column label="AI模型" align="center" prop="modelName"/>
<el-table-column label="提问内容" align="center" prop="questionContent" :show-overflow-tooltip="true"/>
<el-table-column label="回复内容" align="center" prop="answerContent" :show-overflow-tooltip="true"/>
<el-table-column label="提问Token" align="center" prop="promptToken">
<template #default="scope">
<span class="token-count">{{ scope.row.promptTokens.toLocaleString() }}</span>
<span class="token-count">{{ scope.row.promptToken?.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column label="回答Token" align="center" prop="completionTokens">
<el-table-column label="回答Token" align="center" prop="completionToken">
<template #default="scope">
<span class="token-count">{{ scope.row.completionTokens.toLocaleString() }}</span>
<span class="token-count">{{ scope.row.completionToken?.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column label="总Token" align="center" prop="totalTokens">
<el-table-column label="总Token" align="center" prop="totalToken">
<template #default="scope">
<span class="token-count total">{{ scope.row.totalTokens.toLocaleString() }}</span>
<span class="token-count total">{{ scope.row.totalToken?.toLocaleString() }}</span>
</template>
</el-table-column>
</el-table>
<!-- 详情分页 -->
<el-pagination
v-show="detailTotal > 0"
class="mt-4"
:total="detailTotal"
v-model:current-page="detailQueryParams.pageNum"
v-model:page-size="detailQueryParams.pageSize"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleDetailQuery"
@current-change="handleDetailQuery"
/>
<pagination v-show="detailTotal > 0" :total="detailTotal" v-model:page="detailQueryParams.pageNum"
v-model:limit="detailQueryParams.pageSize" @pagination="getDetailList"/>
</el-card>
</div>
</div>
</div>
</template>
<script setup name="TokenUsageDetails">
import { ref, reactive, onMounted, computed } from 'vue';
<script setup name="TokenUsage" lang="ts">
import {ref, reactive, onMounted, computed} from 'vue';
import {listTokenUsage, getAiChatMessageDetailList, getTokenUsage} from '@/api/ai/record/tokenUsage';
import {TokenUsageVO, TokenUsageQuery, TokenUsageForm} from '@/api/ai/record/tokenUsage/types';
import {getAiModelList} from '@/api/ai/base/aiModel';
import {AiModelVO} from '@/api/ai/base/aiModel/types';
const {proxy} = getCurrentInstance() as ComponentInternalInstance;
const {ai_chat_message_detail_type} = toRefs<any>(proxy?.useDict('ai_chat_message_detail_type'));
const selectedUser = ref(null); //
const tokenUsageList = ref<TokenUsageVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const tokenUsageFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
//
const columns = ref<FieldOption[]>([
{key: 0, label: `主键`, visible: true},
{key: 1, label: `租户ID`, visible: true},
{key: 2, label: `用户`, visible: true},
{key: 3, label: `待结算token`, visible: true},
{key: 4, label: `模型ID关联ai_model`, visible: true},
{key: 5, label: `提问token数量`, visible: true},
{key: 6, label: `回复token数量`, visible: true},
{key: 7, label: `累计使用token`, visible: true},
]);
const initFormData: TokenUsageForm = {
tokenUsageId: undefined,
userId: undefined,
token: undefined,
modelId: undefined,
promptToken: undefined,
completionToken: undefined,
totalToken: undefined
}
const data = reactive<PageData<TokenUsageForm, TokenUsageQuery>>({
form: {...initFormData},
queryParams: {
pageNum: 1,
pageSize: 10,
tokenUsageId: undefined,
userId: undefined,
token: undefined,
modelId: undefined,
promptToken: undefined,
completionToken: undefined,
totalToken: undefined,
params: {}
},
rules: {
tokenUsageId: [
{required: true, message: "主键不能为空", trigger: "blur"}
],
userId: [
{required: true, message: "用户不能为空", trigger: "blur"}
],
token: [
{required: true, message: "待结算token不能为空", trigger: "blur"}
],
modelId: [
{required: true, message: "模型ID关联ai_model不能为空", trigger: "blur"}
],
promptToken: [
{required: true, message: "提问token数量不能为空", trigger: "blur"}
],
completionToken: [
{required: true, message: "回复token数量不能为空", trigger: "blur"}
],
totalToken: [
{required: true, message: "累计使用token不能为空", trigger: "blur"}
]
}
});
const {queryParams, form, rules} = toRefs(data);
const aiModelList = ref<AiModelVO[]>([]);
/** 查询AI模型列表 */
const getAIModelList = async () => {
const res = await getAiModelList({});
aiModelList.value = res.data;
}
/** 查询用户token使用详情列表 */
const getList = async () => {
loading.value = true;
const res = await listTokenUsage(queryParams.value);
tokenUsageList.value = res.rows;
total.value = res.total;
loading.value = false;
}
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
}
/** 表单重置 */
const reset = () => {
form.value = {...initFormData};
tokenUsageFormRef.value?.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
}
const handleDetailQuery = () => {
detailQueryParams.pageNum = 1;
getDetailList();
}
/** 重置按钮操作 */
const resetDetailQuery = () => {
detailQueryFormRef.value?.resetFields();
selectedUser.value = null;
detailQueryParams.userId = undefined;
detailQueryParams.modelId = undefined;
handleDetailQuery();
}
/** 多选框选中数据 */
const handleSelectionChange = (selection: TokenUsageVO[]) => {
ids.value = selection.map(item => item.tokenUsageId);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
// ================= =================
const detailLoading = ref(false);
const detailList = ref([]);
const detailTotal = ref(0);
const detailQueryFormRef = ref();
const detailQueryParams = reactive({
pageNum: 1,
pageSize: 10,
messageDetailType: '',
createBy: null, // ID
modelId: null
});
//
const getDetailList = async () => {
detailLoading.value = true;
const res = await getAiChatMessageDetailList(detailQueryParams);
detailList.value = res.rows;
detailTotal.value = res.total;
detailLoading.value = false;
};
// ================= =================
onMounted(() => {
getAIModelList();
getList();
getDetailList();
});
//
const handleUserSelect = (row) => {
selectedUser.value = row;
detailQueryParams.userId = row.userId;
detailQueryParams.modelId = row.modelId;
getDetailList();
};
// ================= MOCK DATA =================
//
@ -372,135 +534,12 @@ const getUsageTypeTagType = (usageType) => {
* 获取用户行样式类名
* @param {object} param0
*/
const getUserRowClass = ({ row }) => {
const getUserRowClass = ({row}) => {
return selectedUser.value && row.id === selectedUser.value.id ? 'selected-row' : '';
};
// ================= END =================
// ================= =================
const userLoading = ref(false);
const userList = ref([]);
const userTotal = ref(0);
const userQueryFormRef = ref();
const userQueryParams = reactive({
pageNum: 1,
pageSize: 10,
userName: '',
aiModel: '',
});
//
const userFilteredList = computed(() => {
let list = userList.value;
if (userQueryParams.userName) {
list = list.filter(item => item.userName.includes(userQueryParams.userName));
}
if (userQueryParams.aiModel) {
list = list.filter(item => item.aiModel === userQueryParams.aiModel);
}
return list;
});
const userPaginatedList = computed(() => {
userTotal.value = userFilteredList.value.length;
const start = (userQueryParams.pageNum - 1) * userQueryParams.pageSize;
const end = start + userQueryParams.pageSize;
return userFilteredList.value.slice(start, end);
});
//
const getUserList = async () => {
userLoading.value = true;
const res = await mockApiCall(mockUserList);
userList.value = res;
handleUserQuery();
userLoading.value = false;
};
//
const handleUserQuery = () => {
userQueryParams.pageNum = 1;
};
//
const resetUserQuery = () => {
userQueryFormRef.value.resetFields();
handleUserQuery();
};
// ================= END =================
// ================= =================
const detailLoading = ref(false);
const detailList = ref([]);
const detailTotal = ref(0);
const detailQueryFormRef = ref();
const detailQueryParams = reactive({
pageNum: 1,
pageSize: 10,
usageType: '',
userId: null, // ID
});
const selectedUser = ref(null); //
//
const detailFilteredList = computed(() => {
let list = detailList.value;
//
if (detailQueryParams.userId) {
list = list.filter(item => item.userId === detailQueryParams.userId);
}
// 使
if (detailQueryParams.usageType) {
list = list.filter(item => item.usageType === detailQueryParams.usageType);
}
return list;
});
const detailPaginatedList = computed(() => {
detailTotal.value = detailFilteredList.value.length;
const start = (detailQueryParams.pageNum - 1) * detailQueryParams.pageSize;
const end = start + detailQueryParams.pageSize;
return detailFilteredList.value.slice(start, end);
});
//
const getDetailList = async () => {
detailLoading.value = true;
const res = await mockApiCall(mockDetailList);
detailList.value = res;
handleDetailQuery();
detailLoading.value = false;
};
//
const handleDetailQuery = () => {
detailQueryParams.pageNum = 1;
};
//
const resetDetailQuery = () => {
detailQueryFormRef.value.resetFields();
detailQueryParams.userId = selectedUser.value ? selectedUser.value.id : null;
handleDetailQuery();
};
//
const handleUserSelect = (row) => {
selectedUser.value = row;
detailQueryParams.userId = row.id;
handleDetailQuery();
};
// ================= END =================
// ================= =================
onMounted(() => {
getUserList();
getDetailList();
});
</script>
<style scoped>

Loading…
Cancel
Save