feat: 访问限制中新增身份验证设置
This commit is contained in:
parent
aa8e68a688
commit
7a0f15b4e4
@ -312,6 +312,20 @@ class ApplicationSerializer(serializers.Serializer):
|
|||||||
if 'show_source' in instance and instance.get('show_source') is not None:
|
if 'show_source' in instance and instance.get('show_source') is not None:
|
||||||
application_access_token.show_source = instance.get('show_source')
|
application_access_token.show_source = instance.get('show_source')
|
||||||
application_access_token.save()
|
application_access_token.save()
|
||||||
|
application_setting_model = DBModelManage.get_model('application_setting')
|
||||||
|
X_PACK_LICENSE_IS_VALID = (settings.XPACK_LICENSE_IS_VALID if hasattr(settings,
|
||||||
|
'XPACK_LICENSE_IS_VALID') else False)
|
||||||
|
if application_setting_model is not None and X_PACK_LICENSE_IS_VALID:
|
||||||
|
application_setting, _ = application_setting_model.objects.get_or_create(
|
||||||
|
application_id=self.data.get('application_id'))
|
||||||
|
if application_setting is not None:
|
||||||
|
application_setting.authentication = instance.get('authentication')
|
||||||
|
application_setting.authentication_value = {
|
||||||
|
"type": "password",
|
||||||
|
"value": instance.get('authentication_value')
|
||||||
|
}
|
||||||
|
application_setting.save()
|
||||||
|
|
||||||
get_application_access_token(application_access_token.access_token, False)
|
get_application_access_token(application_access_token.access_token, False)
|
||||||
return self.one(with_valid=False)
|
return self.one(with_valid=False)
|
||||||
|
|
||||||
@ -734,7 +748,8 @@ class ApplicationSerializer(serializers.Serializer):
|
|||||||
'draggable': application_setting.draggable,
|
'draggable': application_setting.draggable,
|
||||||
'show_guide': application_setting.show_guide,
|
'show_guide': application_setting.show_guide,
|
||||||
'avatar': application_setting.avatar,
|
'avatar': application_setting.avatar,
|
||||||
'float_icon': application_setting.float_icon}
|
'float_icon': application_setting.float_icon,
|
||||||
|
'authentication': application_setting.authentication}
|
||||||
return ApplicationSerializer.Query.reset_application(
|
return ApplicationSerializer.Query.reset_application(
|
||||||
{**ApplicationSerializer.ApplicationModel(application).data,
|
{**ApplicationSerializer.ApplicationModel(application).data,
|
||||||
'stt_model_id': application.stt_model_id,
|
'stt_model_id': application.stt_model_id,
|
||||||
|
|||||||
@ -387,6 +387,15 @@ const updatePlatformStatus: (application_id: string, data: any) => Promise<Resul
|
|||||||
) => {
|
) => {
|
||||||
return post(`/platform/${application_id}/status`, data)
|
return post(`/platform/${application_id}/status`, data)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 验证密码
|
||||||
|
*/
|
||||||
|
const validatePassword: (application_id: string, password: string) => Promise<Result<any>> = (
|
||||||
|
application_id,
|
||||||
|
password
|
||||||
|
) => {
|
||||||
|
return get(`/application/${application_id}/auth/${password}`, undefined)
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getAllAppilcation,
|
getAllAppilcation,
|
||||||
@ -419,5 +428,6 @@ export default {
|
|||||||
getPlatformStatus,
|
getPlatformStatus,
|
||||||
getPlatformConfig,
|
getPlatformConfig,
|
||||||
updatePlatformConfig,
|
updatePlatformConfig,
|
||||||
updatePlatformStatus
|
updatePlatformStatus,
|
||||||
|
validatePassword
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,6 +65,8 @@ export default {
|
|||||||
dialogTitle: 'Access Restrictions',
|
dialogTitle: 'Access Restrictions',
|
||||||
showSourceLabel: 'Show Source',
|
showSourceLabel: 'Show Source',
|
||||||
clientQueryLimitLabel: 'Each Client Query Limit',
|
clientQueryLimitLabel: 'Each Client Query Limit',
|
||||||
|
authentication: 'Authentication',
|
||||||
|
authenticationValue: 'Authentication Password',
|
||||||
timesDays: 'Times/Day',
|
timesDays: 'Times/Day',
|
||||||
whitelistLabel: 'Whitelist',
|
whitelistLabel: 'Whitelist',
|
||||||
whitelistPlaceholder:
|
whitelistPlaceholder:
|
||||||
|
|||||||
@ -65,6 +65,8 @@ export default {
|
|||||||
showSourceLabel: '显示知识来源',
|
showSourceLabel: '显示知识来源',
|
||||||
clientQueryLimitLabel: '每个客户端提问限制',
|
clientQueryLimitLabel: '每个客户端提问限制',
|
||||||
timesDays: '次/天',
|
timesDays: '次/天',
|
||||||
|
authentication: '身份验证',
|
||||||
|
authenticationValue: '验证密码',
|
||||||
whitelistLabel: '白名单',
|
whitelistLabel: '白名单',
|
||||||
whitelistPlaceholder:
|
whitelistPlaceholder:
|
||||||
'请输入允许嵌入第三方的源地址,一行一个,如:\nhttp://127.0.0.1:5678\nhttps://dataease.io',
|
'请输入允许嵌入第三方的源地址,一行一个,如:\nhttp://127.0.0.1:5678\nhttps://dataease.io',
|
||||||
|
|||||||
@ -119,6 +119,18 @@ const useApplicationStore = defineStore({
|
|||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
async validatePassword(id: string, password: string, loading?: Ref<boolean>) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
applicationApi
|
||||||
|
.validatePassword(id, password)
|
||||||
|
.then((data) => {
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -27,6 +27,30 @@
|
|||||||
$t('views.applicationOverview.appInfo.LimitDialog.timesDays')
|
$t('views.applicationOverview.appInfo.LimitDialog.timesDays')
|
||||||
}}</span>
|
}}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<!-- 身份验证 -->
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationOverview.appInfo.LimitDialog.authentication')"
|
||||||
|
v-hasPermission="new ComplexPermission([], ['x-pack'], 'OR')"
|
||||||
|
>
|
||||||
|
<el-switch size="small" v-model="form.authentication"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="form.authentication"
|
||||||
|
:label="$t('views.applicationOverview.appInfo.LimitDialog.authenticationValue')"
|
||||||
|
v-hasPermission="new ComplexPermission([], ['x-pack'], 'OR')"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="form.authentication_value"
|
||||||
|
readonly
|
||||||
|
style="width: 300px; margin-right: 10px"
|
||||||
|
></el-input>
|
||||||
|
<el-button type="primary" text @click="copyClick(form.authentication_value)">
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="refreshAuthentication" type="primary" text style="margin-left: 1px">
|
||||||
|
<el-icon><RefreshRight /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
:label="$t('views.applicationOverview.appInfo.LimitDialog.whitelistLabel')"
|
:label="$t('views.applicationOverview.appInfo.LimitDialog.whitelistLabel')"
|
||||||
@click.prevent
|
@click.prevent
|
||||||
@ -61,6 +85,8 @@ import type { FormInstance, FormRules } from 'element-plus'
|
|||||||
import applicationApi from '@/api/application'
|
import applicationApi from '@/api/application'
|
||||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
|
import { copyClick } from '@/utils/clipboard'
|
||||||
|
import { ComplexPermission } from '@/utils/permission/type'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const {
|
const {
|
||||||
@ -73,7 +99,9 @@ const limitFormRef = ref()
|
|||||||
const form = ref<any>({
|
const form = ref<any>({
|
||||||
access_num: 0,
|
access_num: 0,
|
||||||
white_active: true,
|
white_active: true,
|
||||||
white_list: ''
|
white_list: '',
|
||||||
|
authentication_value: '',
|
||||||
|
authentication: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const dialogVisible = ref<boolean>(false)
|
const dialogVisible = ref<boolean>(false)
|
||||||
@ -93,6 +121,8 @@ const open = (data: any) => {
|
|||||||
form.value.access_num = data.access_num
|
form.value.access_num = data.access_num
|
||||||
form.value.white_active = data.white_active
|
form.value.white_active = data.white_active
|
||||||
form.value.white_list = data.white_list?.length ? data.white_list?.join('\n') : ''
|
form.value.white_list = data.white_list?.length ? data.white_list?.join('\n') : ''
|
||||||
|
form.value.authentication_value = data.authentication_value
|
||||||
|
form.value.authentication = data.authentication
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +133,9 @@ const submit = async (formEl: FormInstance | undefined) => {
|
|||||||
const obj = {
|
const obj = {
|
||||||
white_list: form.value.white_list ? form.value.white_list.split('\n') : [],
|
white_list: form.value.white_list ? form.value.white_list.split('\n') : [],
|
||||||
white_active: form.value.white_active,
|
white_active: form.value.white_active,
|
||||||
access_num: form.value.access_num
|
access_num: form.value.access_num,
|
||||||
|
authentication: form.value.authentication,
|
||||||
|
authentication_value: form.value.authentication_value
|
||||||
}
|
}
|
||||||
applicationApi.putAccessToken(id as string, obj, loading).then((res) => {
|
applicationApi.putAccessToken(id as string, obj, loading).then((res) => {
|
||||||
emit('refresh')
|
emit('refresh')
|
||||||
@ -114,6 +146,17 @@ const submit = async (formEl: FormInstance | undefined) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
function generateAuthenticationValue(length: number = 10) {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||||
|
const randomValues = new Uint8Array(length)
|
||||||
|
window.crypto.getRandomValues(randomValues)
|
||||||
|
return Array.from(randomValues)
|
||||||
|
.map((value) => chars[value % chars.length])
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
|
function refreshAuthentication() {
|
||||||
|
form.value.authentication_value = generateAuthenticationValue()
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({ open })
|
defineExpose({ open })
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,101 +1,130 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chat-embed layout-bg" v-loading="loading">
|
<div class="chat-embed layout-bg" v-loading="loading">
|
||||||
<div class="chat-embed__header" :class="!isDefaultTheme ? 'custom-header' : ''">
|
<el-dialog
|
||||||
<div class="chat-width flex align-center">
|
v-model="isPasswordDialogVisible"
|
||||||
<div class="mr-12 ml-24 flex">
|
width="480px"
|
||||||
<AppAvatar
|
height="236px"
|
||||||
v-if="isAppIcon(applicationDetail?.icon)"
|
title="输入密码打开链接"
|
||||||
shape="square"
|
custom-class="no-close-button"
|
||||||
:size="32"
|
:close-on-click-modal="false"
|
||||||
style="background: none"
|
:close-on-press-escape="false"
|
||||||
>
|
:show-close="false"
|
||||||
<img :src="applicationDetail?.icon" alt="" />
|
center
|
||||||
</AppAvatar>
|
:modal="true"
|
||||||
<AppAvatar
|
|
||||||
v-else-if="applicationDetail?.name"
|
|
||||||
:name="applicationDetail?.name"
|
|
||||||
pinyinColor
|
|
||||||
shape="square"
|
|
||||||
:size="32"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4>{{ applicationDetail?.name }}</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="chat-embed__main">
|
|
||||||
<AiChat
|
|
||||||
ref="AiChatRef"
|
|
||||||
v-model:data="applicationDetail"
|
|
||||||
:available="applicationAvailable"
|
|
||||||
:appId="applicationDetail?.id"
|
|
||||||
:record="currentRecordList"
|
|
||||||
:chatId="currentChatId"
|
|
||||||
@refresh="refresh"
|
|
||||||
@scroll="handleScroll"
|
|
||||||
class="AiChat-embed"
|
|
||||||
>
|
|
||||||
<template #operateBefore>
|
|
||||||
<div class="chat-width">
|
|
||||||
<el-button type="primary" link class="new-chat-button mb-8" @click="newChat">
|
|
||||||
<el-icon><Plus /></el-icon><span class="ml-4">新建对话</span>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</AiChat>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 历史记录弹出层 -->
|
|
||||||
<div
|
|
||||||
v-if="applicationDetail.show_history || !user.isEnterprise()"
|
|
||||||
@click.prevent.stop="show = !show"
|
|
||||||
class="chat-popover-button cursor color-secondary"
|
|
||||||
>
|
>
|
||||||
<AppIcon iconName="app-history-outlined"></AppIcon>
|
<el-input
|
||||||
</div>
|
style="width: 400px; height: 40px"
|
||||||
|
v-model="password"
|
||||||
|
:placeholder="$t('login.ldap.passwordPlaceholder')"
|
||||||
|
show-password
|
||||||
|
/>
|
||||||
|
<span class="input-error" v-if="passwordError">{{ passwordError }}</span>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="validatePassword"
|
||||||
|
style="width: 400px; height: 40px; margin-top: 24px"
|
||||||
|
>确定</el-button
|
||||||
|
>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<el-collapse-transition>
|
<div v-if="isAuthenticated">
|
||||||
<div v-show="show" class="chat-popover w-full" v-click-outside="clickoutside">
|
<div class="chat-embed__header" :class="!isDefaultTheme ? 'custom-header' : ''">
|
||||||
<div class="border-b p-16-24">
|
<div class="chat-width flex align-center">
|
||||||
<span>历史记录</span>
|
<div class="mr-12 ml-24 flex">
|
||||||
</div>
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(applicationDetail?.icon)"
|
||||||
<el-scrollbar max-height="300">
|
shape="square"
|
||||||
<div class="p-8">
|
:size="32"
|
||||||
<common-list
|
style="background: none"
|
||||||
:data="chatLogeData"
|
|
||||||
v-loading="left_loading"
|
|
||||||
:defaultActive="currentChatId"
|
|
||||||
@click="clickListHandle"
|
|
||||||
@mouseenter="mouseenter"
|
|
||||||
@mouseleave="mouseId = ''"
|
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<img :src="applicationDetail?.icon" alt="" />
|
||||||
<div class="flex-between">
|
</AppAvatar>
|
||||||
<auto-tooltip :content="row.abstract">
|
<AppAvatar
|
||||||
{{ row.abstract }}
|
v-else-if="applicationDetail?.name"
|
||||||
</auto-tooltip>
|
:name="applicationDetail?.name"
|
||||||
<div @click.stop v-if="mouseId === row.id && row.id !== 'new'">
|
pinyinColor
|
||||||
<el-button style="padding: 0" link @click.stop="deleteLog(row)">
|
shape="square"
|
||||||
<el-icon><Delete /></el-icon>
|
:size="32"
|
||||||
</el-button>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #empty>
|
|
||||||
<div class="text-center">
|
|
||||||
<el-text type="info">暂无历史记录</el-text>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</common-list>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="chatLogeData.length" class="gradient-divider lighter mt-8">
|
|
||||||
<span>仅显示最近 20 条对话</span>
|
<h4>{{ applicationDetail?.name }}</h4>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
|
||||||
</div>
|
</div>
|
||||||
</el-collapse-transition>
|
<div class="chat-embed__main">
|
||||||
<div class="chat-popover-mask" v-show="show"></div>
|
<AiChat
|
||||||
|
ref="AiChatRef"
|
||||||
|
v-model:data="applicationDetail"
|
||||||
|
:available="applicationAvailable"
|
||||||
|
:appId="applicationDetail?.id"
|
||||||
|
:record="currentRecordList"
|
||||||
|
:chatId="currentChatId"
|
||||||
|
@refresh="refresh"
|
||||||
|
@scroll="handleScroll"
|
||||||
|
class="AiChat-embed"
|
||||||
|
>
|
||||||
|
<template #operateBefore>
|
||||||
|
<div class="chat-width">
|
||||||
|
<el-button type="primary" link class="new-chat-button mb-8" @click="newChat">
|
||||||
|
<el-icon><Plus /></el-icon><span class="ml-4">新建对话</span>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</AiChat>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 历史记录弹出层 -->
|
||||||
|
<div
|
||||||
|
v-if="applicationDetail.show_history || !user.isEnterprise()"
|
||||||
|
@click.prevent.stop="show = !show"
|
||||||
|
class="chat-popover-button cursor color-secondary"
|
||||||
|
>
|
||||||
|
<AppIcon iconName="app-history-outlined"></AppIcon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-collapse-transition>
|
||||||
|
<div v-show="show" class="chat-popover w-full" v-click-outside="clickoutside">
|
||||||
|
<div class="border-b p-16-24">
|
||||||
|
<span>历史记录</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-scrollbar max-height="300">
|
||||||
|
<div class="p-8">
|
||||||
|
<common-list
|
||||||
|
:data="chatLogeData"
|
||||||
|
v-loading="left_loading"
|
||||||
|
:defaultActive="currentChatId"
|
||||||
|
@click="clickListHandle"
|
||||||
|
@mouseenter="mouseenter"
|
||||||
|
@mouseleave="mouseId = ''"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex-between">
|
||||||
|
<auto-tooltip :content="row.abstract">
|
||||||
|
{{ row.abstract }}
|
||||||
|
</auto-tooltip>
|
||||||
|
<div @click.stop v-if="mouseId === row.id && row.id !== 'new'">
|
||||||
|
<el-button style="padding: 0" link @click.stop="deleteLog(row)">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #empty>
|
||||||
|
<div class="text-center">
|
||||||
|
<el-text type="info">暂无历史记录</el-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</common-list>
|
||||||
|
</div>
|
||||||
|
<div v-if="chatLogeData.length" class="gradient-divider lighter mt-8">
|
||||||
|
<span>仅显示最近 20 条对话</span>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</el-collapse-transition>
|
||||||
|
<div class="chat-popover-mask" v-show="show"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -121,6 +150,10 @@ const applicationDetail = ref<any>({})
|
|||||||
const applicationAvailable = ref<boolean>(true)
|
const applicationAvailable = ref<boolean>(true)
|
||||||
const chatLogeData = ref<any[]>([])
|
const chatLogeData = ref<any[]>([])
|
||||||
const show = ref(false)
|
const show = ref(false)
|
||||||
|
const isPasswordDialogVisible = ref(false)
|
||||||
|
const password = ref('')
|
||||||
|
const passwordError = ref('')
|
||||||
|
const isAuthenticated = ref(false)
|
||||||
|
|
||||||
const paginationConfig = reactive({
|
const paginationConfig = reactive({
|
||||||
current_page: 1,
|
current_page: 1,
|
||||||
@ -171,6 +204,20 @@ function newChat() {
|
|||||||
currentRecordList.value = []
|
currentRecordList.value = []
|
||||||
currentChatId.value = 'new'
|
currentChatId.value = 'new'
|
||||||
}
|
}
|
||||||
|
function validatePassword() {
|
||||||
|
if (!password.value) {
|
||||||
|
passwordError.value = '密码不能为空'
|
||||||
|
return // 终止后续执行
|
||||||
|
}
|
||||||
|
application.validatePassword(applicationDetail?.value.id, password.value).then((res: any) => {
|
||||||
|
if (res?.data.is_valid) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
isPasswordDialogVisible.value = false
|
||||||
|
} else {
|
||||||
|
passwordError.value = '密码错误'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getAccessToken(token: string) {
|
function getAccessToken(token: string) {
|
||||||
application
|
application
|
||||||
@ -189,6 +236,12 @@ function getAppProfile() {
|
|||||||
.asyncGetAppProfile(loading)
|
.asyncGetAppProfile(loading)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
applicationDetail.value = res.data
|
applicationDetail.value = res.data
|
||||||
|
if (user.isEnterprise()) {
|
||||||
|
isPasswordDialogVisible.value = applicationDetail?.value.authentication
|
||||||
|
}
|
||||||
|
if (!isPasswordDialogVisible.value) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
}
|
||||||
if (res.data?.show_history || !user.isEnterprise()) {
|
if (res.data?.show_history || !user.isEnterprise()) {
|
||||||
getChatLog(applicationDetail.value.id)
|
getChatLog(applicationDetail.value.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,128 +1,155 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chat-pc layout-bg" :class="classObj" v-loading="loading">
|
<div class="chat-pc layout-bg" :class="classObj" v-loading="loading">
|
||||||
<div class="chat-pc__header" :class="!isDefaultTheme ? 'custom-header' : ''">
|
<el-dialog
|
||||||
<div class="flex align-center">
|
v-model="isPasswordDialogVisible"
|
||||||
<div class="mr-12 ml-24 flex">
|
width="480px"
|
||||||
<AppAvatar
|
height="236px"
|
||||||
v-if="isAppIcon(applicationDetail?.icon)"
|
title="输入密码打开链接"
|
||||||
shape="square"
|
custom-class="no-close-button"
|
||||||
:size="32"
|
:close-on-click-modal="false"
|
||||||
style="background: none"
|
:close-on-press-escape="false"
|
||||||
>
|
:show-close="false"
|
||||||
<img :src="applicationDetail?.icon" alt="" />
|
center
|
||||||
</AppAvatar>
|
:modal="true"
|
||||||
<AppAvatar
|
>
|
||||||
v-else-if="applicationDetail?.name"
|
<el-input
|
||||||
:name="applicationDetail?.name"
|
style="width: 400px; height: 40px"
|
||||||
pinyinColor
|
v-model="password"
|
||||||
shape="square"
|
:placeholder="$t('login.ldap.passwordPlaceholder')"
|
||||||
:size="32"
|
show-password
|
||||||
/>
|
/>
|
||||||
</div>
|
<span class="input-error" v-if="passwordError">{{ passwordError }}</span>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="validatePassword"
|
||||||
|
style="width: 400px; height: 40px; margin-top: 24px"
|
||||||
|
>确定</el-button
|
||||||
|
>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<h4>{{ applicationDetail?.name }}</h4>
|
<div v-if="isAuthenticated">
|
||||||
</div>
|
<div class="chat-pc__header" :class="!isDefaultTheme ? 'custom-header' : ''">
|
||||||
</div>
|
<div class="flex align-center">
|
||||||
<div class="flex">
|
<div class="mr-12 ml-24 flex">
|
||||||
<div class="chat-pc__left border-r">
|
<AppAvatar
|
||||||
<div class="p-24 pb-0">
|
v-if="isAppIcon(applicationDetail?.icon)"
|
||||||
<el-button class="add-button w-full primary" @click="newChat">
|
shape="square"
|
||||||
<el-icon>
|
:size="32"
|
||||||
<Plus />
|
style="background: none"
|
||||||
</el-icon>
|
>
|
||||||
<span class="ml-4">新建对话</span>
|
<img :src="applicationDetail?.icon" alt="" />
|
||||||
</el-button>
|
</AppAvatar>
|
||||||
<p class="mt-20 mb-8">历史记录</p>
|
<AppAvatar
|
||||||
|
v-else-if="applicationDetail?.name"
|
||||||
|
:name="applicationDetail?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h4>{{ applicationDetail?.name }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="left-height pt-0">
|
</div>
|
||||||
<el-scrollbar>
|
<div class="flex">
|
||||||
<div class="p-8 pt-0">
|
<div class="chat-pc__left border-r">
|
||||||
<common-list
|
<div class="p-24 pb-0">
|
||||||
:data="chatLogeData"
|
<el-button class="add-button w-full primary" @click="newChat">
|
||||||
class="mt-8"
|
<el-icon>
|
||||||
v-loading="left_loading"
|
<Plus />
|
||||||
:defaultActive="currentChatId"
|
</el-icon>
|
||||||
@click="clickListHandle"
|
<span class="ml-4">新建对话</span>
|
||||||
@mouseenter="mouseenter"
|
</el-button>
|
||||||
@mouseleave="mouseId = ''"
|
<p class="mt-20 mb-8">历史记录</p>
|
||||||
>
|
</div>
|
||||||
<template #default="{ row }">
|
<div class="left-height pt-0">
|
||||||
<div class="flex-between">
|
<el-scrollbar>
|
||||||
<auto-tooltip :content="row.abstract">
|
<div class="p-8 pt-0">
|
||||||
{{ row.abstract }}
|
<common-list
|
||||||
</auto-tooltip>
|
:data="chatLogeData"
|
||||||
<div @click.stop v-if="mouseId === row.id && row.id !== 'new'">
|
class="mt-8"
|
||||||
<el-button style="padding: 0" link @click.stop="deleteLog(row)">
|
v-loading="left_loading"
|
||||||
<el-icon><Delete /></el-icon>
|
:defaultActive="currentChatId"
|
||||||
</el-button>
|
@click="clickListHandle"
|
||||||
|
@mouseenter="mouseenter"
|
||||||
|
@mouseleave="mouseId = ''"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex-between">
|
||||||
|
<auto-tooltip :content="row.abstract">
|
||||||
|
{{ row.abstract }}
|
||||||
|
</auto-tooltip>
|
||||||
|
<div @click.stop v-if="mouseId === row.id && row.id !== 'new'">
|
||||||
|
<el-button style="padding: 0" link @click.stop="deleteLog(row)">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<el-text type="info">暂无历史记录</el-text>
|
<el-text type="info">暂无历史记录</el-text>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</common-list>
|
</common-list>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="chatLogeData.length" class="gradient-divider lighter mt-8">
|
<div v-if="chatLogeData.length" class="gradient-divider lighter mt-8">
|
||||||
<span>仅显示最近 20 条对话</span>
|
<span>仅显示最近 20 条对话</span>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="chat-pc__right">
|
||||||
<div class="chat-pc__right">
|
<div class="right-header border-b mb-24 p-16-24 flex-between">
|
||||||
<div class="right-header border-b mb-24 p-16-24 flex-between">
|
<h4 class="ellipsis-1" style="width: 70%">
|
||||||
<h4 class="ellipsis-1" style="width: 70%">
|
{{ currentChatName }}
|
||||||
{{ currentChatName }}
|
</h4>
|
||||||
</h4>
|
|
||||||
|
|
||||||
<span class="flex align-center" v-if="currentRecordList.length">
|
<span class="flex align-center" v-if="currentRecordList.length">
|
||||||
<AppIcon
|
<AppIcon
|
||||||
v-if="paginationConfig.total"
|
v-if="paginationConfig.total"
|
||||||
iconName="app-chat-record"
|
iconName="app-chat-record"
|
||||||
class="info mr-8"
|
class="info mr-8"
|
||||||
style="font-size: 16px"
|
style="font-size: 16px"
|
||||||
></AppIcon>
|
></AppIcon>
|
||||||
<span v-if="paginationConfig.total" class="lighter">
|
<span v-if="paginationConfig.total" class="lighter">
|
||||||
{{ paginationConfig.total }} 条提问
|
{{ paginationConfig.total }} 条提问
|
||||||
|
</span>
|
||||||
|
<el-dropdown class="ml-8">
|
||||||
|
<AppIcon iconName="app-export" class="cursor" title="导出聊天记录"></AppIcon>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item @click="exportMarkdown">导出 Markdown</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click="exportHTML">导出 HTML</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
</span>
|
</span>
|
||||||
<el-dropdown class="ml-8">
|
</div>
|
||||||
<AppIcon iconName="app-export" class="cursor" title="导出聊天记录"></AppIcon>
|
<div class="right-height">
|
||||||
<template #dropdown>
|
<AiChat
|
||||||
<el-dropdown-menu>
|
ref="AiChatRef"
|
||||||
<el-dropdown-item @click="exportMarkdown">导出 Markdown</el-dropdown-item>
|
v-model:data="applicationDetail"
|
||||||
<el-dropdown-item @click="exportHTML">导出 HTML</el-dropdown-item>
|
:available="applicationAvailable"
|
||||||
</el-dropdown-menu>
|
:appId="applicationDetail?.id"
|
||||||
</template>
|
:record="currentRecordList"
|
||||||
</el-dropdown>
|
:chatId="currentChatId"
|
||||||
</span>
|
@refresh="refresh"
|
||||||
</div>
|
@scroll="handleScroll"
|
||||||
<div class="right-height">
|
>
|
||||||
<!-- 对话 -->
|
</AiChat>
|
||||||
<AiChat
|
</div>
|
||||||
ref="AiChatRef"
|
|
||||||
v-model:data="applicationDetail"
|
|
||||||
:available="applicationAvailable"
|
|
||||||
:appId="applicationDetail?.id"
|
|
||||||
:record="currentRecordList"
|
|
||||||
:chatId="currentChatId"
|
|
||||||
@refresh="refresh"
|
|
||||||
@scroll="handleScroll"
|
|
||||||
>
|
|
||||||
</AiChat>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="collapse">
|
||||||
|
<el-button @click="isCollapse = !isCollapse">
|
||||||
<div class="collapse">
|
<el-icon> <component :is="isCollapse ? 'Fold' : 'Expand'" /></el-icon>
|
||||||
<el-button @click="isCollapse = !isCollapse">
|
</el-button>
|
||||||
<el-icon> <component :is="isCollapse ? 'Fold' : 'Expand'" /></el-icon>
|
</div>
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref, onMounted, nextTick, computed } from 'vue'
|
import { reactive, ref, onMounted, nextTick, computed } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
@ -130,8 +157,11 @@ import { marked } from 'marked'
|
|||||||
import { saveAs } from 'file-saver'
|
import { saveAs } from 'file-saver'
|
||||||
import { isAppIcon } from '@/utils/application'
|
import { isAppIcon } from '@/utils/application'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
|
|
||||||
import useResize from '@/layout/hooks/useResize'
|
import useResize from '@/layout/hooks/useResize'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import authApi from '@/api/auth-setting'
|
||||||
|
import { MsgSuccess } from '@/utils/message'
|
||||||
useResize()
|
useResize()
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -147,6 +177,10 @@ const isDefaultTheme = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isCollapse = ref(false)
|
const isCollapse = ref(false)
|
||||||
|
const isPasswordDialogVisible = ref(false)
|
||||||
|
const password = ref('')
|
||||||
|
const passwordError = ref('')
|
||||||
|
const isAuthenticated = ref(false)
|
||||||
|
|
||||||
const classObj = computed(() => {
|
const classObj = computed(() => {
|
||||||
return {
|
return {
|
||||||
@ -225,6 +259,12 @@ function getAppProfile() {
|
|||||||
.asyncGetAppProfile(loading)
|
.asyncGetAppProfile(loading)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
applicationDetail.value = res.data
|
applicationDetail.value = res.data
|
||||||
|
if (user.isEnterprise()) {
|
||||||
|
isPasswordDialogVisible.value = applicationDetail?.value.authentication
|
||||||
|
}
|
||||||
|
if (!isPasswordDialogVisible.value) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
}
|
||||||
if (res.data?.show_history || !user.isEnterprise()) {
|
if (res.data?.show_history || !user.isEnterprise()) {
|
||||||
getChatLog(applicationDetail.value.id)
|
getChatLog(applicationDetail.value.id)
|
||||||
}
|
}
|
||||||
@ -336,6 +376,21 @@ async function exportHTML(): Promise<void> {
|
|||||||
saveAs(blob, suggestedName)
|
saveAs(blob, suggestedName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validatePassword() {
|
||||||
|
if (!password.value) {
|
||||||
|
passwordError.value = '密码不能为空'
|
||||||
|
return // 终止后续执行
|
||||||
|
}
|
||||||
|
application.validatePassword(applicationDetail?.value.id, password.value).then((res: any) => {
|
||||||
|
if (res?.data.is_valid) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
isPasswordDialogVisible.value = false
|
||||||
|
} else {
|
||||||
|
passwordError.value = '密码错误'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
user.changeUserType(2)
|
user.changeUserType(2)
|
||||||
getAccessToken(accessToken)
|
getAccessToken(accessToken)
|
||||||
@ -455,4 +510,8 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.input-error {
|
||||||
|
color: red;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user