mirror of
https://github.com/jumpserver/lina.git
synced 2025-11-26 00:35:59 +00:00
Compare commits
1 Commits
v4.10.13-l
...
pr@dev@ope
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62a935668b |
@@ -243,12 +243,10 @@ export default {
|
|||||||
delete routeFilter.search
|
delete routeFilter.search
|
||||||
}
|
}
|
||||||
const asFilterTags = _.cloneDeep(this.filterTags)
|
const asFilterTags = _.cloneDeep(this.filterTags)
|
||||||
setTimeout(() => {
|
this.filterTags = {
|
||||||
this.filterTags = {
|
...asFilterTags,
|
||||||
...asFilterTags,
|
...routeFilter
|
||||||
...routeFilter
|
}
|
||||||
}
|
|
||||||
}, 100)
|
|
||||||
},
|
},
|
||||||
getValueLabel(key, value) {
|
getValueLabel(key, value) {
|
||||||
for (const field of this.options) {
|
for (const field of this.options) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
import { GenericCreateUpdateForm } from '@/layout/components'
|
import { GenericCreateUpdateForm } from '@/layout/components'
|
||||||
import IBox from '@/components/Common/IBox/index.vue'
|
import IBox from '@/components/Common/IBox/index.vue'
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
|
import ChatProvidersField from './components/ChatProvidersField.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -17,106 +18,54 @@ export default {
|
|||||||
GenericCreateUpdateForm
|
GenericCreateUpdateForm
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const vm = this
|
const hasProviders = (formValue) => {
|
||||||
|
const providers = formValue?.CHAT_AI_PROVIDERS?.providers || []
|
||||||
|
return Array.isArray(providers) && providers.length > 0
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
url: '/api/v1/settings/setting/?category=chat',
|
url: '/api/v1/settings/setting/?category=chat',
|
||||||
hasReset: false,
|
hasReset: false,
|
||||||
moreButtons: [
|
moreButtons: [
|
||||||
{
|
|
||||||
title: this.$t('Test'),
|
|
||||||
loading: false,
|
|
||||||
callback: function(value, form, btn) {
|
|
||||||
btn.loading = true
|
|
||||||
vm.$axios.post(
|
|
||||||
'/api/v1/settings/chatai/testing/',
|
|
||||||
value
|
|
||||||
).then(res => {
|
|
||||||
vm.$message.success(res['msg'])
|
|
||||||
}).catch(() => {
|
|
||||||
vm.$log.error('err occur')
|
|
||||||
}).finally(() => {
|
|
||||||
btn.loading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
encryptedFields: ['VAULT_HCP_TOKEN'],
|
encryptedFields: ['VAULT_HCP_TOKEN'],
|
||||||
fields: [
|
fields: [
|
||||||
'CHAT_AI_ENABLED',
|
'CHAT_AI_ENABLED',
|
||||||
'CHAT_AI_METHOD',
|
'CHAT_AI_METHOD',
|
||||||
'CHAT_AI_EMBED_URL',
|
'CHAT_AI_PROVIDERS',
|
||||||
'CHAT_AI_TYPE',
|
'CHAT_AI_EMBED_URL'
|
||||||
'DEEPSEEK_BASE_URL',
|
|
||||||
'DEEPSEEK_API_KEY',
|
|
||||||
'DEEPSEEK_PROXY',
|
|
||||||
'DEEPSEEK_MODEL',
|
|
||||||
'GPT_BASE_URL',
|
|
||||||
'GPT_API_KEY',
|
|
||||||
'GPT_PROXY',
|
|
||||||
'GPT_MODEL'
|
|
||||||
],
|
],
|
||||||
fieldsMeta: {
|
fieldsMeta: {
|
||||||
CHAT_AI_TYPE: {
|
CHAT_AI_TYPE: {
|
||||||
hidden: (formValue) => {
|
hidden: (formValue) => {
|
||||||
return formValue.CHAT_AI_METHOD !== 'api'
|
return formValue.CHAT_AI_METHOD !== 'api' || hasProviders(formValue)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GPT_BASE_URL: {
|
CHAT_AI_PROVIDERS: {
|
||||||
el: {
|
component: ChatProvidersField,
|
||||||
autocomplete: 'new-password'
|
hidden: (formValue) => formValue.CHAT_AI_METHOD !== 'api'
|
||||||
},
|
|
||||||
hidden: (formValue) => {
|
|
||||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'gpt'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GPT_API_KEY: {
|
|
||||||
el: {
|
|
||||||
autocomplete: 'new-password'
|
|
||||||
},
|
|
||||||
hidden: (formValue) => {
|
|
||||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'gpt'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GPT_PROXY: {
|
|
||||||
hidden: (formValue) => {
|
|
||||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'gpt'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GPT_MODEL: {
|
|
||||||
hidden: (formValue) => {
|
|
||||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'gpt'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DEEPSEEK_BASE_URL: {
|
|
||||||
el: {
|
|
||||||
autocomplete: 'new-password'
|
|
||||||
},
|
|
||||||
hidden: (formValue) => {
|
|
||||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'deep-seek'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DEEPSEEK_API_KEY: {
|
|
||||||
el: {
|
|
||||||
autocomplete: 'new-password'
|
|
||||||
},
|
|
||||||
hidden: (formValue) => {
|
|
||||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'deep-seek'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DEEPSEEK_PROXY: {
|
|
||||||
hidden: (formValue) => {
|
|
||||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'deep-seek'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DEEPSEEK_MODEL: {
|
|
||||||
hidden: (formValue) => {
|
|
||||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'deep-seek'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
CHAT_AI_EMBED_URL: {
|
CHAT_AI_EMBED_URL: {
|
||||||
hidden: (formValue) => formValue.CHAT_AI_METHOD !== 'embed'
|
hidden: (formValue) => formValue.CHAT_AI_METHOD !== 'embed'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
afterGetFormValue(formValue) {
|
||||||
|
const providers = Array.isArray(formValue.CHAT_AI_PROVIDERS) ? formValue.CHAT_AI_PROVIDERS : []
|
||||||
|
return {
|
||||||
|
...formValue,
|
||||||
|
CHAT_AI_PROVIDERS: {
|
||||||
|
providers: providers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cleanFormValue(values) {
|
||||||
|
const config = values.CHAT_AI_PROVIDERS || {}
|
||||||
|
const providers = Array.isArray(config.providers) ? config.providers : []
|
||||||
|
return {
|
||||||
|
...values,
|
||||||
|
CHAT_AI_PROVIDERS: providers,
|
||||||
|
CHAT_AI_TYPE: values.CHAT_AI_TYPE || providers[0]?.type || values.CHAT_AI_TYPE
|
||||||
|
}
|
||||||
|
},
|
||||||
submitMethod() {
|
submitMethod() {
|
||||||
return 'patch'
|
return 'patch'
|
||||||
}
|
}
|
||||||
|
|||||||
200
src/views/settings/Feature/components/ChatProvidersField.vue
Normal file
200
src/views/settings/Feature/components/ChatProvidersField.vue
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
<template>
|
||||||
|
<div class="providers-card">
|
||||||
|
<div class="providers-header">
|
||||||
|
<el-button size="mini" type="primary" icon="el-icon-plus" @click="addProvider">
|
||||||
|
{{ $t('Add') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="localProviders" size="mini" border>
|
||||||
|
<el-table-column :label="$t('Type')" width="140">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select v-model="row.type" size="mini" @change="emitChange">
|
||||||
|
<el-option
|
||||||
|
v-for="item in typeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('Base URL')" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input
|
||||||
|
v-model="row.base_url"
|
||||||
|
size="mini"
|
||||||
|
placeholder="https://api.example.com/v1"
|
||||||
|
@input="emitChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('API Key')" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input
|
||||||
|
v-model="row.api_key"
|
||||||
|
size="mini"
|
||||||
|
show-password
|
||||||
|
autocomplete="new-password"
|
||||||
|
@input="emitChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('Proxy')" min-width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input
|
||||||
|
v-model="row.proxy"
|
||||||
|
size="mini"
|
||||||
|
placeholder="http://ip:port"
|
||||||
|
@input="emitChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('Assistant')" min-width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-switch
|
||||||
|
v-model="row.is_assistant"
|
||||||
|
size="mini"
|
||||||
|
@change="handleAssistantChange(row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('Actions')" width="100" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
class="danger-text"
|
||||||
|
icon="el-icon-delete"
|
||||||
|
@click="removeProvider($index)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import cloneDeep from 'lodash/cloneDeep'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ChatProvidersField',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [Object, Array],
|
||||||
|
default: () => ({ providers: [], defaultProvider: '' })
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ([
|
||||||
|
{ label: 'Ollama', value: 'ollama' },
|
||||||
|
{ label: 'OpenAI', value: 'openai' }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
const { providers, defaultProvider } = this.normalizeValue(this.value)
|
||||||
|
return {
|
||||||
|
localProviders: providers,
|
||||||
|
defaultProvider: this.pickDefault(defaultProvider, providers)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
handler(v) {
|
||||||
|
const { providers, defaultProvider } = this.normalizeValue(v)
|
||||||
|
this.localProviders = providers
|
||||||
|
this.defaultProvider = this.pickDefault(defaultProvider, providers) || ''
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
emptyProvider() {
|
||||||
|
return {
|
||||||
|
type: 'openai',
|
||||||
|
base_url: 'https://api.openai.com/v1',
|
||||||
|
api_key: 'sk-JumpserveraAndWebOpenUI',
|
||||||
|
proxy: '',
|
||||||
|
is_assistant: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
normalizeValue(v) {
|
||||||
|
if (!v) {
|
||||||
|
return { providers: [], defaultProvider: '' }
|
||||||
|
}
|
||||||
|
// allow legacy array value
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
return { providers: cloneDeep(v), defaultProvider: '' }
|
||||||
|
}
|
||||||
|
const providers = Array.isArray(v.providers) ? cloneDeep(v.providers) : []
|
||||||
|
return { providers, defaultProvider: v.defaultProvider || '' }
|
||||||
|
},
|
||||||
|
pickDefault(current, providers) {
|
||||||
|
const enabledProviders = providers.filter(item => item?.enabled !== false)
|
||||||
|
if (current && providers.some(item => item.name === current)) {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
return enabledProviders[0]?.name || providers[0]?.name || ''
|
||||||
|
},
|
||||||
|
emitChange() {
|
||||||
|
const providers = cloneDeep(this.localProviders || [])
|
||||||
|
const defaultProvider = this.pickDefault(this.defaultProvider, providers)
|
||||||
|
this.defaultProvider = defaultProvider
|
||||||
|
this.$emit('input', { providers, defaultProvider })
|
||||||
|
this.$emit('change', { providers, defaultProvider })
|
||||||
|
},
|
||||||
|
handleAssistantChange(current) {
|
||||||
|
if (current.IsAssistant) {
|
||||||
|
this.localProviders.forEach(item => {
|
||||||
|
if (item !== current) item.IsAssistant = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.emitChange()
|
||||||
|
},
|
||||||
|
addProvider() {
|
||||||
|
this.localProviders.push(this.emptyProvider())
|
||||||
|
this.emitChange()
|
||||||
|
},
|
||||||
|
removeProvider(index) {
|
||||||
|
this.localProviders.splice(index, 1)
|
||||||
|
this.emitChange()
|
||||||
|
},
|
||||||
|
setDefault(name) {
|
||||||
|
this.defaultProvider = name
|
||||||
|
this.emitChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.providers-card {
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.providers-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
color: #909399;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-text {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user