mirror of
https://github.com/jumpserver/lina.git
synced 2025-11-29 14:05:19 +00:00
Compare commits
84 Commits
pr@dev@ope
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88e10b34cd | ||
|
|
f7830e9b85 | ||
|
|
16a92d10bc | ||
|
|
7b568ec84f | ||
|
|
73d6bda8c3 | ||
|
|
3934b45367 | ||
|
|
0c5e84d1e3 | ||
|
|
18a3f42717 | ||
|
|
68030d98b2 | ||
|
|
a861f77609 | ||
|
|
e58ec6057c | ||
|
|
7ab20c5885 | ||
|
|
e47ddb5355 | ||
|
|
56aa3caa83 | ||
|
|
19b1dc0dbc | ||
|
|
77ef172a23 | ||
|
|
4596887bf1 | ||
|
|
0a3dc30c85 | ||
|
|
51d24bc8e5 | ||
|
|
1b15a4d043 | ||
|
|
7d3f818242 | ||
|
|
4e26f18d77 | ||
|
|
b22613617a | ||
|
|
e971cbf4a8 | ||
|
|
4672abae35 | ||
|
|
ba36d72602 | ||
|
|
4bfbbba4c5 | ||
|
|
ea038ce43a | ||
|
|
e16b19666c | ||
|
|
c7f5409eb6 | ||
|
|
fdbd7d2222 | ||
|
|
ddbaeeafea | ||
|
|
efb0e9dacb | ||
|
|
f6f8301ad5 | ||
|
|
9a63ae63d4 | ||
|
|
1e007ccda3 | ||
|
|
d1d0b06b53 | ||
|
|
5fb70d2f24 | ||
|
|
b54a95430f | ||
|
|
4d8b4c45af | ||
|
|
a6d642df60 | ||
|
|
2e74f1522f | ||
|
|
fe615e0314 | ||
|
|
09f734e6fc | ||
|
|
3117046342 | ||
|
|
b68aecb5cc | ||
|
|
1c9b155d97 | ||
|
|
75b1be9864 | ||
|
|
615c3c1cf4 | ||
|
|
4d82231af4 | ||
|
|
c6cf6571b6 | ||
|
|
8ea990d070 | ||
|
|
f4a32170d5 | ||
|
|
073508675e | ||
|
|
1d6ca0a93a | ||
|
|
36aea652d6 | ||
|
|
1a42ce90ab | ||
|
|
31a401b55d | ||
|
|
582a84178d | ||
|
|
9b9f7c936c | ||
|
|
2a6100957f | ||
|
|
16606d6a27 | ||
|
|
0a612f50e6 | ||
|
|
fe36fa9390 | ||
|
|
ba109900ec | ||
|
|
ec7768267f | ||
|
|
cc58b374ab | ||
|
|
04ffbb8fd6 | ||
|
|
49880f6739 | ||
|
|
e6f98d58c4 | ||
|
|
fd1f16d43c | ||
|
|
968b2415b1 | ||
|
|
776090d6ba | ||
|
|
3a37952288 | ||
|
|
62b8fc0e3b | ||
|
|
b2028869cb | ||
|
|
5277a725f8 | ||
|
|
f137788c1a | ||
|
|
f7d17c8de7 | ||
|
|
feea70b0be | ||
|
|
04696ef3d6 | ||
|
|
1731f4f788 | ||
|
|
6f25d93909 | ||
|
|
46461ec324 |
@@ -149,7 +149,7 @@ export default {
|
|||||||
},
|
},
|
||||||
labelWidth: {
|
labelWidth: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '18.2%'
|
default: '25%'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -243,10 +243,12 @@ 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,7 +10,6 @@
|
|||||||
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: {
|
||||||
@@ -18,54 +17,106 @@ export default {
|
|||||||
GenericCreateUpdateForm
|
GenericCreateUpdateForm
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const hasProviders = (formValue) => {
|
const vm = this
|
||||||
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_PROVIDERS',
|
'CHAT_AI_EMBED_URL',
|
||||||
'CHAT_AI_EMBED_URL'
|
'CHAT_AI_TYPE',
|
||||||
|
'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' || hasProviders(formValue)
|
return formValue.CHAT_AI_METHOD !== 'api'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CHAT_AI_PROVIDERS: {
|
GPT_BASE_URL: {
|
||||||
component: ChatProvidersField,
|
el: {
|
||||||
hidden: (formValue) => formValue.CHAT_AI_METHOD !== 'api'
|
autocomplete: 'new-password'
|
||||||
|
},
|
||||||
|
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'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
<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