dvc890's picture
[feat]add project files
41af422
<script lang="ts" setup>
import { h, onMounted, reactive, ref } from 'vue'
import { NButton, NDataTable, NInput, NModal, NSelect, NSpace, NSwitch, NTag, useDialog, useMessage } from 'naive-ui'
import type { CHATMODEL } from './model'
import { KeyConfig, Status, UserRole, apiModelOptions, userRoleOptions } from './model'
import { fetchGetKeys, fetchUpdateApiKeyStatus, fetchUpsertApiKey } from '@/api'
import { t } from '@/locales'
import { useAuthStore } from '@/store'
import { useBasicLayout } from '@/hooks/useBasicLayout'
const ms = useMessage()
const dialog = useDialog()
const authStore = useAuthStore()
const { isMobile } = useBasicLayout()
const loading = ref(false)
const show = ref(false)
const handleSaving = ref(false)
const keyConfig = ref(new KeyConfig('', 'ChatGPTAPI', [], [], ''))
const keys = ref([])
const columns = [
{
title: 'Key',
key: 'key',
resizable: true,
width: 200,
minWidth: 100,
maxWidth: 200,
ellipsis: true,
},
{
title: 'Api Model',
key: 'keyModel',
width: 190,
},
{
title: 'Chat Model',
key: 'chatModels',
width: 320,
render(row: any) {
const tags = row.chatModels.map((chatModel: CHATMODEL) => {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'info',
bordered: false,
},
{
default: () => chatModel,
},
)
})
return tags
},
},
{
title: 'User Roles',
key: 'userRoles',
width: 200,
render(row: any) {
const tags = row.userRoles.map((userRole: UserRole) => {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'info',
bordered: false,
},
{
default: () => UserRole[userRole],
},
)
})
return tags
},
},
{
title: 'Remark',
key: 'remark',
width: 220,
},
{
title: 'Action',
key: '_id',
width: 220,
render(row: KeyConfig) {
const actions: any[] = []
if (row.status === Status.Normal) {
actions.push(h(
NButton,
{
size: 'small',
style: {
marginRight: '6px',
},
type: 'info',
onClick: () => handleEditKey(row),
},
{ default: () => t('chat.editKeyButton') },
))
actions.push(h(
NButton,
{
size: 'small',
style: {
marginRight: '6px',
},
type: 'error',
onClick: () => handleUpdateApiKeyStatus(row._id as string, Status.Disabled),
},
{ default: () => t('chat.deleteKey') },
))
}
return actions
},
},
]
const pagination = reactive({
page: 1,
pageSize: 100,
pageCount: 1,
itemCount: 1,
prefix({ itemCount }: { itemCount: number | undefined }) {
return `Total is ${itemCount}.`
},
showSizePicker: true,
pageSizes: [100],
onChange: (page: number) => {
pagination.page = page
handleGetKeys(pagination.page)
},
onUpdatePageSize: (pageSize: number) => {
pagination.pageSize = pageSize
pagination.page = 1
handleGetKeys(pagination.page)
},
})
async function handleGetKeys(page: number) {
if (loading.value)
return
keys.value.length = 0
loading.value = true
const size = pagination.pageSize
const data = (await fetchGetKeys(page, size)).data
data.keys.forEach((key: never) => {
keys.value.push(key)
})
keyConfig.value = keys.value[0]
pagination.page = page
pagination.pageCount = data.total / size + (data.total % size === 0 ? 0 : 1)
pagination.itemCount = data.total
loading.value = false
}
async function handleUpdateApiKeyStatus(id: string, status: Status) {
dialog.warning({
title: t('chat.deleteKey'),
content: t('chat.deleteKeyConfirm'),
positiveText: t('common.yes'),
negativeText: t('common.no'),
onPositiveClick: async () => {
await fetchUpdateApiKeyStatus(id, status)
ms.info('OK')
await handleGetKeys(pagination.page)
},
})
}
async function handleUpdateKeyConfig() {
if (!keyConfig.value.key) {
ms.error('Api key is required')
return
}
handleSaving.value = true
try {
await fetchUpsertApiKey(keyConfig.value)
await handleGetKeys(pagination.page)
show.value = false
}
catch (error: any) {
ms.error(error.message)
}
handleSaving.value = false
}
function handleNewKey() {
keyConfig.value = new KeyConfig('', 'ChatGPTAPI', [], [], '')
show.value = true
}
function handleEditKey(key: KeyConfig) {
keyConfig.value = key
show.value = true
}
onMounted(async () => {
await handleGetKeys(pagination.page)
})
</script>
<template>
<div class="p-4 space-y-5 min-h-[300px]">
<div class="space-y-6">
<NSpace vertical :size="12">
<NSpace>
<NButton @click="handleNewKey()">
New Key
</NButton>
</NSpace>
<NDataTable
ref="table"
remote
:loading="loading"
:row-key="(rowData) => rowData._id"
:columns="columns"
:data="keys"
:pagination="pagination"
:max-height="444"
:scroll-x="1300"
striped @update:page="handleGetKeys"
/>
</NSpace>
</div>
</div>
<NModal v-model:show="show" :auto-focus="false" preset="card" :style="{ width: !isMobile ? '50%' : '100%' }">
<div class="p-4 space-y-5 min-h-[200px]">
<div class="space-y-6">
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.apiModel') }}</span>
<div class="flex-1">
<NSelect
style="width: 100%"
:value="keyConfig.keyModel"
:options="apiModelOptions"
@update-value="value => keyConfig.keyModel = value"
/>
</div>
<p v-if="!isMobile">
<a v-if="keyConfig.keyModel === 'ChatGPTAPI'" target="_blank" href="https://platform.openai.com/account/api-keys">Get Api Key</a>
<a v-else target="_blank" href="https://chat.openai.com/api/auth/session">Get Access Token</a>
</p>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.api') }}</span>
<div class="flex-1">
<NInput
v-model:value="keyConfig.key" type="textarea"
:autosize="{ minRows: 3, maxRows: 4 }" placeholder=""
/>
</div>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.chatModels') }}</span>
<div class="flex-1">
<NSelect
style="width: 100%"
multiple
:value="keyConfig.chatModels"
:options="authStore.session?.allChatModels"
@update-value="value => keyConfig.chatModels = value"
/>
</div>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.userRoles') }}</span>
<div class="flex-1">
<NSelect
style="width: 100%"
multiple
:value="keyConfig.userRoles"
:options="userRoleOptions"
@update-value="value => keyConfig.userRoles = value"
/>
</div>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.status') }}</span>
<div class="flex-1">
<NSwitch
:round="false"
:value="keyConfig.status === Status.Normal"
@update:value="(val) => { keyConfig.status = val ? Status.Normal : Status.Disabled }"
/>
</div>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.remark') }}</span>
<div class="flex-1">
<NInput
v-model:value="keyConfig.remark" type="textarea"
:autosize="{ minRows: 1, maxRows: 2 }" placeholder=""
/>
</div>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]" />
<NButton type="primary" :loading="handleSaving" @click="handleUpdateKeyConfig()">
{{ $t('common.save') }}
</NButton>
</div>
</div>
</div>
</NModal>
</template>