diff --git a/.image/common/ai-feature.png b/.image/common/ai-feature.png index 552ed59b..7f8c92f8 100644 Binary files a/.image/common/ai-feature.png and b/.image/common/ai-feature.png differ diff --git a/src/api/ai/workflow/index.ts b/src/api/ai/workflow/index.ts index 985ae905..5245911e 100644 --- a/src/api/ai/workflow/index.ts +++ b/src/api/ai/workflow/index.ts @@ -20,6 +20,6 @@ export const deleteWorkflow = async (id) => { return await request.delete({ url: '/ai/workflow/delete?id=' + id }) } -export const updateWorkflowModel = async (data) => { - return await request.put({ url: '/ai/workflow/updateWorkflowModel', data }) +export const testWorkflow = async (data) => { + return await request.post({ url: '/ai/workflow/test', data }) } diff --git a/src/api/system/tenant/index.ts b/src/api/system/tenant/index.ts index 176c3757..12c32528 100644 --- a/src/api/system/tenant/index.ts +++ b/src/api/system/tenant/index.ts @@ -41,6 +41,11 @@ export const getTenant = (id: number) => { return request.get({ url: '/system/tenant/get?id=' + id }) } +// 获取租户精简信息列表 +export const getTenantList = () => { + return request.get({ url: '/system/tenant/simple-list' }) +} + // 新增租户 export const createTenant = (data: TenantVO) => { return request.post({ url: '/system/tenant/create', data }) diff --git a/src/components/Dialog/src/Dialog.vue b/src/components/Dialog/src/Dialog.vue index 9bf5b7ab..f8dd151f 100644 --- a/src/components/Dialog/src/Dialog.vue +++ b/src/components/Dialog/src/Dialog.vue @@ -91,7 +91,7 @@ const dialogStyle = computed(() => { icon="ep:close" hover-color="var(--el-color-primary)" color="var(--el-color-info)" - @click="close" + @click.stop="close" /> diff --git a/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue b/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue index 3bb7d660..2359aff6 100644 --- a/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue +++ b/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue @@ -237,7 +237,7 @@ const props = defineProps({ const prefix = inject('prefix') const width = inject('width') -const formKey = ref('') +const formKey = ref(undefined) const businessKey = ref('') const optionModelTitle = ref('') const fieldList = ref([]) @@ -462,6 +462,7 @@ const updateElementExtensions = () => { const formList = ref([]) // 流程表单的下拉框的数据 onMounted(async () => { formList.value = await FormApi.getFormSimpleList() + formKey.value = parseInt(formKey.value) }) watch( diff --git a/src/config/axios/service.ts b/src/config/axios/service.ts index 7c31e0b2..74280a92 100644 --- a/src/config/axios/service.ts +++ b/src/config/axios/service.ts @@ -3,7 +3,14 @@ import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestCo import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' import qs from 'qs' import { config } from '@/config/axios/config' -import { getAccessToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth' +import { + getAccessToken, + getRefreshToken, + getTenantId, + getVisitTenantId, + removeToken, + setToken +} from '@/utils/auth' import errorCode from './errorCode' import { resetRouter } from '@/router' @@ -24,7 +31,7 @@ export const isRelogin = { show: false } let requestList: any[] = [] // 是否正在刷新中 let isRefreshToken = false -// 请求白名单,无须token的接口 +// 请求白名单,无须 token 的接口 const whiteList: string[] = ['/login', '/refresh-token'] // 创建axios实例 @@ -55,6 +62,11 @@ service.interceptors.request.use( if (tenantEnable && tenantEnable === 'true') { const tenantId = getTenantId() if (tenantId) config.headers['tenant-id'] = tenantId + // 只有登录时,才设置 visit-tenant-id 访问租户 + const visitTenantId = getVisitTenantId() + if (config.headers.Authorization && visitTenantId) { + config.headers['visit-tenant-id'] = visitTenantId + } } const method = config.method?.toUpperCase() // 防止 GET 请求缓存 diff --git a/src/hooks/web/useCache.ts b/src/hooks/web/useCache.ts index 4f39f307..1acb03bc 100644 --- a/src/hooks/web/useCache.ts +++ b/src/hooks/web/useCache.ts @@ -10,6 +10,7 @@ export const CACHE_KEY = { // 用户相关 ROLE_ROUTERS: 'roleRouters', USER: 'user', + VisitTenantId: 'visitTenantId', // 系统设置 IS_DARK: 'isDark', LANG: 'lang', @@ -35,5 +36,6 @@ export const deleteUserCache = () => { const { wsCache } = useCache() wsCache.delete(CACHE_KEY.USER) wsCache.delete(CACHE_KEY.ROLE_ROUTERS) + wsCache.delete(CACHE_KEY.VisitTenantId) // 注意,不要清理 LoginForm 登录表单 } diff --git a/src/layout/components/TenantVisit/index.vue b/src/layout/components/TenantVisit/index.vue new file mode 100644 index 00000000..81e04a97 --- /dev/null +++ b/src/layout/components/TenantVisit/index.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/layout/components/ToolHeader.vue b/src/layout/components/ToolHeader.vue index 0b8d00d5..276eed1b 100644 --- a/src/layout/components/ToolHeader.vue +++ b/src/layout/components/ToolHeader.vue @@ -8,8 +8,10 @@ import { Breadcrumb } from '@/layout/components/Breadcrumb' import { SizeDropdown } from '@/layout/components/SizeDropdown' import { LocaleDropdown } from '@/layout/components/LocaleDropdown' import RouterSearch from '@/components/RouterSearch/index.vue' +import TenantVisit from '@/layout/components/TenantVisit/index.vue' import { useAppStore } from '@/store/modules/app' import { useDesign } from '@/hooks/web/useDesign' +import { checkPermi } from '@/utils/permission' const { getPrefixCls, variables } = useDesign() @@ -41,6 +43,11 @@ const locale = computed(() => appStore.getLocale) // 消息图标 const message = computed(() => appStore.getMessage) +// 租户切换权限 +const hasTenantVisitPermission = computed( + () => import.meta.env.VITE_APP_TENANT_ENABLE === 'true' && checkPermi(['system:tenant:visit']) +) + export default defineComponent({ name: 'ToolHeader', setup() { @@ -62,6 +69,7 @@ export default defineComponent({ ) : undefined}
+ {hasTenantVisitPermission.value ? : undefined} {screenfull.value ? ( ) : undefined} diff --git a/src/utils/auth.ts b/src/utils/auth.ts index d9e85650..ad67440d 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -67,6 +67,14 @@ export const getTenantId = () => { return wsCache.get(CACHE_KEY.TenantId) } -export const setTenantId = (username: string) => { - wsCache.set(CACHE_KEY.TenantId, username) +export const setTenantId = (tenantId: number) => { + wsCache.set(CACHE_KEY.TenantId, tenantId) +} + +export const getVisitTenantId = () => { + return wsCache.get(CACHE_KEY.VisitTenantId) +} + +export const setVisitTenantId = (visitTenantId: number) => { + wsCache.set(CACHE_KEY.VisitTenantId, visitTenantId) } diff --git a/src/views/ai/chat/index/index.vue b/src/views/ai/chat/index/index.vue index 28f1d65f..ec2b552a 100644 --- a/src/views/ai/chat/index/index.vue +++ b/src/views/ai/chat/index/index.vue @@ -462,6 +462,8 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => { (error) => { message.alert(`对话异常! ${error}`) stopStream() + // 需要抛出异常,禁止重试 + throw error }, () => { stopStream() diff --git a/src/views/ai/mindmap/index/index.vue b/src/views/ai/mindmap/index/index.vue index b85f20ba..72f05538 100644 --- a/src/views/ai/mindmap/index/index.vue +++ b/src/views/ai/mindmap/index/index.vue @@ -80,6 +80,8 @@ const submit = (data: AiMindMapGenerateReqVO) => { onError(err) { console.error('生成思维导图失败', err) stopStream() + // 需要抛出异常,禁止重试 + throw error }, ctrl: ctrl.value }) diff --git a/src/views/ai/model/model/ModelForm.vue b/src/views/ai/model/model/ModelForm.vue index a27ca0fd..32b2c94b 100644 --- a/src/views/ai/model/model/ModelForm.vue +++ b/src/views/ai/model/model/ModelForm.vue @@ -4,7 +4,7 @@ ref="formRef" :model="formData" :rules="formRules" - label-width="120px" + label-width="130px" v-loading="formLoading" > @@ -146,7 +146,10 @@ const formRules = reactive({ platform: [{ required: true, message: '所属平台不能为空', trigger: 'blur' }], type: [{ required: true, message: '模型类型不能为空', trigger: 'blur' }], sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }], - status: [{ required: true, message: '状态不能为空', trigger: 'blur' }] + status: [{ required: true, message: '状态不能为空', trigger: 'blur' }], + temperature: [{ required: true, message: '温度参数不能为空', trigger: 'blur' }], + maxTokens: [{ required: true, message: '回复数 Token 数不能为空', trigger: 'blur' }], + maxContexts: [{ required: true, message: '上下文数量不能为空', trigger: 'blur' }] }) const formRef = ref() // 表单 Ref const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表 diff --git a/src/views/ai/workflow/form/WorkflowDesign.vue b/src/views/ai/workflow/form/WorkflowDesign.vue index 36973196..1346f9c1 100644 --- a/src/views/ai/workflow/form/WorkflowDesign.vue +++ b/src/views/ai/workflow/form/WorkflowDesign.vue @@ -13,11 +13,58 @@ 测试
+ + + +
+

运行参数配置

+
+
+ + + + + +
+ + 添加参数 +
+
+
+

运行结果

+
+
执行中...
+
+ {{ error }} +
+
{{ JSON.stringify(testResult, null, 2) }}
+          
+
点击运行查看结果
+
+
+ + 运行流程 + +
+ + diff --git a/src/views/ai/workflow/form/index.vue b/src/views/ai/workflow/form/index.vue index ee2bd567..dddb7a53 100644 --- a/src/views/ai/workflow/form/index.vue +++ b/src/views/ai/workflow/form/index.vue @@ -59,7 +59,7 @@ @@ -73,7 +73,8 @@ import { CommonStatusEnum } from '@/utils/constants' import * as WorkflowApi from '@/api/ai/workflow' import BasicInfo from './BasicInfo.vue' import WorkflowDesign from './WorkflowDesign.vue' -import { ApiKeyApi } from '@/api/ai/model/apiKey' +import { ModelApi } from '@/api/ai/model/model' +import { AiModelTypeEnum } from '@/views/ai/utils/constants' const router = useRouter() const { delView } = useTagsViewStore() @@ -104,31 +105,35 @@ const formData: any = ref({ graph: '', status: CommonStatusEnum.ENABLE }) -// TODO @lesan:待接入 -const provider = ref() +const llmProvider = ref([]) const workflowData = ref({}) provide('workflowData', workflowData) /** 初始化数据 */ const actionType = route.params.type as string const initData = async () => { + // 编辑情况下,需要加载工作流配置 if (actionType === 'update') { const workflowId = route.params.id as string formData.value = await WorkflowApi.getWorkflow(workflowId) workflowData.value = JSON.parse(formData.value.graph) } - const apiKeys = await ApiKeyApi.getApiKeySimpleList() - provider.value = { + // 加载模型列表 + const models = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT) + llmProvider.value = { llm: () => - apiKeys.map(({ id, name }) => ({ + models.map(({ id, name }) => ({ value: id, label: name })), knowledge: () => [], internal: () => [] } + // TODO @lesan:知识库(可以看下 knowledge) + // TODO @lesan:搜索引擎(这个之前有个 pr 搞了,,,可能来接下) + // 设置当前步骤 currentStep.value = 0 } @@ -164,17 +169,17 @@ const handleSave = async () => { // 更新表单数据 const data = { - ...formData.value + ...formData.value, + graph: JSON.stringify(workflowData.value) } - - data.graph = JSON.stringify(workflowData.value) - if (actionType === 'update') { await WorkflowApi.updateWorkflow(data) } else { await WorkflowApi.createWorkflow(data) } + // 保存成功,提示并跳转到列表页 + message.success('保存成功') delView(unref(router.currentRoute)) await router.push({ name: 'AiWorkflow' }) } catch (error: any) { diff --git a/src/views/ai/write/index/index.vue b/src/views/ai/write/index/index.vue index 0dfda742..0079eed2 100644 --- a/src/views/ai/write/index/index.vue +++ b/src/views/ai/write/index/index.vue @@ -57,9 +57,11 @@ const submit = (data: WriteVO) => { }, ctrl: abortController.value, onClose: stopStream, - onError: (...err) => { - console.error('写作异常', ...err) + onError: (error) => { + console.error('写作异常', error) stopStream() + // 需要抛出异常,禁止重试 + throw error } }) } diff --git a/src/views/mp/material/components/upload.ts b/src/views/mp/material/components/upload.ts index e732fe70..724d5459 100644 --- a/src/views/mp/material/components/upload.ts +++ b/src/views/mp/material/components/upload.ts @@ -1,8 +1,8 @@ import type { UploadProps, UploadRawFile } from 'element-plus' -import { getAccessToken } from '@/utils/auth' +import { getRefreshToken } from '@/utils/auth' import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' -const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 请求头 +const HEADERS = { Authorization: 'Bearer ' + getRefreshToken() } // 请求头(解决 el-upload 上传过程中,无法刷新令牌的问题) const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传地址 interface UploadData { diff --git a/src/views/pay/app/components/channel/WeixinChannelForm.vue b/src/views/pay/app/components/channel/WeixinChannelForm.vue index 0b4a1d38..daebb41e 100644 --- a/src/views/pay/app/components/channel/WeixinChannelForm.vue +++ b/src/views/pay/app/components/channel/WeixinChannelForm.vue @@ -71,11 +71,12 @@ > @@ -108,11 +109,12 @@ > @@ -145,6 +147,47 @@ 前往微信商户平台查看证书序列号 + + + + + + + + 点击上传 + + + + + + + + + 微信支付公钥产品简介及使用说明 + + @@ -184,7 +227,9 @@ const formData = ref({ keyContent: '', privateKeyContent: '', certSerialNo: '', - apiV3Key: '' + apiV3Key: '', + publicKeyContent: '', + publicKeyId: '' } }) const formRules = { @@ -201,6 +246,8 @@ const formRules = { { required: true, message: '请上传 apiclient_key.pem 证书', trigger: 'blur' } ], 'config.certSerialNo': [{ required: true, message: '请输入证书序列号', trigger: 'blur' }], + 'config.publicKeyContent': [{ required: true, message: '请上传 public_key.pem 证书', trigger: 'blur' }], + 'config.publicKeyId': [{ required: true, message: '请输入公钥 ID', trigger: 'blur' }], 'config.apiV3Key': [{ required: true, message: '请上传 api V3 密钥值', trigger: 'blur' }] } const formRef = ref() // 表单 Ref @@ -267,7 +314,9 @@ const resetForm = (appId, code) => { keyContent: '', privateKeyContent: '', certSerialNo: '', - apiV3Key: '' + apiV3Key: '', + publicKeyContent: '', + publicKeyId: '' } } formRef.value?.resetFields() @@ -318,4 +367,15 @@ const keyContentUpload = async (event) => { } readFile.readAsDataURL(event.file) // 读成 base64 } + +/** + * 读取 public_key.pem 到 publicKeyContent 字段 + */ +const publicKeyContentUpload = async (event) => { + const readFile = new FileReader() + readFile.onload = (e: any) => { + formData.value.config.publicKeyContent = e.target.result + } + readFile.readAsText(event.file) +} diff --git a/src/views/report/goview/index.vue b/src/views/report/goview/index.vue index 78cac8b1..1ec8c29b 100644 --- a/src/views/report/goview/index.vue +++ b/src/views/report/goview/index.vue @@ -6,7 +6,11 @@ diff --git a/src/views/report/jmreport/bi.vue b/src/views/report/jmreport/bi.vue new file mode 100644 index 00000000..c773b4a3 --- /dev/null +++ b/src/views/report/jmreport/bi.vue @@ -0,0 +1,15 @@ +