fix
This commit is contained in:
@@ -1,127 +1,124 @@
|
||||
<template>
|
||||
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible"
|
||||
:close-on-click-modal="false" draggable>
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="用户ID" prop="userId">
|
||||
<el-input v-model="form.userId" placeholder="请输入用户ID"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable>
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="用户ID" prop="userId">
|
||||
<el-input v-model="form.userId" placeholder="请输入用户ID" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="提示令牌数量" prop="promptTokens">
|
||||
<el-input v-model="form.promptTokens" placeholder="请输入提示令牌数量"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="提示令牌数量" prop="promptTokens">
|
||||
<el-input v-model="form.promptTokens" placeholder="请输入提示令牌数量" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="补全令牌数量" prop="completionTokens">
|
||||
<el-input v-model="form.completionTokens" placeholder="请输入补全令牌数量"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="补全令牌数量" prop="completionTokens">
|
||||
<el-input v-model="form.completionTokens" placeholder="请输入补全令牌数量" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="模型名称" prop="model">
|
||||
<el-input v-model="form.model" placeholder="请输入模型名称"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="模型名称" prop="model">
|
||||
<el-input v-model="form.model" placeholder="请输入模型名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="请求ID" prop="reqid">
|
||||
<el-input v-model="form.reqid" placeholder="请输入请求ID"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="请求ID" prop="reqid">
|
||||
<el-input v-model="form.reqid" placeholder="请输入请求ID" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="IP地址" prop="ip">
|
||||
<el-input v-model="form.ip" placeholder="请输入IP地址"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="IP地址" prop="ip">
|
||||
<el-input v-model="form.ip" placeholder="请输入IP地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="备注" prop="note">
|
||||
<el-input v-model="form.note" placeholder="请输入备注"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="备注" prop="note">
|
||||
<el-input v-model="form.note" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="令牌ID" prop="tokenId">
|
||||
<el-input v-model="form.tokenId" placeholder="请输入令牌ID"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="令牌ID" prop="tokenId">
|
||||
<el-input v-model="form.tokenId" placeholder="请输入令牌ID" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="令牌数量" prop="tokens">
|
||||
<el-input v-model="form.tokens" placeholder="请输入令牌数量"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="令牌类型 0 系统 1 用户" prop="tokenType">
|
||||
<el-input v-model="form.tokenType" placeholder="请输入令牌类型 0 系统 1 用户"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="令牌数量" prop="tokens">
|
||||
<el-input v-model="form.tokens" placeholder="请输入令牌数量" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="令牌类型 0 系统 1 用户" prop="tokenType">
|
||||
<el-input v-model="form.tokenType" placeholder="请输入令牌类型 0 系统 1 用户" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="AiBillDialog">
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from "/@/hooks/message";
|
||||
import { getObj, addObj, putObj } from '/@/api/knowledge/aiBill'
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { getObj, addObj, putObj } from '/@/api/knowledge/aiBill';
|
||||
import { rule } from '/@/utils/validate';
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
// 定义字典
|
||||
const { yes_no_type } = useDict('yes_no_type')
|
||||
const { yes_no_type } = useDict('yes_no_type');
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id:'',
|
||||
userId: '',
|
||||
promptTokens: '',
|
||||
completionTokens: '',
|
||||
model: '',
|
||||
reqid: '',
|
||||
ip: '',
|
||||
note: '',
|
||||
tokenId: '',
|
||||
tokens: '',
|
||||
tokenType: '',
|
||||
id: '',
|
||||
userId: '',
|
||||
promptTokens: '',
|
||||
completionTokens: '',
|
||||
model: '',
|
||||
reqid: '',
|
||||
ip: '',
|
||||
note: '',
|
||||
tokenId: '',
|
||||
tokens: '',
|
||||
tokenType: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
})
|
||||
const dataRules = ref({});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true
|
||||
form.id = ''
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
|
||||
// 重置表单数据
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取aiBill信息
|
||||
if (id) {
|
||||
form.id = id
|
||||
getaiBillData(id)
|
||||
}
|
||||
// 获取aiBill信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getaiBillData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
@@ -130,7 +127,7 @@ const onSubmit = async () => {
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
loading.value = true;
|
||||
form.id ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(form.id ? '修改成功' : '添加成功');
|
||||
visible.value = false;
|
||||
@@ -138,24 +135,25 @@ const onSubmit = async () => {
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 初始化表单数据
|
||||
const getaiBillData = (id: string) => {
|
||||
// 获取数据
|
||||
loading.value = true
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data)
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
// 获取数据
|
||||
loading.value = true;
|
||||
getObj(id)
|
||||
.then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,82 +1,82 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 顶部折线图-->
|
||||
<bill-line-chart />
|
||||
|
||||
<!-- 顶部折线图-->
|
||||
<bill-line-chart/>
|
||||
<el-row v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input placeholder="请输入用户名" v-model="state.queryForm.username" />
|
||||
</el-form-item>
|
||||
<el-form-item label="系统调用" prop="tokenType">
|
||||
<el-radio-group v-model="state.queryForm.tokenType">
|
||||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in yes_no_type">{{ item.label }} </el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList"> 查询 </el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'knowledge_aiBill_del'" @click="handleDelete(selectObjs)">
|
||||
删除
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'knowledge_aiBill_export'"
|
||||
@exportExcel="exportExcel"
|
||||
class="ml10 mr20"
|
||||
style="float: right"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
@selection-change="selectionChangHandle"
|
||||
@sort-change="sortChangeHandle"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" label="#" width="40" />
|
||||
<el-table-column prop="createBy" label="用户名" show-overflow-tooltip />
|
||||
<el-table-column prop="model" label="模型名称" show-overflow-tooltip />
|
||||
<el-table-column prop="tokens" label="tokens" sortable="custom" show-overflow-tooltip />
|
||||
<el-table-column prop="promptTokens" label="prompt tokens" show-overflow-tooltip />
|
||||
<el-table-column prop="completionTokens" label="completion" show-overflow-tooltip />
|
||||
<el-table-column prop="ip" label="IP地址" show-overflow-tooltip />
|
||||
<el-table-column prop="tokenType" label="系统调用" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="yes_no_type" :value="scope.row.tokenType"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="调用时间" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="delete" text type="primary" v-auth="'knowledge_aiBill_del'" @click="handleDelete([scope.row.id])">删除 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<el-row v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input placeholder="请输入用户名" v-model="state.queryForm.username"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="系统调用" prop="tokenType">
|
||||
<el-radio-group v-model="state.queryForm.tokenType">
|
||||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in yes_no_type">{{
|
||||
item.label
|
||||
}}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList">
|
||||
查询
|
||||
</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary"
|
||||
v-auth="'knowledge_aiBill_del'" @click="handleDelete(selectObjs)">
|
||||
删除
|
||||
</el-button>
|
||||
<right-toolbar v-model:showSearch="showSearch" :export="'knowledge_aiBill_export'"
|
||||
@exportExcel="exportExcel" class="ml10 mr20" style="float: right;"
|
||||
@queryTable="getDataList"></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table :data="state.dataList" v-loading="state.loading" border
|
||||
:cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle"
|
||||
@selection-change="selectionChangHandle"
|
||||
@sort-change="sortChangeHandle">
|
||||
<el-table-column type="selection" width="40" align="center"/>
|
||||
<el-table-column type="index" label="#" width="40"/>
|
||||
<el-table-column prop="createBy" label="用户名" show-overflow-tooltip/>
|
||||
<el-table-column prop="model" label="模型名称" show-overflow-tooltip/>
|
||||
<el-table-column prop="tokens" label="tokens" sortable="custom" show-overflow-tooltip/>
|
||||
<el-table-column prop="promptTokens" label="prompt tokens" show-overflow-tooltip/>
|
||||
<el-table-column prop="completionTokens" label="completion" show-overflow-tooltip/>
|
||||
<el-table-column prop="ip" label="IP地址" show-overflow-tooltip/>
|
||||
<el-table-column prop="tokenType" label="系统调用" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="yes_no_type" :value="scope.row.tokenType"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="调用时间" show-overflow-tooltip/>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="delete" text type="primary" v-auth="'knowledge_aiBill_del'"
|
||||
@click="handleDelete([scope.row.id])">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination"/>
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList(false)"/>
|
||||
|
||||
</div>
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemAiBill">
|
||||
import {BasicTableProps, useTable} from "/@/hooks/table";
|
||||
import {fetchList, delObjs} from "/@/api/knowledge/aiBill";
|
||||
import {useMessage, useMessageBox} from "/@/hooks/message";
|
||||
import {useDict} from '/@/hooks/dict';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList, delObjs } from '/@/api/knowledge/aiBill';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
@@ -84,66 +84,59 @@ const BillLineChart = defineAsyncComponent(() => import('./line-chart.vue'));
|
||||
|
||||
// 定义查询字典
|
||||
|
||||
const {yes_no_type} = useDict('yes_no_type')
|
||||
const { yes_no_type } = useDict('yes_no_type');
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref()
|
||||
const formDialogRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any
|
||||
const multiple = ref(true)
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time']
|
||||
})
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
sortChangeHandle,
|
||||
downBlobFile,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields()
|
||||
// 清空多选
|
||||
selectObjs.value = []
|
||||
getDataList()
|
||||
}
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields();
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/knowledge/aiBill/export', Object.assign(state.queryForm, {ids: selectObjs}), 'aiBill.xlsx')
|
||||
}
|
||||
downBlobFile('/knowledge/aiBill/export', Object.assign(state.queryForm, { ids: selectObjs }), 'aiBill.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const selectionChangHandle = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({id}) => id);
|
||||
multiple.value = !objs.length;
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm('此操作将永久删除');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await useMessageBox().confirm('此操作将永久删除');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success('删除成功');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
try {
|
||||
await delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success('删除成功');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -208,8 +208,8 @@ const createNewChat = () => {
|
||||
const currentRoute = router.currentRoute.value;
|
||||
|
||||
// 从路由查询参数中获取mcpId和dataId
|
||||
const mcpId = currentRoute.query.mcpId as string || '';
|
||||
const dataId = currentRoute.query.dataId as string || '';
|
||||
const mcpId = (currentRoute.query.mcpId as string) || '';
|
||||
const dataId = (currentRoute.query.dataId as string) || '';
|
||||
|
||||
// 格式化当前日期和时间作为标题
|
||||
const now = new Date();
|
||||
|
||||
@@ -86,11 +86,7 @@
|
||||
collapseStates['tool_' + index] === true ? 'collapse-open' : 'collapse-close',
|
||||
]"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="peer"
|
||||
@change="collapseStates['tool_' + index] = !collapseStates['tool_' + index]"
|
||||
/>
|
||||
<input type="checkbox" class="peer" @change="collapseStates['tool_' + index] = !collapseStates['tool_' + index]" />
|
||||
<div
|
||||
class="text-sm font-medium text-gray-700 collapse-title dark:text-gray-300 peer-checked:bg-gray-100 dark:peer-checked:bg-gray-700/50 hover:bg-gray-100 dark:hover:bg-gray-700/50"
|
||||
>
|
||||
@@ -108,12 +104,7 @@
|
||||
stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
工具调用:{{ message.toolInfo.name }}
|
||||
</div>
|
||||
|
||||
@@ -296,16 +296,12 @@ watch(
|
||||
// 如果有参数且没有选择对话字段,自动选择第一个适合的参数作为对话字段
|
||||
if (flowStartParams.value.length > 0 && !selectedChatField.value) {
|
||||
// 优先选择名称包含 prompt、message、input、content 的参数
|
||||
const preferredField = flowStartParams.value.find(param =>
|
||||
/prompt|message|input|content|text/i.test(param.name)
|
||||
);
|
||||
const preferredField = flowStartParams.value.find((param) => /prompt|message|input|content|text/i.test(param.name));
|
||||
if (preferredField) {
|
||||
selectedChatField.value = preferredField.name;
|
||||
} else {
|
||||
// 如果没有找到优先字段,选择第一个字符串类型的参数
|
||||
const firstStringField = flowStartParams.value.find(param =>
|
||||
param.inputType === 'input' || param.inputType === 'textarea'
|
||||
);
|
||||
const firstStringField = flowStartParams.value.find((param) => param.inputType === 'input' || param.inputType === 'textarea');
|
||||
if (firstStringField) {
|
||||
selectedChatField.value = firstStringField.name;
|
||||
}
|
||||
@@ -348,7 +344,7 @@ const sendFlowChatMessage = async (content: string) => {
|
||||
|
||||
// 动态构建执行参数,基于流程开始节点的参数配置
|
||||
const executeParams: Record<string, any> = {};
|
||||
|
||||
|
||||
if (flowStartParams.value && flowStartParams.value.length > 0) {
|
||||
// 根据参数配置构建执行参数
|
||||
flowStartParams.value.forEach((param: any) => {
|
||||
@@ -383,7 +379,7 @@ const sendFlowChatMessage = async (content: string) => {
|
||||
Local.set(conversationKey.value, JSON.stringify(messageList.value));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 滚动到底部
|
||||
debouncedScrollToBottom();
|
||||
},
|
||||
@@ -393,7 +389,7 @@ const sendFlowChatMessage = async (content: string) => {
|
||||
onError: (error: string) => {
|
||||
isFinish.value = true;
|
||||
useMessage().error(error || '流程执行失败');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 执行流程SSE聊天
|
||||
@@ -403,7 +399,7 @@ const sendFlowChatMessage = async (content: string) => {
|
||||
conversationId: flowConversationId,
|
||||
params: executeParams,
|
||||
envs: {},
|
||||
stream: true
|
||||
stream: true,
|
||||
},
|
||||
callbacks
|
||||
);
|
||||
@@ -420,12 +416,11 @@ const sendFlowChatMessage = async (content: string) => {
|
||||
// @ts-ignore - 流式完成标识
|
||||
currentMessage.stream = false;
|
||||
currentMessage.time = parseTime(new Date());
|
||||
|
||||
|
||||
// 保存到本地存储
|
||||
Local.set(conversationKey.value, JSON.stringify(messageList.value));
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
// 更新错误消息
|
||||
const aiMessageIndex = messageList.value.length - 1;
|
||||
@@ -435,11 +430,11 @@ const sendFlowChatMessage = async (content: string) => {
|
||||
// @ts-ignore - 流式完成标识
|
||||
currentMessage.stream = false;
|
||||
currentMessage.time = parseTime(new Date());
|
||||
|
||||
|
||||
// 保存到本地存储
|
||||
Local.set(conversationKey.value, JSON.stringify(messageList.value));
|
||||
}
|
||||
|
||||
|
||||
isFinish.value = true;
|
||||
useMessage().error(error.message || '流程执行失败');
|
||||
}
|
||||
@@ -622,7 +617,7 @@ const appendLastMessageContent = (result: any) => {
|
||||
|
||||
const sendOrSave = async () => {
|
||||
if (!messageContent.value.length || !isFinish.value) return;
|
||||
|
||||
|
||||
// 如果是流程编排模式,使用流程参数配置组件进行验证
|
||||
if (props.flowId && flowStartParams.value.length > 0 && flowParamsConfigRef.value) {
|
||||
const validation = flowParamsConfigRef.value.validateRequiredParams();
|
||||
@@ -631,7 +626,7 @@ const sendOrSave = async () => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await sendChatMessage();
|
||||
};
|
||||
|
||||
@@ -797,5 +792,4 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="flowId && flowStartParams.length > 0"
|
||||
class="mx-auto mb-4 bg-white border-t border-gray-200 shadow-lg flow-params-container dark:border-gray-700 dark:bg-gray-900"
|
||||
style="width: 50%;"
|
||||
<div
|
||||
v-if="flowId && flowStartParams.length > 0"
|
||||
class="mx-auto mb-4 bg-white border-t border-gray-200 shadow-lg flow-params-container dark:border-gray-700 dark:bg-gray-900"
|
||||
style="width: 50%"
|
||||
>
|
||||
<!-- 配置区头部 -->
|
||||
<div class="flex items-center justify-between px-4 py-3 border-b border-gray-100 dark:border-gray-800 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-800 dark:to-gray-800">
|
||||
<div
|
||||
class="flex items-center justify-between px-4 py-3 border-b border-gray-100 dark:border-gray-800 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-800 dark:to-gray-800"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="w-2 h-2 bg-blue-500 rounded-full"></div>
|
||||
<h3 class="text-sm font-medium text-gray-800 dark:text-gray-200">流程参数配置</h3>
|
||||
@@ -41,8 +43,12 @@
|
||||
>
|
||||
<div v-show="!isCollapsed" class="p-4 space-y-4 overflow-y-auto max-h-96">
|
||||
<!-- 对话字段选择 -->
|
||||
<div class="p-3 rounded-lg bg-gradient-to-r from-amber-50 dark:from-amber-900/20 dark:to-yellow-900/20 border-amber-200 dark:border-amber-800">
|
||||
<div class="p-3 transition-colors border rounded-lg border-amber-200 param-item bg-amber-50/50 dark:bg-amber-900/10 dark:border-amber-800 hover:border-amber-300 dark:hover:border-amber-600">
|
||||
<div
|
||||
class="p-3 rounded-lg bg-gradient-to-r from-amber-50 dark:from-amber-900/20 dark:to-yellow-900/20 border-amber-200 dark:border-amber-800"
|
||||
>
|
||||
<div
|
||||
class="p-3 transition-colors border rounded-lg border-amber-200 param-item bg-amber-50/50 dark:bg-amber-900/10 dark:border-amber-800 hover:border-amber-300 dark:hover:border-amber-600"
|
||||
>
|
||||
<label class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm font-medium text-amber-800 dark:text-amber-200">对话字段</span>
|
||||
<span class="px-1.5 py-0.5 text-xs bg-amber-100 text-amber-600 dark:bg-amber-900 dark:text-amber-300 rounded">必选</span>
|
||||
@@ -59,7 +65,11 @@
|
||||
</select>
|
||||
<div class="flex items-start mt-2 space-x-2 text-xs text-amber-700 dark:text-amber-300">
|
||||
<svg class="w-3 h-3 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>选择的字段值将自动使用底部输入框的内容</span>
|
||||
</div>
|
||||
@@ -70,16 +80,24 @@
|
||||
<div v-if="displayParams.length > 0">
|
||||
<div class="flex items-center mb-3 space-x-2">
|
||||
<svg class="w-4 h-4 text-blue-600 dark:text-blue-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M5 4a1 1 0 00-2 0v7.268a2 2 0 000 3.464V16a1 1 0 102 0v-1.268a2 2 0 000-3.464V4zM11 4a1 1 0 10-2 0v1.268a2 2 0 000 3.464V16a1 1 0 102 0V8.732a2 2 0 000-3.464V4zM15 4a1 1 0 00-2 0v7.268a2 2 0 000 3.464V16a1 1 0 102 0v-1.268a2 2 0 000-3.464V4z" />
|
||||
<path
|
||||
d="M5 4a1 1 0 00-2 0v7.268a2 2 0 000 3.464V16a1 1 0 102 0v-1.268a2 2 0 000-3.464V4zM11 4a1 1 0 10-2 0v1.268a2 2 0 000 3.464V16a1 1 0 102 0V8.732a2 2 0 000-3.464V4zM15 4a1 1 0 00-2 0v7.268a2 2 0 000 3.464V16a1 1 0 102 0v-1.268a2 2 0 000-3.464V4z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">其他参数</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">({{ displayParams.length }} 个)</span>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div v-for="(param, index) in displayParams" :key="index" class="p-3 transition-colors border border-gray-200 rounded-lg param-item bg-gray-50 dark:bg-gray-800 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600">
|
||||
<div
|
||||
v-for="(param, index) in displayParams"
|
||||
:key="index"
|
||||
class="p-3 transition-colors border border-gray-200 rounded-lg param-item bg-gray-50 dark:bg-gray-800 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600"
|
||||
>
|
||||
<label class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ param.name }}</span>
|
||||
<span v-if="param.required" class="px-1.5 py-0.5 text-xs bg-red-100 text-red-600 dark:bg-red-900 dark:text-red-300 rounded">必填</span>
|
||||
<span v-if="param.required" class="px-1.5 py-0.5 text-xs bg-red-100 text-red-600 dark:bg-red-900 dark:text-red-300 rounded"
|
||||
>必填</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
v-if="param.inputType === 'input'"
|
||||
@@ -116,9 +134,16 @@
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
<div v-if="param.type === 'prompt' || param.type === 'message'" class="flex items-center mt-2 space-x-1 text-xs text-blue-600 dark:text-blue-400">
|
||||
<div
|
||||
v-if="param.type === 'prompt' || param.type === 'message'"
|
||||
class="flex items-center mt-2 space-x-1 text-xs text-blue-600 dark:text-blue-400"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>此参数将自动使用您的消息内容</span>
|
||||
</div>
|
||||
@@ -143,19 +168,19 @@ const props = defineProps<{
|
||||
const emit = defineEmits<{
|
||||
'update:selectedChatField': [value: string];
|
||||
'update:isCollapsed': [value: boolean];
|
||||
'paramsChange': [params: FlowParam[]];
|
||||
paramsChange: [params: FlowParam[]];
|
||||
}>();
|
||||
|
||||
// 计算属性:过滤掉对话字段的参数列表
|
||||
const displayParams = computed(() => {
|
||||
return props.flowStartParams.filter(param => param.name !== props.selectedChatField);
|
||||
return props.flowStartParams.filter((param) => param.name !== props.selectedChatField);
|
||||
});
|
||||
|
||||
// 计算属性:可用作对话字段的参数选项
|
||||
const chatFieldOptions = computed((): ChatFieldOption[] => {
|
||||
return props.flowStartParams.map(param => ({
|
||||
return props.flowStartParams.map((param) => ({
|
||||
label: param.name,
|
||||
value: param.name
|
||||
value: param.name,
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -178,23 +203,24 @@ const handleParamChange = () => {
|
||||
|
||||
// 验证必填参数
|
||||
const validateRequiredParams = (): FlowParamsValidationResult => {
|
||||
const missingParams = props.flowStartParams.filter(param =>
|
||||
param.required &&
|
||||
param.type !== 'prompt' &&
|
||||
param.type !== 'message' &&
|
||||
param.name !== props.selectedChatField && // 排除对话字段
|
||||
!param.value
|
||||
const missingParams = props.flowStartParams.filter(
|
||||
(param) =>
|
||||
param.required &&
|
||||
param.type !== 'prompt' &&
|
||||
param.type !== 'message' &&
|
||||
param.name !== props.selectedChatField && // 排除对话字段
|
||||
!param.value
|
||||
);
|
||||
|
||||
|
||||
return {
|
||||
isValid: missingParams.length === 0,
|
||||
missingParams: missingParams.map(p => p.name)
|
||||
missingParams: missingParams.map((p) => p.name),
|
||||
};
|
||||
};
|
||||
|
||||
// 对外暴露方法
|
||||
defineExpose({
|
||||
validateRequiredParams
|
||||
validateRequiredParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,75 +1,74 @@
|
||||
<template>
|
||||
<el-drawer title="提示词" v-model="promptVisible" close-on-click-modal>
|
||||
<div class="max-w-xl rounded-lg h-full py-4 dark:border-gray-700">
|
||||
<div class="mx-2">
|
||||
<input
|
||||
id="search-chats"
|
||||
type="text"
|
||||
class="w-full rounded-lg border border-slate-300 dark:border-gray-700 bg-slate-50 dark:bg-gray-800 p-3 pr-10 text-sm text-slate-800 dark:text-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-600"
|
||||
placeholder="查询提示词"
|
||||
v-model="queryString"
|
||||
@keydown.enter="querySearchAsync"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<el-drawer title="提示词" v-model="promptVisible" close-on-click-modal>
|
||||
<div class="max-w-xl rounded-lg h-full py-4 dark:border-gray-700">
|
||||
<div class="mx-2">
|
||||
<input
|
||||
id="search-chats"
|
||||
type="text"
|
||||
class="w-full rounded-lg border border-slate-300 dark:border-gray-700 bg-slate-50 dark:bg-gray-800 p-3 pr-10 text-sm text-slate-800 dark:text-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-600"
|
||||
placeholder="查询提示词"
|
||||
v-model="queryString"
|
||||
@keydown.enter="querySearchAsync"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ul role="list" class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-2">
|
||||
<li class="col-span-2 rounded-lg mt-2 shadow dark:bg-gray-800" v-for="prompt in promptList">
|
||||
<div class="flex w-full items-center justify-between space-x-6 p-6">
|
||||
<button class="group flex-1 truncate" @click="selectPrompt(prompt)">
|
||||
<div class="flex items-center space-x-3">
|
||||
<h3 class="truncate text-sm font-medium text-slate-900 dark:text-gray-200 transition-colors group-hover:text-blue-600">
|
||||
{{ prompt.act }}
|
||||
</h3>
|
||||
</div>
|
||||
<p class="mt-1 truncate text-sm text-slate-500 dark:text-gray-400">
|
||||
{{ prompt.prompt }}
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-drawer>
|
||||
<ul role="list" class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-2">
|
||||
<li class="col-span-2 rounded-lg mt-2 shadow dark:bg-gray-800" v-for="prompt in promptList">
|
||||
<div class="flex w-full items-center justify-between space-x-6 p-6">
|
||||
<button class="group flex-1 truncate" @click="selectPrompt(prompt)">
|
||||
<div class="flex items-center space-x-3">
|
||||
<h3 class="truncate text-sm font-medium text-slate-900 dark:text-gray-200 transition-colors group-hover:text-blue-600">
|
||||
{{ prompt.act }}
|
||||
</h3>
|
||||
</div>
|
||||
<p class="mt-1 truncate text-sm text-slate-500 dark:text-gray-400">
|
||||
{{ prompt.prompt }}
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script setup lang="ts" name="AiPromptsDialog">
|
||||
import {list} from "/@/api/knowledge/aiPrompt";
|
||||
import { list } from '/@/api/knowledge/aiPrompt';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
const promptVisible = ref(false)
|
||||
const promptList = ref([])
|
||||
const queryString = ref('')
|
||||
|
||||
const promptVisible = ref(false);
|
||||
const promptList = ref([]);
|
||||
const queryString = ref('');
|
||||
|
||||
/**
|
||||
* 选中提示词
|
||||
* @param prompt
|
||||
*/
|
||||
const selectPrompt = (prompt: any) => {
|
||||
// Close the prompt dialog
|
||||
promptVisible.value = false
|
||||
// Emit a 'refresh' event with the selected prompt
|
||||
emit('refresh', prompt.prompt);
|
||||
}
|
||||
// Close the prompt dialog
|
||||
promptVisible.value = false;
|
||||
// Emit a 'refresh' event with the selected prompt
|
||||
emit('refresh', prompt.prompt);
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询所有提示词.
|
||||
*/
|
||||
const querySearchAsync = async () => {
|
||||
const {data} = await list({act: queryString.value})
|
||||
promptList.value = data
|
||||
}
|
||||
const { data } = await list({ act: queryString.value });
|
||||
promptList.value = data;
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开提示词选择界面.
|
||||
*/
|
||||
const openDialog = () => {
|
||||
querySearchAsync()
|
||||
promptVisible.value = true
|
||||
}
|
||||
querySearchAsync();
|
||||
promptVisible.value = true;
|
||||
};
|
||||
|
||||
// Expose the openDialog function
|
||||
defineExpose({
|
||||
openDialog
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -80,7 +80,7 @@ const handleRowDblClick = (row: any) => {
|
||||
selectedDataId.value = row.dataId;
|
||||
selectedDatasetName.value = row.datasetName;
|
||||
// Emit a 'refresh' event with the selected data
|
||||
emit('refresh', row.dataId );
|
||||
emit('refresh', row.dataId);
|
||||
useMessage().success('已选择数据集: ' + row.datasetName);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {genTts} from "/@/api/knowledge/aiGen";
|
||||
import {ref} from 'vue';
|
||||
import {useMessage} from '/@/hooks/message';
|
||||
import { genTts } from '/@/api/knowledge/aiGen';
|
||||
import { ref } from 'vue';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
|
||||
// 接收传入的文本属性
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 定义状态变量
|
||||
@@ -25,92 +25,102 @@ const isLocked = ref(false); // 按钮锁控制,防止重复点击
|
||||
|
||||
// 切换播放/暂停的主函数
|
||||
const togglePlayPause = () => {
|
||||
if (isLocked.value) return; // 如果按钮已锁定,直接返回
|
||||
isLocked.value = true; // 锁定按钮,防止重复点击
|
||||
if (isLocked.value) return; // 如果按钮已锁定,直接返回
|
||||
isLocked.value = true; // 锁定按钮,防止重复点击
|
||||
|
||||
if (isPlaying.value) {
|
||||
pauseAudio(); // 如果正在播放,则暂停
|
||||
} else {
|
||||
playAudio(props.text); // 如果未播放,则生成或播放音频
|
||||
}
|
||||
if (isPlaying.value) {
|
||||
pauseAudio(); // 如果正在播放,则暂停
|
||||
} else {
|
||||
playAudio(props.text); // 如果未播放,则生成或播放音频
|
||||
}
|
||||
};
|
||||
|
||||
// 播放音频的函数
|
||||
const playAudio = (text) => {
|
||||
if (audioSrc.value) {
|
||||
resumeAudio(); // 如果音频已生成,则直接恢复播放
|
||||
} else {
|
||||
genTts(text).then((res) => {
|
||||
const audioBlob = base64ToBlob(res.data, 'audio/wav');
|
||||
const audioUrl = URL.createObjectURL(audioBlob);
|
||||
audioSrc.value = audioUrl; // 存储生成的音频 URL
|
||||
handleAudioPlayPause(audioElement, audioUrl); // 处理音频的播放和暂停
|
||||
}).catch((error) => {
|
||||
if (typeof error === 'object' && error !== null && 'msg' in error) {
|
||||
useMessage().error(error.msg);
|
||||
} else {
|
||||
useMessage().error('An error occurred');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
isLocked.value = false; // 操作完成后释放锁
|
||||
});
|
||||
}
|
||||
if (audioSrc.value) {
|
||||
resumeAudio(); // 如果音频已生成,则直接恢复播放
|
||||
} else {
|
||||
genTts(text)
|
||||
.then((res) => {
|
||||
const audioBlob = base64ToBlob(res.data, 'audio/wav');
|
||||
const audioUrl = URL.createObjectURL(audioBlob);
|
||||
audioSrc.value = audioUrl; // 存储生成的音频 URL
|
||||
handleAudioPlayPause(audioElement, audioUrl); // 处理音频的播放和暂停
|
||||
})
|
||||
.catch((error) => {
|
||||
if (typeof error === 'object' && error !== null && 'msg' in error) {
|
||||
useMessage().error(error.msg);
|
||||
} else {
|
||||
useMessage().error('An error occurred');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
isLocked.value = false; // 操作完成后释放锁
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 恢复播放音频的函数
|
||||
const resumeAudio = () => {
|
||||
if (audioElement.value && audioElement.value.paused) {
|
||||
audioElement.value.play().then(() => {
|
||||
isPlaying.value = true; // 更新状态为播放中
|
||||
}).catch(error => {
|
||||
console.error("恢复播放音频时出错:", error); // 捕获并打印错误
|
||||
}).finally(() => {
|
||||
isLocked.value = false; // 操作完成后释放锁
|
||||
});
|
||||
}
|
||||
if (audioElement.value && audioElement.value.paused) {
|
||||
audioElement.value
|
||||
.play()
|
||||
.then(() => {
|
||||
isPlaying.value = true; // 更新状态为播放中
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('恢复播放音频时出错:', error); // 捕获并打印错误
|
||||
})
|
||||
.finally(() => {
|
||||
isLocked.value = false; // 操作完成后释放锁
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 暂停音频的函数
|
||||
const pauseAudio = () => {
|
||||
if (audioElement.value && !audioElement.value.paused) {
|
||||
audioElement.value.pause(); // 暂停音频
|
||||
isPlaying.value = false; // 更新状态为暂停
|
||||
isLocked.value = false; // 释放锁
|
||||
}
|
||||
if (audioElement.value && !audioElement.value.paused) {
|
||||
audioElement.value.pause(); // 暂停音频
|
||||
isPlaying.value = false; // 更新状态为暂停
|
||||
isLocked.value = false; // 释放锁
|
||||
}
|
||||
};
|
||||
|
||||
// 处理音频播放和暂停的核心函数
|
||||
const handleAudioPlayPause = (audioRef, src) => {
|
||||
if (audioRef.value) {
|
||||
if (!audioRef.value.paused && audioRef.value.src === src) {
|
||||
audioRef.value.pause(); // 如果正在播放且 URL 相同,则暂停
|
||||
isPlaying.value = false; // 更新状态为暂停
|
||||
isLocked.value = false; // 释放锁
|
||||
} else {
|
||||
audioRef.value.src = src; // 设置音频 URL
|
||||
audioRef.value.load(); // 重新加载音频
|
||||
audioRef.value.onloadedmetadata = () => {
|
||||
audioRef.value.play().then(() => {
|
||||
isPlaying.value = true; // 更新状态为播放中
|
||||
}).catch(error => {
|
||||
console.error("播放音频时出错:", error); // 捕获并打印错误
|
||||
}).finally(() => {
|
||||
isLocked.value = false; // 操作完成后释放锁
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
if (audioRef.value) {
|
||||
if (!audioRef.value.paused && audioRef.value.src === src) {
|
||||
audioRef.value.pause(); // 如果正在播放且 URL 相同,则暂停
|
||||
isPlaying.value = false; // 更新状态为暂停
|
||||
isLocked.value = false; // 释放锁
|
||||
} else {
|
||||
audioRef.value.src = src; // 设置音频 URL
|
||||
audioRef.value.load(); // 重新加载音频
|
||||
audioRef.value.onloadedmetadata = () => {
|
||||
audioRef.value
|
||||
.play()
|
||||
.then(() => {
|
||||
isPlaying.value = true; // 更新状态为播放中
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('播放音频时出错:', error); // 捕获并打印错误
|
||||
})
|
||||
.finally(() => {
|
||||
isLocked.value = false; // 操作完成后释放锁
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 将 base64 编码转换为 Blob 对象的辅助函数
|
||||
function base64ToBlob(base64, fileType) {
|
||||
const byteCharacters = atob(base64); // 解码 base64 字符串
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i); // 将字符转换为字节
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers); // 创建字节数组
|
||||
return new Blob([byteArray], {type: fileType}); // 创建并返回 Blob 对象
|
||||
const byteCharacters = atob(base64); // 解码 base64 字符串
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i); // 将字符转换为字节
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers); // 创建字节数组
|
||||
return new Blob([byteArray], { type: fileType }); // 创建并返回 Blob 对象
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,62 +1,65 @@
|
||||
<template>
|
||||
<el-dialog v-model="audioVisible" destroy-on-close>
|
||||
<tapir-widget :time="0.2" :backendEndpoint="backendEndpoint"
|
||||
title="音频交互"
|
||||
instructionMessageStart="点击开始录音"
|
||||
instructionMessageStop="点击停止录音"
|
||||
listenInstructions="播放您的录音"
|
||||
successMessageRecorded="录音成功"
|
||||
successMessageSubmitted="上传成功"
|
||||
:successfulUpload="successfulUpload"
|
||||
submitLabel="点击识别"
|
||||
buttonColor="border border-primary"/>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="audioVisible" destroy-on-close>
|
||||
<tapir-widget
|
||||
:time="0.2"
|
||||
:backendEndpoint="backendEndpoint"
|
||||
title="音频交互"
|
||||
instructionMessageStart="点击开始录音"
|
||||
instructionMessageStop="点击停止录音"
|
||||
listenInstructions="播放您的录音"
|
||||
successMessageRecorded="录音成功"
|
||||
successMessageSubmitted="上传成功"
|
||||
:successfulUpload="successfulUpload"
|
||||
submitLabel="点击识别"
|
||||
buttonColor="border border-primary"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts" name="AiPromptsDialog">
|
||||
// @ts-ignore
|
||||
import TapirWidget from 'vue-audio-record';
|
||||
import 'vue-audio-record/dist/vue-audio-tapir.css';
|
||||
import {Session} from "/@/utils/storage";
|
||||
import { Session } from '/@/utils/storage';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
const audioVisible = ref(false)
|
||||
const audioVisible = ref(false);
|
||||
|
||||
/**
|
||||
* 从会话存储中获取访问令牌
|
||||
* @returns {string} 访问令牌
|
||||
*/
|
||||
const token = computed(() => {
|
||||
return Session.getToken();
|
||||
return Session.getToken();
|
||||
});
|
||||
/**
|
||||
* 从会话存储中获取访问租户
|
||||
* @returns {string} 租户
|
||||
*/
|
||||
const tenant = computed(() => {
|
||||
return Session.getTenant();
|
||||
return Session.getTenant();
|
||||
});
|
||||
|
||||
const backendEndpoint = computed(() => {
|
||||
return `${import.meta.env.VITE_API_URL}${import.meta.env.VITE_IS_MICRO == 'false' ? '/admin' : '/knowledge'}/chat/audio?access_token=${token.value}&TENANT-ID=${tenant.value}`
|
||||
})
|
||||
|
||||
return `${import.meta.env.VITE_API_URL}${import.meta.env.VITE_IS_MICRO == 'false' ? '/admin' : '/knowledge'}/chat/audio?access_token=${
|
||||
token.value
|
||||
}&TENANT-ID=${tenant.value}`;
|
||||
});
|
||||
|
||||
const successfulUpload = async (response: any) => {
|
||||
audioVisible.value = false
|
||||
const {data} = await response.json()
|
||||
emit('refresh', JSON.parse(data).text);
|
||||
}
|
||||
|
||||
audioVisible.value = false;
|
||||
const { data } = await response.json();
|
||||
emit('refresh', JSON.parse(data).text);
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开提示词选择界面.
|
||||
*/
|
||||
const openDialog = () => {
|
||||
audioVisible.value = true
|
||||
}
|
||||
audioVisible.value = true;
|
||||
};
|
||||
|
||||
// Expose the openDialog function
|
||||
defineExpose({
|
||||
openDialog
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user