mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
refactor: 对话秘钥校验 (#1430)
This commit is contained in:
parent
5973957037
commit
23c7269231
|
|
@ -36,7 +36,7 @@ from common.db.sql_execute import select_list
|
|||
from common.exception.app_exception import AppApiException, NotFound404, AppUnauthorizedFailed
|
||||
from common.field.common import UploadedImageField
|
||||
from common.models.db_model_manage import DBModelManage
|
||||
from common.util.common import valid_license
|
||||
from common.util.common import valid_license, password_encrypt
|
||||
from common.util.field_message import ErrMessage
|
||||
from common.util.file_util import get_file_content
|
||||
from dataset.models import DataSet, Document, Image
|
||||
|
|
@ -264,7 +264,9 @@ class ApplicationSerializer(serializers.Serializer):
|
|||
if work_flow is not None:
|
||||
for node in work_flow.get('nodes', []):
|
||||
if node['id'] == 'base-node':
|
||||
input_field_list = node.get('properties', {}).get('api_input_field_list', node.get('properties', {}).get('input_field_list', []))
|
||||
input_field_list = node.get('properties', {}).get('api_input_field_list',
|
||||
node.get('properties', {}).get(
|
||||
'input_field_list', []))
|
||||
if input_field_list is not None:
|
||||
for field in input_field_list:
|
||||
if field['assignment_method'] == 'api_input' and field['variable'] in params:
|
||||
|
|
@ -352,6 +354,8 @@ class ApplicationSerializer(serializers.Serializer):
|
|||
|
||||
class Authentication(serializers.Serializer):
|
||||
access_token = serializers.CharField(required=True, error_messages=ErrMessage.char("access_token"))
|
||||
authentication_value = serializers.JSONField(required=False, allow_null=True,
|
||||
error_messages=ErrMessage.char("认证信息"))
|
||||
|
||||
def auth(self, request, with_valid=True):
|
||||
token = request.META.get('HTTP_AUTHORIZATION')
|
||||
|
|
@ -366,21 +370,47 @@ class ApplicationSerializer(serializers.Serializer):
|
|||
self.is_valid(raise_exception=True)
|
||||
access_token = self.data.get("access_token")
|
||||
application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first()
|
||||
authentication_value = self.data.get('authentication_value', None)
|
||||
authentication = {}
|
||||
if application_access_token is not None and application_access_token.is_active:
|
||||
if token_details is not None and 'client_id' in token_details and token_details.get(
|
||||
'client_id') is not None:
|
||||
client_id = token_details.get('client_id')
|
||||
authentication = {'type': token_details.get('type'),
|
||||
'value': token_details.get('value')}
|
||||
else:
|
||||
client_id = str(uuid.uuid1())
|
||||
if authentication_value is not None:
|
||||
# 认证用户token
|
||||
self.auth_authentication_value(authentication_value, str(application_access_token.application_id))
|
||||
authentication = {'type': authentication_value.get('type'),
|
||||
'value': password_encrypt(authentication_value.get('value'))}
|
||||
token = signing.dumps({'application_id': str(application_access_token.application_id),
|
||||
'user_id': str(application_access_token.application.user.id),
|
||||
'access_token': application_access_token.access_token,
|
||||
'type': AuthenticationType.APPLICATION_ACCESS_TOKEN.value,
|
||||
'client_id': client_id})
|
||||
'client_id': client_id
|
||||
, **authentication})
|
||||
return token
|
||||
else:
|
||||
raise NotFound404(404, "无效的access_token")
|
||||
|
||||
def auth_authentication_value(self, authentication_value, application_id):
|
||||
application_setting_model = DBModelManage.get_model('application_setting')
|
||||
xpack_cache = DBModelManage.get_model('xpack_cache')
|
||||
X_PACK_LICENSE_IS_VALID = False if xpack_cache is None else xpack_cache.get('XPACK_LICENSE_IS_VALID', False)
|
||||
if application_setting_model is not None and X_PACK_LICENSE_IS_VALID:
|
||||
application_setting = QuerySet(application_setting_model).filter(application_id=application_id).first()
|
||||
if application_setting.authentication and authentication_value is not None:
|
||||
if authentication_value.get('type') == 'password':
|
||||
if not self.auth_password(authentication_value, application_setting.authentication_value):
|
||||
raise AppApiException(1005, "密码错误")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def auth_password(source_authentication_value, authentication_value):
|
||||
return source_authentication_value.get('value') == authentication_value.get('value')
|
||||
|
||||
class Edit(serializers.Serializer):
|
||||
name = serializers.CharField(required=False, max_length=64, min_length=1,
|
||||
error_messages=ErrMessage.char("应用名称"))
|
||||
|
|
@ -762,6 +792,8 @@ class ApplicationSerializer(serializers.Serializer):
|
|||
'avatar': application_setting.avatar,
|
||||
'float_icon': application_setting.float_icon,
|
||||
'authentication': application_setting.authentication,
|
||||
'authentication_type': application_setting.authentication_value.get(
|
||||
'type', 'password'),
|
||||
'disclaimer': application_setting.disclaimer,
|
||||
'disclaimer_value': application_setting.disclaimer_value,
|
||||
'custom_theme': application_setting.custom_theme,
|
||||
|
|
|
|||
|
|
@ -376,7 +376,9 @@ class Application(APIView):
|
|||
security=[])
|
||||
def post(self, request: Request):
|
||||
return result.success(
|
||||
ApplicationSerializer.Authentication(data={'access_token': request.data.get("access_token")}).auth(
|
||||
ApplicationSerializer.Authentication(data={'access_token': request.data.get("access_token"),
|
||||
'authentication_value': request.data.get(
|
||||
'authentication_value')}).auth(
|
||||
request),
|
||||
headers={"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true",
|
||||
"Access-Control-Allow-Methods": "POST",
|
||||
|
|
@ -539,12 +541,13 @@ class Application(APIView):
|
|||
authentication_classes = [TokenAuth]
|
||||
|
||||
@action(methods=['POST'], detail=False)
|
||||
@has_permissions(ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN],
|
||||
[lambda r, keywords: Permission(group=Group.APPLICATION,
|
||||
operate=Operate.USE,
|
||||
dynamic_tag=keywords.get(
|
||||
'application_id'))],
|
||||
compare=CompareConstants.AND))
|
||||
@has_permissions(
|
||||
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN],
|
||||
[lambda r, keywords: Permission(group=Group.APPLICATION,
|
||||
operate=Operate.USE,
|
||||
dynamic_tag=keywords.get(
|
||||
'application_id'))],
|
||||
compare=CompareConstants.AND))
|
||||
def post(self, request: Request, application_id: str):
|
||||
return result.success(
|
||||
ApplicationSerializer.Operate(data={'application_id': application_id, 'user_id': request.user.id})
|
||||
|
|
@ -554,31 +557,33 @@ class Application(APIView):
|
|||
authentication_classes = [TokenAuth]
|
||||
|
||||
@action(methods=['POST'], detail=False)
|
||||
@has_permissions(ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN],
|
||||
[lambda r, keywords: Permission(group=Group.APPLICATION,
|
||||
operate=Operate.USE,
|
||||
dynamic_tag=keywords.get(
|
||||
'application_id'))],
|
||||
compare=CompareConstants.AND))
|
||||
@has_permissions(
|
||||
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN],
|
||||
[lambda r, keywords: Permission(group=Group.APPLICATION,
|
||||
operate=Operate.USE,
|
||||
dynamic_tag=keywords.get(
|
||||
'application_id'))],
|
||||
compare=CompareConstants.AND))
|
||||
def post(self, request: Request, application_id: str):
|
||||
byte_data = ApplicationSerializer.Operate(
|
||||
data={'application_id': application_id, 'user_id': request.user.id}).text_to_speech(
|
||||
request.data.get('text'))
|
||||
return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',
|
||||
'Content-Disposition': 'attachment; filename="abc.mp3"'})
|
||||
'Content-Disposition': 'attachment; filename="abc.mp3"'})
|
||||
|
||||
class PlayDemoText(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@action(methods=['POST'], detail=False)
|
||||
@has_permissions(ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN],
|
||||
[lambda r, keywords: Permission(group=Group.APPLICATION,
|
||||
operate=Operate.USE,
|
||||
dynamic_tag=keywords.get(
|
||||
'application_id'))],
|
||||
compare=CompareConstants.AND))
|
||||
@has_permissions(
|
||||
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN],
|
||||
[lambda r, keywords: Permission(group=Group.APPLICATION,
|
||||
operate=Operate.USE,
|
||||
dynamic_tag=keywords.get(
|
||||
'application_id'))],
|
||||
compare=CompareConstants.AND))
|
||||
def post(self, request: Request, application_id: str):
|
||||
byte_data = ApplicationSerializer.Operate(
|
||||
data={'application_id': application_id, 'user_id': request.user.id}).play_demo_text(request.data)
|
||||
return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',
|
||||
'Content-Disposition': 'attachment; filename="abc.mp3"'})
|
||||
'Content-Disposition': 'attachment; filename="abc.mp3"'})
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ from application.models.api_key_model import ApplicationAccessToken
|
|||
from common.auth.handle.auth_base_handle import AuthBaseHandle
|
||||
from common.constants.authentication_type import AuthenticationType
|
||||
from common.constants.permission_constants import RoleConstants, Permission, Group, Operate, Auth
|
||||
from common.exception.app_exception import AppAuthenticationFailed
|
||||
from common.exception.app_exception import AppAuthenticationFailed, ChatException
|
||||
from common.models.db_model_manage import DBModelManage
|
||||
from common.util.common import password_encrypt
|
||||
|
||||
|
||||
class PublicAccessToken(AuthBaseHandle):
|
||||
|
|
@ -29,6 +31,20 @@ class PublicAccessToken(AuthBaseHandle):
|
|||
auth_details = get_token_details()
|
||||
application_access_token = QuerySet(ApplicationAccessToken).filter(
|
||||
application_id=auth_details.get('application_id')).first()
|
||||
application_setting_model = DBModelManage.get_model('application_setting')
|
||||
xpack_cache = DBModelManage.get_model('xpack_cache')
|
||||
X_PACK_LICENSE_IS_VALID = False if xpack_cache is None else xpack_cache.get('XPACK_LICENSE_IS_VALID', False)
|
||||
if application_setting_model is not None and X_PACK_LICENSE_IS_VALID:
|
||||
application_setting = QuerySet(application_setting_model).filter(application_id=str(
|
||||
application_access_token.application_id)).first()
|
||||
if application_setting.authentication:
|
||||
authentication = auth_details.get('authentication', {})
|
||||
if authentication is None:
|
||||
authentication = {}
|
||||
if application_setting.authentication_value.get('type') != authentication.get(
|
||||
'type') or password_encrypt(
|
||||
application_setting.authentication_value.get('value')) != authentication.get('value'):
|
||||
raise ChatException(1002, "身份验证信息不正确")
|
||||
if application_access_token is None:
|
||||
raise AppAuthenticationFailed(1002, "身份验证信息不正确")
|
||||
if not application_access_token.is_active:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
@date:2023/10/16 16:42
|
||||
@desc:
|
||||
"""
|
||||
import hashlib
|
||||
import importlib
|
||||
from functools import reduce
|
||||
from typing import Dict, List
|
||||
|
|
@ -62,6 +63,18 @@ def flat_map(array: List[List]):
|
|||
return result
|
||||
|
||||
|
||||
def password_encrypt(raw_password):
|
||||
"""
|
||||
密码 md5加密
|
||||
:param raw_password: 密码
|
||||
:return: 加密后密码
|
||||
"""
|
||||
md5 = hashlib.md5() # 2,实例化md5() 方法
|
||||
md5.update(raw_password.encode()) # 3,对字符串的字节类型加密
|
||||
result = md5.hexdigest() # 4,加密
|
||||
return result
|
||||
|
||||
|
||||
def post(post_function):
|
||||
def inner(func):
|
||||
def run(*args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -122,11 +122,17 @@ const putAccessToken: (
|
|||
"access_token": "string"
|
||||
}
|
||||
*/
|
||||
const postAppAuthentication: (access_token: string, loading?: Ref<boolean>) => Promise<any> = (
|
||||
access_token,
|
||||
loading
|
||||
) => {
|
||||
return post(`${prefix}/authentication`, { access_token }, undefined, loading)
|
||||
const postAppAuthentication: (
|
||||
access_token: string,
|
||||
loading?: Ref<boolean>,
|
||||
authentication_value?: any
|
||||
) => Promise<any> = (access_token, loading, authentication_value) => {
|
||||
return post(
|
||||
`${prefix}/authentication`,
|
||||
{ access_token: access_token, authentication_value },
|
||||
undefined,
|
||||
loading
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -365,8 +365,7 @@ function handleInputFieldList() {
|
|||
?.filter((v: any) => v.id === 'base-node')
|
||||
.map((v: any) => {
|
||||
inputFieldList.value = v.properties.user_input_field_list
|
||||
? v.properties.user_input_field_list
|
||||
.map((v: any) => {
|
||||
? v.properties.user_input_field_list.map((v: any) => {
|
||||
switch (v.type) {
|
||||
case 'input':
|
||||
return {
|
||||
|
|
@ -404,51 +403,51 @@ function handleInputFieldList() {
|
|||
return v
|
||||
}
|
||||
})
|
||||
: v.properties.input_field_list ? v.properties.input_field_list
|
||||
.filter((v: any) => v.assignment_method === 'user_input')
|
||||
.map((v: any) => {
|
||||
switch (v.type) {
|
||||
case 'input':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'TextInput',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required
|
||||
}
|
||||
case 'select':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'SingleSelect',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required,
|
||||
option_list: v.optionList.map((o: any) => {
|
||||
return { key: o, value: o }
|
||||
})
|
||||
}
|
||||
case 'date':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'DatePicker',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required,
|
||||
attrs: {
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
||||
type: 'datetime'
|
||||
: v.properties.input_field_list
|
||||
? v.properties.input_field_list
|
||||
.filter((v: any) => v.assignment_method === 'user_input')
|
||||
.map((v: any) => {
|
||||
switch (v.type) {
|
||||
case 'input':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'TextInput',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
case 'select':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'SingleSelect',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required,
|
||||
option_list: v.optionList.map((o: any) => {
|
||||
return { key: o, value: o }
|
||||
})
|
||||
}
|
||||
case 'date':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'DatePicker',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required,
|
||||
attrs: {
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
||||
type: 'datetime'
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
: []
|
||||
|
||||
apiInputFieldList.value = v.properties.api_input_field_list
|
||||
? v.properties.api_input_field_list
|
||||
.map((v: any) => {
|
||||
? v.properties.api_input_field_list.map((v: any) => {
|
||||
switch (v.type) {
|
||||
case 'input':
|
||||
return {
|
||||
|
|
@ -488,45 +487,45 @@ function handleInputFieldList() {
|
|||
})
|
||||
: v.properties.input_field_list
|
||||
? v.properties.input_field_list
|
||||
.filter((v: any) => v.assignment_method === 'api_input')
|
||||
.map((v: any) => {
|
||||
switch (v.type) {
|
||||
case 'input':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'TextInput',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required
|
||||
}
|
||||
case 'select':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'SingleSelect',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required,
|
||||
option_list: v.optionList.map((o: any) => {
|
||||
return { key: o, value: o }
|
||||
})
|
||||
}
|
||||
case 'date':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'DatePicker',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required,
|
||||
attrs: {
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
||||
type: 'datetime'
|
||||
.filter((v: any) => v.assignment_method === 'api_input')
|
||||
.map((v: any) => {
|
||||
switch (v.type) {
|
||||
case 'input':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'TextInput',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
case 'select':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'SingleSelect',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required,
|
||||
option_list: v.optionList.map((o: any) => {
|
||||
return { key: o, value: o }
|
||||
})
|
||||
}
|
||||
case 'date':
|
||||
return {
|
||||
field: v.variable,
|
||||
input_type: 'DatePicker',
|
||||
label: v.name,
|
||||
default_value: default_value[v.variable],
|
||||
required: v.is_required,
|
||||
attrs: {
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
||||
type: 'datetime'
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
: []
|
||||
})
|
||||
}
|
||||
|
|
@ -912,7 +911,7 @@ const mediaRecorderStatus = ref(true)
|
|||
const startRecording = async () => {
|
||||
try {
|
||||
// 取消录音控制台日志
|
||||
Recorder.CLog=function(){}
|
||||
Recorder.CLog = function () {}
|
||||
mediaRecorderStatus.value = false
|
||||
handleTimeChange()
|
||||
mediaRecorder.value = new Recorder({
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ instance.interceptors.response.use(
|
|||
(response: any) => {
|
||||
if (response.data) {
|
||||
if (response.data.code !== 200 && !(response.data instanceof Blob)) {
|
||||
if (response.config.url.includes('/application/authentication')) {
|
||||
return Promise.reject(response.data)
|
||||
}
|
||||
if (
|
||||
!response.config.url.includes('/valid') &&
|
||||
!response.config.url.includes('/function_lib/debug')
|
||||
|
|
|
|||
|
|
@ -89,10 +89,14 @@ const useApplicationStore = defineStore({
|
|||
})
|
||||
},
|
||||
|
||||
async asyncAppAuthentication(token: string, loading?: Ref<boolean>) {
|
||||
async asyncAppAuthentication(
|
||||
token: string,
|
||||
loading?: Ref<boolean>,
|
||||
authentication_value?: any
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
applicationApi
|
||||
.postAppAuthentication(token, loading)
|
||||
.postAppAuthentication(token, loading, authentication_value)
|
||||
.then((res) => {
|
||||
localStorage.setItem('accessToken', res.data)
|
||||
sessionStorage.setItem('accessToken', res.data)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:modelValue="show"
|
||||
modal-class="positioned-mask"
|
||||
width="300"
|
||||
title="请输入密码打开链接"
|
||||
custom-class="no-close-button"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
top="25vh"
|
||||
center
|
||||
:modal="true"
|
||||
>
|
||||
<el-form ref="FormRef" :model="form">
|
||||
<el-form-item prop="value" :rules="rules.value">
|
||||
<el-input show-password v-model="form.value" />
|
||||
</el-form-item>
|
||||
<el-button class="w-full mt-8" type="primary" @click="validator" :loading="loading"
|
||||
>确定</el-button
|
||||
>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import useStore from '@/stores'
|
||||
const route = useRoute()
|
||||
const FormRef = ref()
|
||||
const {
|
||||
params: { accessToken }
|
||||
} = route as any
|
||||
const { application } = useStore()
|
||||
const props = defineProps<{ applicationProfile: any; modelValue: boolean }>()
|
||||
const loading = ref<boolean>(false)
|
||||
const show = computed(() => {
|
||||
if (props.applicationProfile) {
|
||||
if (props.modelValue) {
|
||||
return false
|
||||
}
|
||||
return props.applicationProfile.authentication
|
||||
}
|
||||
return false
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const auth = () => {
|
||||
return application.asyncAppAuthentication(accessToken, loading, form.value).then(() => {
|
||||
emit('update:modelValue', true)
|
||||
})
|
||||
}
|
||||
const validator_auth = (rule: any, value: string, callback: any) => {
|
||||
if (value === '') {
|
||||
callback(new Error('密码不能为空'))
|
||||
} else {
|
||||
auth().catch(() => {
|
||||
callback(new Error('密码错误'))
|
||||
})
|
||||
}
|
||||
}
|
||||
const validator = () => {
|
||||
FormRef.value.validate()
|
||||
}
|
||||
|
||||
const rules = {
|
||||
value: [{ required: true, validator: validator_auth, trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const form = ref({
|
||||
type: 'password',
|
||||
value: ''
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.positioned-mask {
|
||||
top: var(--app-header-height);
|
||||
height: calc(100% - var(--app-header-height));
|
||||
.el-overlay-dialog {
|
||||
top: var(--app-header-height);
|
||||
height: calc(100% - var(--app-header-height));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<div class="chat-pc__header">
|
||||
<div class="flex align-center">
|
||||
<div class="mr-12 ml-24 flex">
|
||||
<AppAvatar
|
||||
v-if="isAppIcon(application_profile?.icon)"
|
||||
shape="square"
|
||||
:size="32"
|
||||
style="background: none"
|
||||
>
|
||||
<img :src="application_profile?.icon" alt="" />
|
||||
</AppAvatar>
|
||||
<AppAvatar
|
||||
v-else-if="application_profile?.name"
|
||||
:name="application_profile?.name"
|
||||
pinyinColor
|
||||
shape="square"
|
||||
:size="32"
|
||||
/>
|
||||
</div>
|
||||
<h4>{{ application_profile?.name }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<component
|
||||
:is="auth_components[`/src/views/chat/auth/component/${auth_type}.vue`].default"
|
||||
v-model="is_auth"
|
||||
:applicationProfile="application_profile"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { isAppIcon } from '@/utils/application'
|
||||
const auth_components: any = import.meta.glob('@/views/chat/auth/component/*.vue', {
|
||||
eager: true
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{ modelValue: boolean; application_profile: any; auth_type?: string }>(),
|
||||
{
|
||||
auth_type: 'password'
|
||||
}
|
||||
)
|
||||
const is_auth = computed({
|
||||
get: () => {
|
||||
return props.modelValue
|
||||
},
|
||||
set: (v) => {
|
||||
emit('update:modelValue', v)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
|
@ -34,49 +34,27 @@
|
|||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
import { isAppIcon } from '@/utils/application'
|
||||
import useStore from '@/stores'
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { accessToken }
|
||||
} = route as any
|
||||
|
||||
const { application, user } = useStore()
|
||||
const { user } = useStore()
|
||||
|
||||
const isDefaultTheme = computed(() => {
|
||||
return user.isDefaultTheme()
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const applicationDetail = ref<any>({})
|
||||
const applicationAvailable = ref<boolean>(true)
|
||||
|
||||
function getAccessToken(token: string) {
|
||||
application
|
||||
.asyncAppAuthentication(token, loading)
|
||||
.then(() => {
|
||||
getAppProfile()
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
function getAppProfile() {
|
||||
application
|
||||
.asyncGetAppProfile(loading)
|
||||
.then((res: any) => {
|
||||
applicationDetail.value = res.data
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
user.changeUserType(2)
|
||||
getAccessToken(accessToken)
|
||||
const props = defineProps<{
|
||||
application_profile: any
|
||||
applicationAvailable: boolean
|
||||
}>()
|
||||
const applicationDetail = computed({
|
||||
get: () => {
|
||||
return props.application_profile
|
||||
},
|
||||
set: (v) => {}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,31 +1,5 @@
|
|||
<template>
|
||||
<div class="chat-embed layout-bg" v-loading="loading">
|
||||
<el-dialog
|
||||
v-model="isPasswordDialogVisible"
|
||||
width="300"
|
||||
title="请输入密码打开链接"
|
||||
custom-class="no-close-button"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
align-center
|
||||
center
|
||||
:modal="true"
|
||||
>
|
||||
<el-form ref="FormRef" :model="form" :rules="rules" v-loading="validateLoading">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
:placeholder="$t('login.ldap.passwordPlaceholder')"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-button class="w-full mt-8" type="primary" @click="submitHandle(FormRef)"
|
||||
>确定</el-button
|
||||
>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
|
||||
<div class="chat-embed__header" :class="!isDefaultTheme ? 'custom-header' : ''">
|
||||
<div class="chat-width flex align-center">
|
||||
<div class="mr-12 ml-24 flex">
|
||||
|
|
@ -49,7 +23,7 @@
|
|||
<h4>{{ applicationDetail?.name }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isAuthenticated">
|
||||
<div>
|
||||
<div class="chat-embed__main">
|
||||
<AiChat
|
||||
ref="AiChatRef"
|
||||
|
|
@ -128,16 +102,11 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive, nextTick, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { isAppIcon } from '@/utils/application'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import useStore from '@/stores'
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { accessToken }
|
||||
} = route as any
|
||||
|
||||
const { application, user, log } = useStore()
|
||||
import { isAppIcon } from '@/utils/application'
|
||||
import useStore from '@/stores'
|
||||
|
||||
const { user, log } = useStore()
|
||||
|
||||
const isDefaultTheme = computed(() => {
|
||||
return user.isDefaultTheme()
|
||||
|
|
@ -146,47 +115,19 @@ const isDefaultTheme = computed(() => {
|
|||
const AiChatRef = ref()
|
||||
const loading = ref(false)
|
||||
const left_loading = ref(false)
|
||||
const applicationDetail = ref<any>({})
|
||||
const applicationAvailable = ref<boolean>(true)
|
||||
|
||||
const chatLogeData = ref<any[]>([])
|
||||
const show = ref(false)
|
||||
|
||||
const FormRef = ref()
|
||||
|
||||
const isPasswordDialogVisible = ref(false)
|
||||
const validateLoading = ref(false)
|
||||
|
||||
const form = ref({
|
||||
password: ''
|
||||
const props = defineProps<{
|
||||
application_profile: any
|
||||
applicationAvailable: boolean
|
||||
}>()
|
||||
const applicationDetail = computed({
|
||||
get: () => {
|
||||
return props.application_profile
|
||||
},
|
||||
set: (v) => {}
|
||||
})
|
||||
|
||||
const isAuthenticated = ref(false)
|
||||
|
||||
const validateName = (rule: any, value: string, callback: any) => {
|
||||
if (value === '') {
|
||||
callback(new Error('密码不能为空'))
|
||||
} else {
|
||||
application
|
||||
.validatePassword(applicationDetail?.value.id, form.value.password, validateLoading)
|
||||
.then((res: any) => {
|
||||
if (res?.data.is_valid) {
|
||||
isAuthenticated.value = true
|
||||
isPasswordDialogVisible.value = false
|
||||
} else {
|
||||
callback(new Error('密码错误'))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const rules = reactive({
|
||||
password: [{ required: true, validator: validateName, trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const submitHandle = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid) => {})
|
||||
}
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 20,
|
||||
|
|
@ -237,38 +178,6 @@ function newChat() {
|
|||
currentChatId.value = 'new'
|
||||
}
|
||||
|
||||
function getAccessToken(token: string) {
|
||||
application
|
||||
.asyncAppAuthentication(token, loading)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
getAppProfile()
|
||||
}, 500)
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
function getAppProfile() {
|
||||
application
|
||||
.asyncGetAppProfile(loading)
|
||||
.then((res: any) => {
|
||||
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()) {
|
||||
getChatLog(applicationDetail.value.id)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function getChatLog(id: string) {
|
||||
const page = {
|
||||
current_page: 1,
|
||||
|
|
@ -324,10 +233,16 @@ function refresh(id: string) {
|
|||
getChatLog(applicationDetail.value.id)
|
||||
currentChatId.value = id
|
||||
}
|
||||
|
||||
/**
|
||||
*初始化历史对话记录
|
||||
*/
|
||||
const init = () => {
|
||||
if (applicationDetail.value.show_history || !user.isEnterprise()) {
|
||||
getChatLog(applicationDetail.value.id)
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
user.changeUserType(2)
|
||||
getAccessToken(accessToken)
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,20 +1,36 @@
|
|||
<template>
|
||||
<component :is="currentTemplate" :key="route.fullPath" />
|
||||
<component
|
||||
v-if="chat_show"
|
||||
:applicationAvailable="applicationAvailable"
|
||||
:is="currentTemplate"
|
||||
:application_profile="application_profile"
|
||||
:key="route.fullPath"
|
||||
v-loading="loading"
|
||||
/>
|
||||
<Auth
|
||||
v-else
|
||||
:application_profile="application_profile"
|
||||
:auth_type="application_profile.authentication_type"
|
||||
v-model="is_auth"
|
||||
></Auth>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import useStore from '@/stores'
|
||||
import Auth from '@/views/chat/auth/index.vue'
|
||||
const route = useRoute()
|
||||
const { application, user } = useStore()
|
||||
|
||||
const components: any = import.meta.glob('@/views/chat/**/index.vue', {
|
||||
eager: true
|
||||
})
|
||||
const route = useRoute()
|
||||
const {
|
||||
query: { mode }
|
||||
} = route as any
|
||||
|
||||
const {
|
||||
query: { mode },
|
||||
params: { accessToken }
|
||||
} = route as any
|
||||
const is_auth = ref<boolean>(false)
|
||||
const currentTemplate = computed(() => {
|
||||
let modeName = ''
|
||||
if (mode && mode === 'embed') {
|
||||
|
|
@ -22,26 +38,57 @@ const currentTemplate = computed(() => {
|
|||
} else {
|
||||
modeName = show_history.value || !user.isEnterprise() ? 'pc' : 'base'
|
||||
}
|
||||
|
||||
const name = `/src/views/chat/${modeName}/index.vue`
|
||||
return components[name].default
|
||||
})
|
||||
/**
|
||||
* 是否显示对话
|
||||
*/
|
||||
const chat_show = computed(() => {
|
||||
if (init_data_end.value) {
|
||||
if (!applicationAvailable.value) {
|
||||
return true
|
||||
}
|
||||
if (application_profile.value) {
|
||||
if (application_profile.value.authentication && is_auth.value) {
|
||||
return true
|
||||
} else if (!application_profile.value.authentication) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
const loading = ref(false)
|
||||
|
||||
const show_history = ref(false)
|
||||
|
||||
const application_profile = ref<any>({})
|
||||
/**
|
||||
|
||||
* 初始化结束
|
||||
*/
|
||||
const init_data_end = ref<boolean>(false)
|
||||
|
||||
const applicationAvailable = ref<boolean>(true)
|
||||
function getAppProfile() {
|
||||
application.asyncGetAppProfile(loading).then((res: any) => {
|
||||
return application.asyncGetAppProfile(loading).then((res: any) => {
|
||||
show_history.value = res.data?.show_history
|
||||
application_profile.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
user.asyncGetProfile().then(() => {
|
||||
if (user.isEnterprise()) {
|
||||
getAppProfile()
|
||||
}
|
||||
function getAccessToken(token: string) {
|
||||
return application.asyncAppAuthentication(token, loading).then(() => {
|
||||
getAppProfile()
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
Promise.all([user.changeUserType(2), getAccessToken(accessToken)])
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
.finally(() => (init_data_end.value = true))
|
||||
user.asyncGetProfile()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
|
|
|||
|
|
@ -1,30 +1,5 @@
|
|||
<template>
|
||||
<div class="chat-pc layout-bg" :class="classObj" v-loading="loading">
|
||||
<el-dialog
|
||||
v-model="isPasswordDialogVisible"
|
||||
width="480"
|
||||
title="请输入密码打开链接"
|
||||
custom-class="no-close-button"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
align-center
|
||||
center
|
||||
:modal="true"
|
||||
>
|
||||
<el-form ref="FormRef" :model="form" :rules="rules" v-loading="validateLoading">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
:placeholder="$t('login.ldap.passwordPlaceholder')"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-button class="w-full mt-8" type="primary" @click="submitHandle(FormRef)"
|
||||
>确定</el-button
|
||||
>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
<div class="chat-pc__header" :class="!isDefaultTheme ? 'custom-header' : ''">
|
||||
<div class="flex align-center">
|
||||
<div class="mr-12 ml-24 flex">
|
||||
|
|
@ -47,7 +22,7 @@
|
|||
<h4>{{ applicationDetail?.name }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isAuthenticated">
|
||||
<div>
|
||||
<div class="flex">
|
||||
<div class="chat-pc__left border-r">
|
||||
<div class="p-24 pb-0">
|
||||
|
|
@ -149,65 +124,22 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, nextTick, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, onMounted, nextTick, computed } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import { saveAs } from 'file-saver'
|
||||
import { isAppIcon } from '@/utils/application'
|
||||
import useStore from '@/stores'
|
||||
import useResize from '@/layout/hooks/useResize'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { t } from '@/locales'
|
||||
|
||||
useResize()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const {
|
||||
params: { accessToken }
|
||||
} = route as any
|
||||
|
||||
const { application, user, log, common } = useStore()
|
||||
const { user, log, common } = useStore()
|
||||
|
||||
const isDefaultTheme = computed(() => {
|
||||
return user.isDefaultTheme()
|
||||
})
|
||||
|
||||
const FormRef = ref()
|
||||
|
||||
const isCollapse = ref(false)
|
||||
const isPasswordDialogVisible = ref(false)
|
||||
const validateLoading = ref(false)
|
||||
|
||||
const form = ref({
|
||||
password: ''
|
||||
})
|
||||
|
||||
const isAuthenticated = ref(false)
|
||||
|
||||
const validateName = (rule: any, value: string, callback: any) => {
|
||||
if (value === '') {
|
||||
callback(new Error('密码不能为空'))
|
||||
} else {
|
||||
application
|
||||
.validatePassword(applicationDetail?.value.id, form.value.password, validateLoading)
|
||||
.then((res: any) => {
|
||||
if (res?.data.is_valid) {
|
||||
isAuthenticated.value = true
|
||||
isPasswordDialogVisible.value = false
|
||||
} else {
|
||||
callback(new Error('密码错误'))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const rules = reactive({
|
||||
password: [{ required: true, validator: validateName, trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const submitHandle = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid) => {})
|
||||
}
|
||||
|
||||
const classObj = computed(() => {
|
||||
return {
|
||||
|
|
@ -221,12 +153,21 @@ const newObj = {
|
|||
id: 'new',
|
||||
abstract: '新建对话'
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
application_profile: any
|
||||
applicationAvailable: boolean
|
||||
}>()
|
||||
const AiChatRef = ref()
|
||||
const loading = ref(false)
|
||||
const left_loading = ref(false)
|
||||
const applicationDetail = ref<any>({})
|
||||
const applicationAvailable = ref<boolean>(true)
|
||||
|
||||
const applicationDetail = computed({
|
||||
get: () => {
|
||||
return props.application_profile
|
||||
},
|
||||
set: (v) => {}
|
||||
})
|
||||
|
||||
const chatLogeData = ref<any[]>([])
|
||||
|
||||
const paginationConfig = ref({
|
||||
|
|
@ -270,37 +211,6 @@ function handleScroll(event: any) {
|
|||
}
|
||||
}
|
||||
|
||||
function getAccessToken(token: string) {
|
||||
application
|
||||
.asyncAppAuthentication(token, loading)
|
||||
.then(() => {
|
||||
getAppProfile()
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function getAppProfile() {
|
||||
application
|
||||
.asyncGetAppProfile(loading)
|
||||
.then((res: any) => {
|
||||
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()) {
|
||||
getChatLog(applicationDetail.value.id)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
applicationAvailable.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function newChat() {
|
||||
if (!chatLogeData.value.some((v) => v.id === 'new')) {
|
||||
paginationConfig.value.current_page = 1
|
||||
|
|
@ -403,9 +313,19 @@ async function exportHTML(): Promise<void> {
|
|||
saveAs(blob, suggestedName)
|
||||
}
|
||||
|
||||
/**
|
||||
*初始化历史对话记录
|
||||
*/
|
||||
const init = () => {
|
||||
if (
|
||||
(applicationDetail.value.show_history || !user.isEnterprise()) &&
|
||||
props.applicationAvailable
|
||||
) {
|
||||
getChatLog(applicationDetail.value.id)
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
user.changeUserType(2)
|
||||
getAccessToken(accessToken)
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
|
|
|||
Loading…
Reference in New Issue