1
This commit is contained in:
@@ -3,19 +3,3 @@ import request from './request.js'
|
||||
export function login(data) {
|
||||
return request.post('/system-config/login', data)
|
||||
}
|
||||
|
||||
export function getSystemConfig() {
|
||||
return request.get('/system-config')
|
||||
}
|
||||
|
||||
export function updateSystemConfig(data) {
|
||||
return request.put('/system-config', data)
|
||||
}
|
||||
|
||||
export function regenerateToken() {
|
||||
return request.post('/system-config/regenerate-token')
|
||||
}
|
||||
|
||||
export function getToken() {
|
||||
return request.get('/system-config/token')
|
||||
}
|
||||
|
||||
@@ -15,3 +15,7 @@ export function deleteModelConfig(id) {
|
||||
export function testModelConfig(id) {
|
||||
return request.post('/model-config/test', { id })
|
||||
}
|
||||
|
||||
export function setDefaultModelConfig(id) {
|
||||
return request.post('/model-config/set-default', { id })
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ function scrollBottom() {
|
||||
}
|
||||
|
||||
async function refreshConversations() {
|
||||
conversations.value = await listConversations()
|
||||
const c = await listConversations()
|
||||
conversations.value = (c && c.list) || []
|
||||
}
|
||||
|
||||
function newConversation() {
|
||||
@@ -100,7 +101,8 @@ function newConversation() {
|
||||
async function openConversation(c) {
|
||||
currentConvId.value = c.id
|
||||
if (c.dataset_id) datasetId.value = c.dataset_id
|
||||
const list = await listMessages(c.id)
|
||||
const r = await listMessages(c.id)
|
||||
const list = (r && r.list) || []
|
||||
messages.value = list.map(m => {
|
||||
let citations = []
|
||||
if (m.citations) {
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
<el-dialog v-model="editDialog" title="编辑分块" width="640px">
|
||||
<el-input v-model="editContent" type="textarea" :rows="10" />
|
||||
<div class="edit-tip">保存后重新分词并向量化,向量模型未绑定时仅更新全文索引</div>
|
||||
<div class="edit-tip">保存后重新分词并向量化</div>
|
||||
<template #footer>
|
||||
<el-button @click="editDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="editSaving" @click="saveChunk">保存</el-button>
|
||||
|
||||
@@ -9,13 +9,17 @@
|
||||
<el-table-column label="向量模型" width="200">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.embedding_cfg_id">{{ embeddingName(row.embedding_cfg_id) }}</span>
|
||||
<el-tag v-else size="small" type="info">未绑定(仅全文检索)</el-tag>
|
||||
<el-tag v-else size="small" type="info">未设置</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分块" width="140">
|
||||
<template #default="{ row }">{{ STRATEGY_NAMES[row.chunk_strategy || 'title'] }} {{ row.chunk_size || 800 }}{{ row.chunk_strategy === 'semantic' ? '' : ' / ' + (row.chunk_overlap ?? 150) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<el-table-column label="操作" width="190" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="$router.push('/datasets/' + row.id)">详情</el-button>
|
||||
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="remove(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -29,11 +33,27 @@
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="form.description" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
<el-form-item label="向量模型">
|
||||
<el-select v-model="form.embedding_cfg_id" clearable placeholder="不绑定则仅全文检索" style="width: 100%">
|
||||
<el-form-item label="向量模型" required>
|
||||
<el-select v-model="form.embedding_cfg_id" placeholder="请选择向量模型" style="width: 100%">
|
||||
<el-option v-for="m in embedders" :key="m.id" :label="m.name" :value="m.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分块策略">
|
||||
<el-select v-model="form.chunk_strategy" style="width: 100%">
|
||||
<el-option label="标题分块(推荐)" value="title" />
|
||||
<el-option label="递归字符分块" value="recursive" />
|
||||
<el-option label="语义分块" value="semantic" />
|
||||
</el-select>
|
||||
<div class="ds-tip">标题分块保留标题与段落结构;递归字符分块按分隔符切分,通用文本;语义分块按语义相似度切分,质量更高但解析更慢,且需绑定向量模型</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="分块大小">
|
||||
<el-input-number v-model="form.chunk_size" :min="50" :max="5000" :step="50" style="width: 100%" />
|
||||
<div class="ds-tip">{{ form.chunk_strategy === 'semantic' ? '语义分块按句子相似度切分,此值决定最小分块大小(低于该值的候选块会继续合并)' : '每块最大字数,超长段落自动按句号/换行切分' }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.chunk_strategy !== 'semantic'" label="重叠字数">
|
||||
<el-input-number v-model="form.chunk_overlap" :min="0" :max="500" :step="10" style="width: 100%" />
|
||||
<div class="ds-tip">相邻分块间的重叠字数,用于保持上下文连贯</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
@@ -49,13 +69,17 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { listDatasets, saveDataset, deleteDataset } from '../api/dataset.js'
|
||||
import { listModelConfigs } from '../api/model_config.js'
|
||||
|
||||
const STRATEGY_NAMES = { title: '标题', recursive: '递归', semantic: '语义' }
|
||||
|
||||
const datasets = ref([])
|
||||
const embedders = ref([])
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const editing = ref(false)
|
||||
const form = ref({ id: 0, name: '', description: '', embedding_cfg_id: 0 })
|
||||
let editingRowCfgId = 0
|
||||
let editingRowChunk = { chunk_size: 0, chunk_overlap: 0, chunk_strategy: '' }
|
||||
const form = ref({ id: 0, name: '', description: '', embedding_cfg_id: 0, chunk_size: 800, chunk_overlap: 150, chunk_strategy: 'title' })
|
||||
|
||||
onMounted(async () => {
|
||||
await load()
|
||||
@@ -82,7 +106,22 @@ function embeddingName(id) {
|
||||
|
||||
function openCreate() {
|
||||
editing.value = false
|
||||
form.value = { id: 0, name: '', description: '', embedding_cfg_id: 0 }
|
||||
editingRowCfgId = 0
|
||||
editingRowChunk = { chunk_size: 0, chunk_overlap: 0, chunk_strategy: '' }
|
||||
// 默认选中默认向量模型,其次第一个
|
||||
const def = embedders.value.find(x => x.is_default === 1) || embedders.value[0]
|
||||
form.value = { id: 0, name: '', description: '', embedding_cfg_id: def?.id || 0, chunk_size: 800, chunk_overlap: 150, chunk_strategy: 'title' }
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function openEdit(row) {
|
||||
editing.value = true
|
||||
editingRowCfgId = row.embedding_cfg_id
|
||||
editingRowChunk = { chunk_size: row.chunk_size || 800, chunk_overlap: row.chunk_overlap ?? 150, chunk_strategy: row.chunk_strategy || 'title' }
|
||||
form.value = {
|
||||
id: row.id, name: row.name, description: row.description, embedding_cfg_id: row.embedding_cfg_id,
|
||||
chunk_size: editingRowChunk.chunk_size, chunk_overlap: editingRowChunk.chunk_overlap, chunk_strategy: editingRowChunk.chunk_strategy,
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
@@ -91,10 +130,26 @@ async function save() {
|
||||
ElMessage.warning('请输入数据集名称')
|
||||
return
|
||||
}
|
||||
if (!form.value.embedding_cfg_id) {
|
||||
ElMessage.warning('请选择向量模型(数据集必须绑定向量模型)')
|
||||
return
|
||||
}
|
||||
const modelChanged = editing.value && form.value.embedding_cfg_id !== editingRowCfgId
|
||||
const chunkChanged = editing.value && (form.value.chunk_size !== editingRowChunk.chunk_size || form.value.chunk_overlap !== editingRowChunk.chunk_overlap || form.value.chunk_strategy !== editingRowChunk.chunk_strategy)
|
||||
if (modelChanged || chunkChanged) {
|
||||
const changed = []
|
||||
if (modelChanged) changed.push('向量模型')
|
||||
if (chunkChanged) changed.push('分块策略')
|
||||
try {
|
||||
await ElMessageBox.confirm(`${changed.join('与')}已变更,保存后将重新处理该数据集下所有文档(异步进行,可在文档列表查看进度)。是否继续?`, '重新处理文档', { type: 'warning' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
await saveDataset(form.value)
|
||||
ElMessage.success('保存成功')
|
||||
ElMessage.success(modelChanged || chunkChanged ? '已保存,正在重新处理全部文档' : '保存成功')
|
||||
dialogVisible.value = false
|
||||
await load()
|
||||
} finally {
|
||||
@@ -120,4 +175,9 @@ async function remove(row) {
|
||||
.ds-head {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.ds-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
|
||||
+37
-130
@@ -1,66 +1,33 @@
|
||||
<template>
|
||||
<div class="settings-page">
|
||||
<el-tabs v-model="tab">
|
||||
<el-tab-pane label="模型配置" name="models">
|
||||
<div class="section-head">
|
||||
<el-radio-group v-model="modelType" @change="loadModels">
|
||||
<el-radio-button value="chat">对话模型</el-radio-button>
|
||||
<el-radio-button value="embedding">向量模型</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button type="primary" @click="openModelDialog()">新增配置</el-button>
|
||||
</div>
|
||||
<el-table :data="models" v-loading="modelLoading">
|
||||
<el-table-column prop="name" label="名称" min-width="140" />
|
||||
<el-table-column prop="model_name" label="模型" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="endpoint_url" label="接口地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="dimension" label="维度" width="80">
|
||||
<template #default="{ row }">{{ row.model_type === 'embedding' ? row.dimension : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" :loading="testingId === row.id" @click="test(row)">测试</el-button>
|
||||
<el-button link type="primary" @click="openModelDialog(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="removeModel(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="默认设置" name="defaults">
|
||||
<el-form label-width="120px" style="max-width: 480px">
|
||||
<el-form-item label="默认对话模型">
|
||||
<el-select v-model="defaults.default_chat_model" clearable placeholder="选择默认对话模型" style="width: 100%">
|
||||
<el-option v-for="m in chatModels" :key="m.id" :label="m.name" :value="m.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="默认数据集">
|
||||
<el-select v-model="defaults.default_dataset" clearable placeholder="选择默认数据集" style="width: 100%">
|
||||
<el-option v-for="d in datasets" :key="d.id" :label="d.name" :value="d.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="defaultSaving" @click="saveDefaults">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="defaults-tip">默认对话模型用于知识图谱抽取与问答;默认数据集用于问答页初始选择</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="访问令牌" name="token">
|
||||
<el-form label-width="120px" style="max-width: 520px">
|
||||
<el-form-item label="当前令牌">
|
||||
<el-input :model-value="currentToken" readonly>
|
||||
<template #append>
|
||||
<el-button @click="copyToken">复制</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="danger" plain :loading="tokenRegenerating" @click="regenerate">重新生成令牌</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="defaults-tip">重新生成后旧令牌立即失效,当前会话将被登出,需使用新令牌重新登录</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div class="section-head">
|
||||
<el-radio-group v-model="modelType" @change="loadModels">
|
||||
<el-radio-button value="chat">对话模型</el-radio-button>
|
||||
<el-radio-button value="embedding">向量模型</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button type="primary" @click="openModelDialog()">新增配置</el-button>
|
||||
</div>
|
||||
<el-table :data="models" v-loading="modelLoading">
|
||||
<el-table-column prop="name" label="名称" min-width="140" />
|
||||
<el-table-column prop="model_name" label="模型" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="endpoint_url" label="接口地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="dimension" label="维度" width="80">
|
||||
<template #default="{ row }">{{ row.model_type === 'embedding' ? row.dimension : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="默认" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.is_default === 1" type="success" size="small">默认</el-tag>
|
||||
<el-button v-else link type="primary" @click="setDefault(row)">设默认</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" :loading="testingId === row.id" @click="test(row)">测试</el-button>
|
||||
<el-button link type="primary" @click="openModelDialog(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="removeModel(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="modelDialog" :title="modelForm.id ? '编辑模型配置' : '新增模型配置'" width="520px">
|
||||
<el-form :model="modelForm" label-width="90px">
|
||||
@@ -97,39 +64,21 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { listModelConfigs, saveModelConfig, deleteModelConfig, testModelConfig } from '../api/model_config.js'
|
||||
import { listDatasets } from '../api/dataset.js'
|
||||
import { getSystemConfig, updateSystemConfig, regenerateToken, getToken } from '../api/auth.js'
|
||||
import { listModelConfigs, saveModelConfig, deleteModelConfig, testModelConfig, setDefaultModelConfig } from '../api/model_config.js'
|
||||
|
||||
const tab = ref('models')
|
||||
const modelType = ref('chat')
|
||||
const models = ref([])
|
||||
const allModels = ref([])
|
||||
const modelLoading = ref(false)
|
||||
const testingId = ref(0)
|
||||
const chatModels = ref([])
|
||||
|
||||
const modelDialog = ref(false)
|
||||
const modelSaving = ref(false)
|
||||
const modelForm = ref({ id: 0, name: '', model_type: 'chat', model_name: '', endpoint_url: '', api_key: '', dimension: 1024 })
|
||||
|
||||
const defaults = ref({ default_chat_model: 0, default_dataset: 0 })
|
||||
const datasets = ref([])
|
||||
const defaultSaving = ref(false)
|
||||
|
||||
const currentToken = ref('')
|
||||
const tokenRegenerating = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadModels()
|
||||
try { const all = await listModelConfigs(''); if (all && all.list) allModels.value = all.list } catch { /* 忽略 */ }
|
||||
try { const m = await listModelConfigs('chat'); if (m && m.list) chatModels.value = m.list } catch { /* 忽略 */ }
|
||||
try {
|
||||
const d = await listDatasets()
|
||||
if (d && d.list) datasets.value = d.list
|
||||
} catch { /* 忽略 */ }
|
||||
await loadDefaults()
|
||||
await loadToken()
|
||||
})
|
||||
|
||||
async function loadModels() {
|
||||
@@ -142,19 +91,6 @@ async function loadModels() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDefaults() {
|
||||
try {
|
||||
defaults.value = await getSystemConfig()
|
||||
} catch { /* 忽略 */ }
|
||||
}
|
||||
|
||||
async function loadToken() {
|
||||
try {
|
||||
const res = await getToken()
|
||||
currentToken.value = res.token
|
||||
} catch { /* 忽略 */ }
|
||||
}
|
||||
|
||||
function openModelDialog(row) {
|
||||
if (row) {
|
||||
modelForm.value = { ...row }
|
||||
@@ -193,7 +129,6 @@ async function saveModel() {
|
||||
ElMessage.success('保存成功')
|
||||
modelDialog.value = false
|
||||
await loadModels()
|
||||
if (modelType.value === 'chat') { const m = await listModelConfigs('chat'); if (m && m.list) chatModels.value = m.list }
|
||||
} finally {
|
||||
modelSaving.value = false
|
||||
}
|
||||
@@ -212,6 +147,14 @@ async function removeModel(row) {
|
||||
} catch { /* request.js 已提示 */ }
|
||||
}
|
||||
|
||||
async function setDefault(row) {
|
||||
try {
|
||||
await setDefaultModelConfig(row.id)
|
||||
ElMessage.success(`已将「${row.name}」设为默认${row.model_type === 'chat' ? '对话模型' : '向量模型'}`)
|
||||
await loadModels()
|
||||
} catch { /* request.js 已提示 */ }
|
||||
}
|
||||
|
||||
async function test(row) {
|
||||
testingId.value = row.id
|
||||
try {
|
||||
@@ -225,38 +168,6 @@ async function test(row) {
|
||||
testingId.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDefaults() {
|
||||
defaultSaving.value = true
|
||||
try {
|
||||
await updateSystemConfig(defaults.value)
|
||||
ElMessage.success('已保存')
|
||||
} finally {
|
||||
defaultSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function regenerate() {
|
||||
try {
|
||||
await ElMessageBox.confirm('重新生成后旧令牌立即失效,确认继续?', '重新生成令牌', { type: 'warning' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
tokenRegenerating.value = true
|
||||
try {
|
||||
const res = await regenerateToken()
|
||||
currentToken.value = res.token
|
||||
ElMessage.success('新令牌已生成,请用新令牌重新登录')
|
||||
} finally {
|
||||
tokenRegenerating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function copyToken() {
|
||||
if (!currentToken.value) return
|
||||
navigator.clipboard?.writeText(currentToken.value)
|
||||
ElMessage.success('已复制')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -269,8 +180,4 @@ function copyToken() {
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.defaults-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user