feat(webui): WebUI 管理后台新增 AII 管理栏目
在 WebUI NyaHome 管理后台中实现 AII 管理栏目,用于在线修改模型设置。 同时在后端补全了两个路由端点。
This commit is contained in:
Vendored
+8
-4
@@ -12,8 +12,10 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AiiModelAddModal: typeof import('./src/components/chatroom/AiiModelAddModal.vue')['default']
|
||||
AiiProviderAddModal: typeof import('./src/components/chatroom/AiiProviderAddModal.vue')['default']
|
||||
AiiModelAddModal: typeof import('./src/components/aii/AiiModelAddModal.vue')['default']
|
||||
AiiModelEditModal: typeof import('./src/components/aii/AiiModelEditModal.vue')['default']
|
||||
AiiProviderAddModal: typeof import('./src/components/aii/AiiProviderAddModal.vue')['default']
|
||||
AiiProviderEditModal: typeof import('./src/components/aii/AiiProviderEditModal.vue')['default']
|
||||
ChangeEmailModal: typeof import('./src/components/admin/ChangeEmailModal.vue')['default']
|
||||
ChangePhoneModal: typeof import('./src/components/admin/ChangePhoneModal.vue')['default']
|
||||
ChatControlPanel: typeof import('./src/components/chatroom/ChatControlPanel.vue')['default']
|
||||
@@ -88,8 +90,10 @@ declare module 'vue' {
|
||||
|
||||
// For TSX support
|
||||
declare global {
|
||||
const AiiModelAddModal: typeof import('./src/components/chatroom/AiiModelAddModal.vue')['default']
|
||||
const AiiProviderAddModal: typeof import('./src/components/chatroom/AiiProviderAddModal.vue')['default']
|
||||
const AiiModelAddModal: typeof import('./src/components/aii/AiiModelAddModal.vue')['default']
|
||||
const AiiModelEditModal: typeof import('./src/components/aii/AiiModelEditModal.vue')['default']
|
||||
const AiiProviderAddModal: typeof import('./src/components/aii/AiiProviderAddModal.vue')['default']
|
||||
const AiiProviderEditModal: typeof import('./src/components/aii/AiiProviderEditModal.vue')['default']
|
||||
const ChangeEmailModal: typeof import('./src/components/admin/ChangeEmailModal.vue')['default']
|
||||
const ChangePhoneModal: typeof import('./src/components/admin/ChangePhoneModal.vue')['default']
|
||||
const ChatControlPanel: typeof import('./src/components/chatroom/ChatControlPanel.vue')['default']
|
||||
|
||||
+54
-38
@@ -2,15 +2,21 @@
|
||||
import { type SelectOption, useMessage } from 'naive-ui'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import AiiProviderAddModal from '@/components/chatroom/AiiProviderAddModal.vue'
|
||||
import AiiProviderAddModal from '@/components/aii/AiiProviderAddModal.vue'
|
||||
import { aiiModelRules, check_remote_model } from '@/tools/avaliable-check.ts'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { AiiProviderPublicWithoutKey } from '@/types/aii.js'
|
||||
import type { AiiModelPublic, AiiProviderPublicWithoutKey } from '@/types/aii.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
const showModal = defineModel<boolean>('showModal', { required: true })
|
||||
|
||||
const { reload } = defineProps<{
|
||||
noAddProvider?: boolean
|
||||
reload?: () => void
|
||||
}>()
|
||||
|
||||
const showAddProviderModal = ref(false)
|
||||
const selectProvider = ref<number | null>(null)
|
||||
const providers = ref<AiiProviderPublicWithoutKey[]>([])
|
||||
@@ -20,6 +26,7 @@ const addModelForm = ref({
|
||||
id: 0,
|
||||
model_name: '',
|
||||
max_context_length: 0,
|
||||
reasonable: false,
|
||||
aii_provider_id: selectProvider.value,
|
||||
})
|
||||
|
||||
@@ -30,14 +37,7 @@ watch(selectProvider, (newValue) => {
|
||||
function loadProviders() {
|
||||
api
|
||||
.get('/aii/provider/')
|
||||
.then((res) => res.data as ReturnDto)
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
return data.result as AiiProviderPublicWithoutKey[]
|
||||
} else {
|
||||
throw TypeError('因未知原因,后端业务失败。')
|
||||
}
|
||||
})
|
||||
.then((res) => res.data as AiiProviderPublicWithoutKey[])
|
||||
.then((result) => {
|
||||
providers.value = result
|
||||
MESSAGE.success(`成功加载了 ${result.length} 个模型提供商。`)
|
||||
@@ -82,33 +82,26 @@ function onGetRemoteModels() {
|
||||
})
|
||||
}
|
||||
|
||||
function onCheck() {
|
||||
api
|
||||
.get(`/aii/provider/${selectProvider.value}/remote/model/${addModelForm.value.model_name}/`)
|
||||
.then((res) => res.data as ReturnDto)
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
MESSAGE.success(`检测成功,模型 ${addModelForm.value.model_name} 可用。`)
|
||||
} else {
|
||||
MESSAGE.warning(`检测完成,模型 ${addModelForm.value.model_name} 不可用。`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
MESSAGE.error(`检测过程出现问题:${err}`)
|
||||
})
|
||||
async function onCheck() {
|
||||
if (selectProvider.value) {
|
||||
if (await check_remote_model(selectProvider.value, addModelForm.value.model_name)) {
|
||||
MESSAGE.success(`提供商的模型 ${addModelForm.value.model_name} 可用。`)
|
||||
} else {
|
||||
MESSAGE.warning(`提供商的模型 ${addModelForm.value.model_name} 不可用。`)
|
||||
}
|
||||
} else {
|
||||
MESSAGE.warning('请选择模型提供商。')
|
||||
}
|
||||
}
|
||||
|
||||
function onConfirm() {
|
||||
api
|
||||
.post('/aii/model/', JSON.stringify(addModelForm.value))
|
||||
.then((res) => res.data as ReturnDto)
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
MESSAGE.success(`模型 ${addModelForm.value.model_name} 成功添加。`)
|
||||
showModal.value = false
|
||||
} else {
|
||||
throw TypeError('因未知原因,后端业务失败。')
|
||||
}
|
||||
.then((res) => res.data as AiiModelPublic)
|
||||
.then(() => {
|
||||
MESSAGE.success(`模型 ${addModelForm.value.model_name} 成功添加。`)
|
||||
showModal.value = false
|
||||
if (reload) reload()
|
||||
})
|
||||
.catch((err) => {
|
||||
MESSAGE.error(`添加模型失败:${err}`)
|
||||
@@ -118,16 +111,36 @@ function onConfirm() {
|
||||
|
||||
<template>
|
||||
<n-modal v-model:show="showModal" preset="card" title="添加模型">
|
||||
<n-form :model="addModelForm" label-placement="left" label-width="auto" label-align="right">
|
||||
<n-form
|
||||
:model="addModelForm"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
label-align="right"
|
||||
:rules="aiiModelRules"
|
||||
>
|
||||
<n-form-item label="模型提供商" path="aii_provider_id">
|
||||
<n-flex style="width: 100%" justify="right" align="center">
|
||||
<n-select v-model:value="selectProvider" :options="providerOptions" />
|
||||
<n-tag round type="info">修改已添加的提供商?请前往管理中心</n-tag>
|
||||
<n-button secondary type="success" size="small" round @click="loadProviders()"
|
||||
>刷新
|
||||
<n-tag round type="info" v-if="!noAddProvider">修改已添加的提供商?请前往管理中心</n-tag>
|
||||
<n-button
|
||||
secondary
|
||||
type="success"
|
||||
size="small"
|
||||
round
|
||||
@click="loadProviders()"
|
||||
v-if="!noAddProvider"
|
||||
>
|
||||
刷新
|
||||
</n-button>
|
||||
<n-button secondary type="warning" size="small" round @click="showAddProviderModal = true"
|
||||
>添加
|
||||
<n-button
|
||||
secondary
|
||||
type="warning"
|
||||
size="small"
|
||||
round
|
||||
@click="showAddProviderModal = true"
|
||||
v-if="!noAddProvider"
|
||||
>
|
||||
添加
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
@@ -156,6 +169,9 @@ function onConfirm() {
|
||||
<template #suffix>K</template>
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
<n-form-item label="支持思考">
|
||||
<n-switch v-model:value="addModelForm.reasonable" />
|
||||
</n-form-item>
|
||||
<n-form-item label="添加完成">
|
||||
<n-flex>
|
||||
<n-button secondary type="info" @click="onCheck()">检测</n-button>
|
||||
@@ -0,0 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { aiiModelRules, check_remote_model } from '@/tools/avaliable-check.ts'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { AiiModelPublic } from '@/types/aii.ts'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
const showModal = defineModel('showModal', { required: true })
|
||||
|
||||
const { model } = defineProps<{
|
||||
model: AiiModelPublic
|
||||
reload: () => void
|
||||
}>()
|
||||
|
||||
const provider = computed(() => {
|
||||
return `[${model.provider_id}] ${model.provider_name}`
|
||||
})
|
||||
|
||||
async function onCheck() {
|
||||
if (await check_remote_model(model.provider_id, model.model_name)) {
|
||||
MESSAGE.success(`提供商的模型 ${model.model_name} 可用。`)
|
||||
} else {
|
||||
MESSAGE.warning(`提供商的模型 ${model.model_name} 不可用。`)
|
||||
}
|
||||
}
|
||||
|
||||
function onSave() {
|
||||
api
|
||||
.post(
|
||||
`/aii/model/${model.id}`,
|
||||
JSON.stringify({
|
||||
model_name: model.model_name,
|
||||
max_context_length: model.max_context_length,
|
||||
reasonable: model.reasonable,
|
||||
aii_provider_id: model.provider_id,
|
||||
}),
|
||||
)
|
||||
.then((res) => res.data as AiiModelPublic)
|
||||
.then((data) => {
|
||||
MESSAGE.success(
|
||||
`提供商 [${model.provider_id}] ${model.provider_name} 的模型 ${data.model_name} 已更新。`,
|
||||
)
|
||||
showModal.value = false
|
||||
})
|
||||
.catch((err) => {
|
||||
MESSAGE.error(`更新模型失败:${err}`)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal v-model:show="showModal" preset="card" title="修改模型">
|
||||
<n-alert type="warning" class="in-form-alert">
|
||||
不支持更换 API Key。如果需要更换 Key,请移除并重新添加模型提供商与模型。
|
||||
</n-alert>
|
||||
<n-form
|
||||
label-width="auto"
|
||||
label-align="right"
|
||||
label-placement="left"
|
||||
:model="model"
|
||||
:rules="aiiModelRules"
|
||||
>
|
||||
<n-form-item label="模型提供商">
|
||||
<n-input v-model:value="provider" readonly />
|
||||
</n-form-item>
|
||||
<n-form-item label="模型名称" path="model_name">
|
||||
<n-input v-model:value="model.model_name" />
|
||||
</n-form-item>
|
||||
<n-form-item label="最大上下文长度" path="max_context_length">
|
||||
<n-input-number v-model:value="model.max_context_length">
|
||||
<template #suffix>k</template>
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
<n-form-item label="支持思考" path="reasonable">
|
||||
<n-switch v-model:value="model.reasonable" />
|
||||
</n-form-item>
|
||||
<n-form-item label="操作">
|
||||
<n-flex>
|
||||
<n-button type="info" secondary @click="onCheck()">检测</n-button>
|
||||
<n-button type="primary" secondary @click="onSave()">确认</n-button>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
+20
-26
@@ -2,13 +2,18 @@
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { check_remote_provider } from '@/tools/avaliable-check.ts'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
import type { AiiProviderPublicWithoutKey } from '@/types/aii.ts'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
const showModal = defineModel('showModal', { required: true })
|
||||
|
||||
const { reload } = defineProps<{
|
||||
reload?: () => void
|
||||
}>()
|
||||
|
||||
const addProviderForm = ref({
|
||||
id: 0,
|
||||
name: '',
|
||||
@@ -16,33 +21,22 @@ const addProviderForm = ref({
|
||||
api_key: '',
|
||||
})
|
||||
|
||||
function onCheck() {
|
||||
api
|
||||
.post('/aii/remote/provider/check/', JSON.stringify(addProviderForm.value))
|
||||
.then((res) => res.data as ReturnDto)
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
MESSAGE.success(`模型提供商检测成功,探测到 ${data.result} 个可用模型。`)
|
||||
} else {
|
||||
MESSAGE.warning('模型提供商检测失败,请确认 Base URI 与 API key 是否正确。')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
MESSAGE.error(`检测模型提供商时遇到未知的异常,请检查后端业务:${err}`)
|
||||
})
|
||||
async function onCheck() {
|
||||
if (await check_remote_provider(addProviderForm.value)) {
|
||||
MESSAGE.success(`检查模型提供商 ${addProviderForm.value.name} 可用性成功。`)
|
||||
} else {
|
||||
MESSAGE.success(`检查模型提供商 ${addProviderForm.value.name} 可用性失败?`)
|
||||
}
|
||||
}
|
||||
|
||||
function onConfirm() {
|
||||
api
|
||||
.post('/aii/provider/', JSON.stringify(addProviderForm.value))
|
||||
.then((res) => res.data as ReturnDto)
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
MESSAGE.success(`已添加模型提供商 ${addProviderForm.value.name} 。`)
|
||||
showModal.value = false
|
||||
} else {
|
||||
throw TypeError('后端业务表示添加模型提供商失败,但未提供原因。')
|
||||
}
|
||||
.then((res) => res.data as AiiProviderPublicWithoutKey)
|
||||
.then(() => {
|
||||
MESSAGE.success(`已添加模型提供商 ${addProviderForm.value.name} 。`)
|
||||
showModal.value = false
|
||||
if (reload) reload()
|
||||
})
|
||||
.catch((err) => {
|
||||
MESSAGE.error(`添加模型提供商失败:${err}`)
|
||||
@@ -53,13 +47,13 @@ function onConfirm() {
|
||||
<template>
|
||||
<n-modal v-model:show="showModal" preset="card" title="添加模型提供商">
|
||||
<n-form :model="addProviderForm" label-placement="left" label-width="auto" label-align="right">
|
||||
<n-form-item label="名称" path="name">
|
||||
<n-form-item label="名称" path="name" :rule="{ required: true, trigger: 'blur' }">
|
||||
<n-input v-model:value="addProviderForm.name" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Base URL" path="base_url">
|
||||
<n-form-item label="Base URL" path="base_url" :rule="{ required: true, trigger: 'blur' }">
|
||||
<n-input v-model:value="addProviderForm.base_url" />
|
||||
</n-form-item>
|
||||
<n-form-item label="API Key" path="api_key">
|
||||
<n-form-item label="API Key" path="api_key" :rule="{ required: true, trigger: 'blur' }">
|
||||
<n-input v-model:value="addProviderForm.api_key" />
|
||||
</n-form-item>
|
||||
<n-form-item label="添加完成">
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import {useMessage} from 'naive-ui'
|
||||
|
||||
import {api} from '@/tools/web.ts'
|
||||
import type {AiiProviderPublicWithoutKey} from '@/types/aii.ts'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
const showModal = defineModel('showModal', { required: true })
|
||||
|
||||
const { provider, reload } = defineProps<{
|
||||
provider: AiiProviderPublicWithoutKey
|
||||
reload: () => void
|
||||
}>()
|
||||
|
||||
function onSave() {
|
||||
api.post(`/aii/provider/${provider.id}/`, JSON.stringify(provider)).then(() => {
|
||||
MESSAGE.success(`模型提供商 [${provider.id}]${provider.name} 成功保存~`)
|
||||
showModal.value = false
|
||||
reload()
|
||||
}).catch((err) => {
|
||||
MESSAGE.error(`修改提供商信息失败:${err}`)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal v-model:show="showModal" preset="card" title="修改模型提供商">
|
||||
<n-alert type="warning" class="in-form-alert">
|
||||
不支持更换 API Key。如果需要更换 Key,请移除并重新添加模型提供商与模型。
|
||||
</n-alert>
|
||||
<n-form label-placement="left" label-align="right" label-width="auto" :model="provider">
|
||||
<n-form-item label="提供商名称" path="name" :rule="{ required: true, trigger: 'blur' }">
|
||||
<n-input v-model:value="provider.name" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Base URL" path="base_url" :rule="{ required: true, trigger: 'blur' }">
|
||||
<n-input v-model:value="provider.base_url" />
|
||||
</n-form-item>
|
||||
<n-form-item label="操作">
|
||||
<n-button secondary type="primary" @click="onSave()">确认</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,8 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { type SelectOption, useMessage } from 'naive-ui'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { NTag, type SelectOption, useMessage } from 'naive-ui'
|
||||
import { computed, h, onMounted, ref, type VNode, watch } from 'vue'
|
||||
|
||||
import AiiModelAddModal from '@/components/chatroom/AiiModelAddModal.vue'
|
||||
import ChatPromptQuicker from '@/components/chatroom/ChatPromptQuicker.vue'
|
||||
import ChatroomEditorModal from '@/components/chatroom/ChatroomEditorModal.vue'
|
||||
import ScriptDrawer from '@/components/chatroom/ScriptDrawer.vue'
|
||||
@@ -10,12 +9,14 @@ import { useNowUser } from '@/stores/now-user.ts'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { AiiModelPublic } from '@/types/aii.js'
|
||||
import type { Chatroom, ChatroomPublic } from '@/types/chatroom.ts'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
|
||||
import AiiModelAddModal from '../aii/AiiModelAddModal.vue'
|
||||
|
||||
const NOWUSER = useNowUser()
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
const selectedModel = defineModel<number | null>('selectModel', { required: true })
|
||||
const selectedModelId = defineModel<number | null>('selectModelId', { required: true })
|
||||
const selectedModel = defineModel<AiiModelPublic | null>('selectModel', { required: true })
|
||||
const quickerPrompt = defineModel<string>('quickerPrompt', { required: true })
|
||||
|
||||
const { chatroom, loadPage } = defineProps<{
|
||||
@@ -32,23 +33,34 @@ const modelOptions = computed(() => {
|
||||
for (const model of models.value) {
|
||||
options.push({
|
||||
value: model.id,
|
||||
label: `[${model.provider_name}] ${model.model_name}`,
|
||||
label: model.model_name,
|
||||
provider: model.provider_name,
|
||||
reasonable: model.reasonable ? '思考' : '非思考',
|
||||
})
|
||||
}
|
||||
return options
|
||||
})
|
||||
|
||||
// 在选中的模型 ID 以及请求得到的模型列表出现变化时,重新确定当前选中模型。
|
||||
// 此处选中的模型会同步到上级组件 Chatroom1Page,然后同步给 ChatTable。
|
||||
// 从而,ChatTable 能够提供思考开关以及更多设置。
|
||||
watch(
|
||||
[selectedModelId, models],
|
||||
(newVal) => {
|
||||
if (newVal[0]) {
|
||||
const newModel = models.value.find((v) => v.id === newVal[0])
|
||||
if (newModel) {
|
||||
selectedModel.value = newModel
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function loadModels() {
|
||||
api
|
||||
.get('/aii/model')
|
||||
.then((res) => res.data as ReturnDto)
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
models.value = data.result as AiiModelPublic[]
|
||||
} else {
|
||||
throw TypeError('获取模型列表失败……')
|
||||
}
|
||||
})
|
||||
.get('/aii/model/')
|
||||
.then((res) => (models.value = res.data as AiiModelPublic[]))
|
||||
.catch((err) => {
|
||||
MESSAGE.error(`加载模型列表失败:${err}`)
|
||||
})
|
||||
@@ -57,7 +69,7 @@ function loadModels() {
|
||||
onMounted(() => {
|
||||
loadModels()
|
||||
if (chatroom.default_model_id) {
|
||||
selectedModel.value = chatroom.default_model_id
|
||||
selectedModelId.value = chatroom.default_model_id
|
||||
} else {
|
||||
MESSAGE.info(
|
||||
'此聊天室还未设置默认模型。你需要选择一个模型然后开始聊天,或者现在就保存一个默认模型嘛?',
|
||||
@@ -77,8 +89,8 @@ const chatroomInfo = ref<ChatroomPublic>({
|
||||
})
|
||||
|
||||
function saveDefaultModel() {
|
||||
if (selectedModel.value) {
|
||||
chatroomInfo.value.default_model_id = selectedModel.value
|
||||
if (selectedModelId.value) {
|
||||
chatroomInfo.value.default_model_id = selectedModelId.value
|
||||
api
|
||||
.post(`/chatroom/${chatroom.id}/`, JSON.stringify(chatroomInfo.value))
|
||||
.then(() => {
|
||||
@@ -91,6 +103,41 @@ function saveDefaultModel() {
|
||||
MESSAGE.warning('请先选择一个模型哦~')
|
||||
}
|
||||
}
|
||||
|
||||
function renderLabel(option: SelectOption): VNode {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: '5px',
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
NTag,
|
||||
{
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
round: true,
|
||||
},
|
||||
option.provider as string,
|
||||
),
|
||||
option.label as string,
|
||||
h(
|
||||
NTag,
|
||||
{
|
||||
type: 'info',
|
||||
size: 'small',
|
||||
round: true,
|
||||
},
|
||||
option.reasonable as string,
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -99,16 +146,6 @@ function saveDefaultModel() {
|
||||
<template #header-extra>
|
||||
<n-flex>
|
||||
<n-button secondary type="info" size="small" round @click="loadModels()">刷新</n-button>
|
||||
<n-button
|
||||
v-if="NOWUSER.is_admin"
|
||||
secondary
|
||||
type="warning"
|
||||
size="small"
|
||||
round
|
||||
@click="showAddModelModal = true"
|
||||
>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button-group>
|
||||
<n-button secondary type="primary" size="small" round @click="saveDefaultModel()">
|
||||
保存
|
||||
@@ -127,7 +164,23 @@ function saveDefaultModel() {
|
||||
</n-button-group>
|
||||
</n-flex>
|
||||
</template>
|
||||
<n-select v-model:value="selectedModel" :options="modelOptions" />
|
||||
<n-select v-model:value="selectedModelId" :options="modelOptions" :render-label="renderLabel">
|
||||
<template #action>
|
||||
<n-flex>
|
||||
<n-button
|
||||
v-if="NOWUSER.is_admin"
|
||||
secondary
|
||||
type="warning"
|
||||
size="small"
|
||||
round
|
||||
@click="showAddModelModal = true"
|
||||
>
|
||||
添加
|
||||
</n-button>
|
||||
<n-tag type="info" round v-if="NOWUSER.is_admin">前往管理后端修改已有模型</n-tag>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-select>
|
||||
<aii-model-add-modal v-model:show-modal="showAddModelModal" />
|
||||
</n-card>
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { createChatTableMessages } from '@/components/chatroom/chat-table-messages.js'
|
||||
import { md } from '@/tools/md.js'
|
||||
import type { AiiModelPublic } from '@/types/aii.ts'
|
||||
|
||||
defineProps<{
|
||||
content: string | null
|
||||
aiiThinking: string
|
||||
aiiMessage: string | null
|
||||
aiiTokenInfo: string
|
||||
model: AiiModelPublic | null
|
||||
onSendMessage: () => void
|
||||
onAccept: () => void
|
||||
onRewrite: () => void
|
||||
@@ -35,8 +37,13 @@ const mode = defineModel<'continue' | 'expand'>('mode', { required: true })
|
||||
|
||||
<div v-if="aiiMessage === null" class="editor">
|
||||
<n-input v-model:value="message" type="textarea" :resizable="false" />
|
||||
<n-flex justify="right" align="center">
|
||||
<n-flex justify="right" align="center" size="small" v-if="model">
|
||||
<n-button type="tertiary" size="small" circle>!</n-button>
|
||||
<n-switch size="large" v-if="model.reasonable">
|
||||
<template #checked>开启思考</template>
|
||||
<template #unchecked>关闭思考</template>
|
||||
<template #icon>💡</template>
|
||||
</n-switch>
|
||||
<n-switch
|
||||
v-model:value="mode"
|
||||
size="large"
|
||||
|
||||
@@ -3,8 +3,6 @@ import { useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import InputFile from '@/components/file/InputFile.vue'
|
||||
import SelectFileModal from '@/components/file/SelectFileModal.vue'
|
||||
import UploadFileModal from '@/components/file/UploadFileModal.vue'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { ChatroomPublic } from '@/types/chatroom.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
|
||||
@@ -9,7 +9,7 @@ import ChatControlPanel from '@/components/chatroom/ChatControlPanel.vue'
|
||||
import ChatroomCard from '@/components/chatroom/ChatroomCard.vue'
|
||||
import ChatTable from '@/components/chatroom/ChatTable.vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { AiiTokenInfo } from '@/types/aii.ts'
|
||||
import type { AiiModelPublic, AiiTokenInfo } from '@/types/aii.ts'
|
||||
import type { Chatroom } from '@/types/chatroom.ts'
|
||||
import type { ReturnDto } from '@/types/response.ts'
|
||||
import { SEE_YOU_TOMORROW } from '@/types/syt.ts'
|
||||
@@ -26,7 +26,8 @@ const MESSAGE = useMessage()
|
||||
|
||||
const chatroom = ref<Chatroom | null>(null)
|
||||
|
||||
const selectedModel = ref<number | null>(null)
|
||||
const selectedModelId = ref<number | null>(null)
|
||||
const selectedModel = ref<AiiModelPublic | null>(null)
|
||||
const quickerPrompt = ref('')
|
||||
const inputMessage = ref<string>('')
|
||||
const inputMode = ref<'continue' | 'expand'>('expand')
|
||||
@@ -67,7 +68,7 @@ watch(
|
||||
)
|
||||
|
||||
function chat() {
|
||||
if (!selectedModel.value) {
|
||||
if (!selectedModelId.value) {
|
||||
MESSAGE.warning('未选择模型,无法开始创作喵!')
|
||||
return
|
||||
}
|
||||
@@ -91,7 +92,7 @@ function chat() {
|
||||
message: inputMessage.value,
|
||||
prefix: quickerPrompt.value,
|
||||
mode: inputMode.value,
|
||||
model_id: selectedModel.value,
|
||||
model_id: selectedModelId.value,
|
||||
}),
|
||||
openWhenHidden: true, // 此开关控制在浏览器失去焦点时是否保持连接开启。默认为 false 会导致焦点转移时流式传输中断然后重连,很怪
|
||||
|
||||
@@ -249,6 +250,7 @@ function enableSidebar() {
|
||||
:aii-thinking
|
||||
:aii-message
|
||||
:aii-token-info
|
||||
:model="selectedModel"
|
||||
v-model:message="inputMessage"
|
||||
v-model:mode="inputMode"
|
||||
:on-send-message="chat"
|
||||
@@ -265,6 +267,7 @@ function enableSidebar() {
|
||||
:chatroom="chatroom"
|
||||
:load-page="load"
|
||||
v-model:quicker-prompt="quickerPrompt"
|
||||
v-model:select-model-id="selectedModelId"
|
||||
v-model:select-model="selectedModel"
|
||||
/>
|
||||
<div id="sidebar-toggle" @click="disableSidebar" />
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ref } from 'vue'
|
||||
|
||||
import ConfigCard from '@/components/admin/ConfigCard.vue'
|
||||
import InDev from '@/components/InDev.vue'
|
||||
import AdminAii from '@/pages/nyahome/AdminAii.vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { ReturnDto } from '@/types/response.ts'
|
||||
|
||||
@@ -101,6 +102,10 @@ function sendTestMail() {
|
||||
</config-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="aii" tab="AII" display-directive="show">
|
||||
<admin-aii />
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="site_info" tab="站点信息" display-directive="show">
|
||||
<n-flex vertical>
|
||||
<config-card title="基本信息">
|
||||
@@ -233,8 +238,4 @@ function sendTestMail() {
|
||||
<n-empty size="large" v-else description="请尝试手动获取设置..." />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.in-form-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
<script setup lang="ts">
|
||||
import { type DataTableColumns, NButton, NTag } from 'naive-ui'
|
||||
import { h, onMounted, ref } from 'vue'
|
||||
|
||||
import ConfigCard from '@/components/admin/ConfigCard.vue'
|
||||
import AiiModelAddModal from '@/components/aii/AiiModelAddModal.vue'
|
||||
import AiiModelEditModal from '@/components/aii/AiiModelEditModal.vue'
|
||||
import AiiProviderAddModal from '@/components/aii/AiiProviderAddModal.vue'
|
||||
import AiiProviderEditModal from '@/components/aii/AiiProviderEditModal.vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { AiiModelPublic, AiiProviderPublicWithoutKey } from '@/types/aii.ts'
|
||||
|
||||
const showProviderAddModal = ref(false)
|
||||
const showProviderEditModal = ref(false)
|
||||
const showModelAddModal = ref(false)
|
||||
const showModelEditModal = ref(false)
|
||||
|
||||
function createProviderColumns(): DataTableColumns<AiiProviderPublicWithoutKey> {
|
||||
return [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: 'error',
|
||||
round: true,
|
||||
},
|
||||
row.id,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '提供商名称',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Base URL',
|
||||
key: 'base_url',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render(row) {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
type: 'warning',
|
||||
secondary: true,
|
||||
round: true,
|
||||
onClick() {
|
||||
selectedProvider.value = row
|
||||
showProviderEditModal.value = true
|
||||
},
|
||||
},
|
||||
'修改',
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function createModelColumns(): DataTableColumns<AiiModelPublic> {
|
||||
return [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: 'primary',
|
||||
round: true,
|
||||
},
|
||||
row.id,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '模型名称',
|
||||
key: 'model_name',
|
||||
},
|
||||
{
|
||||
title: '最大上下文长度(k)',
|
||||
key: 'max_context_length',
|
||||
},
|
||||
{
|
||||
title: '支持思考',
|
||||
key: 'reasonable',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: 'info',
|
||||
round: true,
|
||||
},
|
||||
row.reasonable ? '思考' : '非思考',
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '所属提供商',
|
||||
key: 'provider_id',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: 'error',
|
||||
round: true,
|
||||
},
|
||||
row.provider_id,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render(row) {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
type: 'warning',
|
||||
secondary: true,
|
||||
round: true,
|
||||
onClick() {
|
||||
selectedModel.value = row
|
||||
showModelEditModal.value = true
|
||||
},
|
||||
},
|
||||
'修改',
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const providerColumns = createProviderColumns()
|
||||
const modelColumns = createModelColumns()
|
||||
const providers = ref<AiiProviderPublicWithoutKey[]>([])
|
||||
const models = ref<AiiModelPublic[]>([])
|
||||
const selectedModel = ref<AiiModelPublic | null>(null)
|
||||
const selectedProvider = ref<AiiProviderPublicWithoutKey | null>(null)
|
||||
|
||||
function load() {
|
||||
api
|
||||
.get('/aii/provider/')
|
||||
.then((res) => res.data as AiiProviderPublicWithoutKey[])
|
||||
.then((data) => (providers.value = data))
|
||||
api
|
||||
.get('/aii/model/')
|
||||
.then((res) => res.data as AiiModelPublic[])
|
||||
.then((data) => (models.value = data))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-flex vertical align="center">
|
||||
<n-card>
|
||||
<n-flex>
|
||||
<n-h4 style="margin: 0">刷新本页信息(如果你正在从其他地方修改)</n-h4>
|
||||
<n-button style="margin-left: auto" type="info" @click="load()">更新</n-button>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
|
||||
<config-card title="模型提供商">
|
||||
<template #extra>
|
||||
<n-button round type="info" @click="showProviderAddModal = true">添加</n-button>
|
||||
</template>
|
||||
<n-data-table :columns="providerColumns" :data="providers" />
|
||||
</config-card>
|
||||
|
||||
<config-card title="模型">
|
||||
<template #extra>
|
||||
<n-button round type="info" @click="showModelAddModal = true">添加</n-button>
|
||||
</template>
|
||||
<n-data-table :columns="modelColumns" :data="models" />
|
||||
</config-card>
|
||||
|
||||
<aii-provider-add-modal v-model:show-modal="showProviderAddModal" :reload="load" />
|
||||
<aii-model-add-modal v-model:show-modal="showModelAddModal" no-add-provider :reload="load" />
|
||||
<aii-provider-edit-modal
|
||||
:provider="selectedProvider"
|
||||
v-model:show-modal="showProviderEditModal"
|
||||
:reload="load"
|
||||
/>
|
||||
<aii-model-edit-modal
|
||||
:model="selectedModel"
|
||||
v-model:show-modal="showModelEditModal"
|
||||
:reload="load"
|
||||
/>
|
||||
</n-flex>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { FormRules } from 'naive-ui'
|
||||
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { AiiProviderPublic } from '@/types/aii.ts'
|
||||
import type { ReturnDto } from '@/types/response.ts'
|
||||
|
||||
export async function check_remote_model(provider_id: number, model_name: string) {
|
||||
try {
|
||||
return await api
|
||||
.get(`/aii/provider/${provider_id}/remote/model/${model_name}/`)
|
||||
.then((res) => res.data as ReturnDto)
|
||||
.then((data) => data.success)
|
||||
} catch (err) {
|
||||
console.error('检测远端模型可用性时出现问题:', provider_id, model_name, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function check_remote_provider(provider: AiiProviderPublic) {
|
||||
try {
|
||||
return await api
|
||||
.post('/aii/remote/provider/check/', JSON.stringify(provider))
|
||||
.then((res) => res.data as ReturnDto)
|
||||
.then((data) => data.success)
|
||||
} catch (err) {
|
||||
console.error(`检查远端模型提供商可用性时出现问题:`, provider, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const aiiModelRules: FormRules = {
|
||||
aii_provider_id: {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
},
|
||||
model_name: {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
},
|
||||
max_context_length: {
|
||||
required: true,
|
||||
trigger: ['change', 'blur'],
|
||||
message: '最大上下文长度需要合理设置。大部分模型的上下文长度在数百到一千 k 左右',
|
||||
validator(_, value) {
|
||||
if (typeof value !== 'number') return new Error('非数字')
|
||||
if (value < 20) return new Error('上下文长度过小,不太合理?')
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export interface AiiModelPublic {
|
||||
provider_id: number
|
||||
provider_name: string
|
||||
base_url: string
|
||||
reasonable: boolean
|
||||
}
|
||||
|
||||
export interface AiiProviderPublic {
|
||||
|
||||
Reference in New Issue
Block a user