Compare commits
5 Commits
52a4ffd7a9
...
b715b8e859
| Author | SHA1 | Date | |
|---|---|---|---|
| b715b8e859 | |||
| afa3026585 | |||
| 44ce7156cf | |||
| a104046fbd | |||
| 0c63c3b44d |
@@ -58,6 +58,7 @@ type UpdateModelRequest struct {
|
||||
type TestModelRequest struct {
|
||||
Provider string `json:"provider" binding:"required"`
|
||||
Model string `json:"model" binding:"required"`
|
||||
ModelType string `json:"model_type" binding:"required"`
|
||||
APIKey string `json:"api_key" binding:"required"`
|
||||
BaseURL string `json:"base_url" binding:"required"`
|
||||
APIEndpoint string `json:"api_endpoint"`
|
||||
|
||||
@@ -114,8 +114,6 @@ func (s *ModelService) Delete(id string) error {
|
||||
|
||||
// TestConnection 测试模型连接
|
||||
func (s *ModelService) TestConnection(req model.TestModelRequest) (*model.TestModelResponse, error) {
|
||||
log.Printf("[TestConnection] 开始测试连接: provider=%s, model=%s, base_url=%s, api_endpoint=%s", req.Provider, req.Model, req.BaseURL, req.APIEndpoint)
|
||||
|
||||
// 构建请求 URL
|
||||
baseURL := req.BaseURL
|
||||
// 去掉 base_url 末尾的斜杠
|
||||
@@ -126,31 +124,49 @@ func (s *ModelService) TestConnection(req model.TestModelRequest) (*model.TestMo
|
||||
apiEndpoint := strings.TrimLeft(req.APIEndpoint, "/")
|
||||
baseURL = baseURL + "/" + apiEndpoint
|
||||
} else {
|
||||
// 默认端点 - 根据不同 provider 设置
|
||||
switch req.Provider {
|
||||
case "OpenAI":
|
||||
baseURL = baseURL + "/v1/chat/completions"
|
||||
case "Ollama":
|
||||
baseURL = baseURL + "/api/chat"
|
||||
case "ali", "Ali", "aliyun", "Aliyun":
|
||||
// 阿里云 DashScope 兼容 OpenAI 格式,需要添加 /chat/completions
|
||||
// base_url 格式: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
baseURL = baseURL + "/chat/completions"
|
||||
// 根据 model_type 确定端点
|
||||
switch req.ModelType {
|
||||
case "embedding":
|
||||
// embedding 模型使用 /v1/embeddings
|
||||
switch req.Provider {
|
||||
case "Ollama":
|
||||
baseURL = baseURL + "/api/embeddings"
|
||||
default:
|
||||
baseURL = baseURL + "/v1/embeddings"
|
||||
}
|
||||
default:
|
||||
// 默认使用 OpenAI 兼容格式
|
||||
baseURL = baseURL + "/v1/chat/completions"
|
||||
// chat 模型使用 /chat/completions
|
||||
switch req.Provider {
|
||||
case "OpenAI":
|
||||
baseURL = baseURL + "/v1/chat/completions"
|
||||
case "Ollama":
|
||||
baseURL = baseURL + "/api/chat"
|
||||
case "ali", "Ali", "aliyun", "Aliyun":
|
||||
// 阿里云 DashScope 兼容 OpenAI 格式
|
||||
baseURL = baseURL + "/chat/completions"
|
||||
default:
|
||||
// 默认使用 OpenAI 兼容格式
|
||||
baseURL = baseURL + "/v1/chat/completions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[TestConnection] 请求 URL: %s", baseURL)
|
||||
|
||||
// 构建请求体
|
||||
requestBody := map[string]interface{}{
|
||||
"model": req.Model,
|
||||
"messages": []map[string]string{
|
||||
{"role": "user", "content": "Hello"},
|
||||
},
|
||||
"max_tokens": 10,
|
||||
// 构建请求体 - 根据 model_type 使用不同的格式
|
||||
var requestBody map[string]interface{}
|
||||
if req.ModelType == "embedding" {
|
||||
requestBody = map[string]interface{}{
|
||||
"model": req.Model,
|
||||
"input": "Hello",
|
||||
"format": "float",
|
||||
}
|
||||
} else {
|
||||
requestBody = map[string]interface{}{
|
||||
"model": req.Model,
|
||||
"messages": []map[string]string{
|
||||
{"role": "user", "content": "Hello"},
|
||||
},
|
||||
"max_tokens": 10,
|
||||
}
|
||||
}
|
||||
|
||||
body, err := json.Marshal(requestBody)
|
||||
@@ -191,8 +207,6 @@ func (s *ModelService) TestConnection(req model.TestModelRequest) (*model.TestMo
|
||||
return &model.TestModelResponse{Success: false, Message: err.Error()}, nil
|
||||
}
|
||||
|
||||
log.Printf("[TestConnection] 响应状态码: %d, 响应体: %s", resp.StatusCode, string(respBody))
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return &model.TestModelResponse{Success: true, Message: "Connection successful"}, nil
|
||||
}
|
||||
|
||||
130
web/src/components/FormDialog.vue
Normal file
130
web/src/components/FormDialog.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
modelValue: boolean
|
||||
title: string
|
||||
description?: string
|
||||
icon?: string
|
||||
iconClass?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
}>()
|
||||
|
||||
const close = () => {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="modelValue" class="dialog-overlay" @click.self="close">
|
||||
<div class="dialog-container">
|
||||
<!-- 头部 -->
|
||||
<div class="dialog-header">
|
||||
<div class="flex items-center gap-3">
|
||||
<div v-if="icon" :class="['dialog-icon', iconClass]">
|
||||
<i :class="icon"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="dialog-title">{{ title }}</h3>
|
||||
<p v-if="description" class="dialog-desc">{{ description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-icon" @click="close">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div class="dialog-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<!-- 底部 -->
|
||||
<div class="dialog-footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.dialog-container {
|
||||
background-color: #121218;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
max-height: 90vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #2a2a3a;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #2a2a3a;
|
||||
background-color: rgba(30, 30, 40, 0.5);
|
||||
}
|
||||
|
||||
.dialog-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dialog-icon i {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dialog-desc {
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #2a2a3a;
|
||||
background-color: rgba(30, 30, 40, 0.5);
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useModelSettings } from './settings/useModelSettings'
|
||||
import './knowledge/knowledge.css'
|
||||
|
||||
// 获取已配置的模型列表
|
||||
const { models, fetchModels } = useModelSettings()
|
||||
|
||||
// 步骤验证
|
||||
const step1Valid = computed(() => !!newKbForm.value.name.trim())
|
||||
const step2Valid = computed(() => !!embeddingConfig.value.modelId)
|
||||
const step3Valid = computed(() => true) // Parsing - 暂时默认通过
|
||||
const step4Valid = computed(() => true) // Storage - 暂时默认通过
|
||||
|
||||
// 获取当前步骤是否有效
|
||||
const isCurrentStepValid = computed(() => {
|
||||
switch (createStep.value) {
|
||||
case 1: return step1Valid.value
|
||||
case 2: return step2Valid.value
|
||||
case 3: return step3Valid.value
|
||||
case 4: return step4Valid.value
|
||||
default: return false
|
||||
}
|
||||
})
|
||||
|
||||
// 步骤是否可以点击(只能点击当前步骤或已完成的步骤)
|
||||
const canClickStep = (step: number) => {
|
||||
if (step === 1) return true
|
||||
if (step === createStep.value) return true
|
||||
// 可以点击已完成的前面的步骤
|
||||
if (step < createStep.value) {
|
||||
switch (step) {
|
||||
case 1: return step1Valid.value
|
||||
case 2: return step2Valid.value
|
||||
case 3: return step3Valid.value
|
||||
case 4: return step4Valid.value
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 下一步
|
||||
const nextStep = () => {
|
||||
if (!isCurrentStepValid.value) {
|
||||
ElMessage.warning('Please complete the current step first')
|
||||
return
|
||||
}
|
||||
if (createStep.value < 4) {
|
||||
createStep.value++
|
||||
}
|
||||
}
|
||||
|
||||
// 上一步
|
||||
const prevStep = () => {
|
||||
if (createStep.value > 1) {
|
||||
createStep.value--
|
||||
}
|
||||
}
|
||||
|
||||
// 模拟知识库数据
|
||||
const knowledgeBases = ref([
|
||||
{
|
||||
@@ -55,8 +111,7 @@ const newKbForm = ref({
|
||||
description: '',
|
||||
})
|
||||
const embeddingConfig = ref({
|
||||
provider: '',
|
||||
model: '',
|
||||
modelId: '',
|
||||
})
|
||||
|
||||
const parsingConfig = ref({
|
||||
@@ -71,7 +126,9 @@ const parsingConfig = ref({
|
||||
const openCreateDialog = () => {
|
||||
createStep.value = 1
|
||||
newKbForm.value = { name: '', description: '' }
|
||||
embeddingConfig.value = { provider: '', model: '' }
|
||||
embeddingConfig.value = { modelId: '' }
|
||||
// 获取已配置的模型列表
|
||||
fetchModels()
|
||||
parsingConfig.value = {
|
||||
enablePdf: true,
|
||||
engine: 'default',
|
||||
@@ -85,7 +142,7 @@ const openCreateDialog = () => {
|
||||
|
||||
const cancelCreate = () => {
|
||||
newKbForm.value = { name: '', description: '' }
|
||||
embeddingConfig.value = { provider: '', model: '' }
|
||||
embeddingConfig.value = { modelId: '' }
|
||||
parsingConfig.value = {
|
||||
enablePdf: true,
|
||||
engine: 'default',
|
||||
@@ -97,18 +154,6 @@ const cancelCreate = () => {
|
||||
showCreateDialog.value = false
|
||||
}
|
||||
|
||||
const nextStep = () => {
|
||||
if (!newKbForm.value.name) {
|
||||
ElMessage.warning('Please enter knowledge base name')
|
||||
return
|
||||
}
|
||||
createStep.value = 2
|
||||
}
|
||||
|
||||
const prevStep = () => {
|
||||
createStep.value = 1
|
||||
}
|
||||
|
||||
const createKnowledgeBase = () => {
|
||||
knowledgeBases.value.push({
|
||||
id: Date.now().toString(),
|
||||
@@ -120,7 +165,7 @@ const createKnowledgeBase = () => {
|
||||
status: 'ready'
|
||||
})
|
||||
newKbForm.value = { name: '', description: '' }
|
||||
embeddingConfig.value = { provider: '', model: '' }
|
||||
embeddingConfig.value = { modelId: '' }
|
||||
showCreateDialog.value = false
|
||||
ElMessage.success('Knowledge base created successfully')
|
||||
}
|
||||
@@ -300,11 +345,12 @@ const viewDetail = (kb: any) => {
|
||||
<span class="menu-desc">Name and description</span>
|
||||
</div>
|
||||
<i v-if="createStep === 1" class="fa-solid fa-chevron-right menu-arrow"></i>
|
||||
<i v-else-if="createStep > 1 && step1Valid" class="fa-solid fa-check-circle menu-check"></i>
|
||||
</div>
|
||||
<div
|
||||
class="menu-item"
|
||||
:class="{ active: createStep === 2 }"
|
||||
@click="createStep = 2"
|
||||
:class="{ active: createStep === 2, disabled: !canClickStep(2) }"
|
||||
@click="canClickStep(2) && (createStep = 2)"
|
||||
>
|
||||
<i class="fa-solid fa-robot menu-icon"></i>
|
||||
<div class="menu-content">
|
||||
@@ -312,11 +358,12 @@ const viewDetail = (kb: any) => {
|
||||
<span class="menu-desc">Embedding & rerank models</span>
|
||||
</div>
|
||||
<i v-if="createStep === 2" class="fa-solid fa-chevron-right menu-arrow"></i>
|
||||
<i v-else-if="createStep > 2 && step2Valid" class="fa-solid fa-check-circle menu-check"></i>
|
||||
</div>
|
||||
<div
|
||||
class="menu-item"
|
||||
:class="{ active: createStep === 3 }"
|
||||
@click="createStep = 3"
|
||||
:class="{ active: createStep === 3, disabled: !canClickStep(3) }"
|
||||
@click="canClickStep(3) && (createStep = 3)"
|
||||
>
|
||||
<i class="fa-solid fa-file-lines menu-icon"></i>
|
||||
<div class="menu-content">
|
||||
@@ -324,11 +371,12 @@ const viewDetail = (kb: any) => {
|
||||
<span class="menu-desc">Document parsing settings</span>
|
||||
</div>
|
||||
<i v-if="createStep === 3" class="fa-solid fa-chevron-right menu-arrow"></i>
|
||||
<i v-else-if="createStep > 3 && step3Valid" class="fa-solid fa-check-circle menu-check"></i>
|
||||
</div>
|
||||
<div
|
||||
class="menu-item"
|
||||
:class="{ active: createStep === 4 }"
|
||||
@click="createStep = 4"
|
||||
:class="{ active: createStep === 4, disabled: !canClickStep(4) }"
|
||||
@click="canClickStep(4) && (createStep = 4)"
|
||||
>
|
||||
<i class="fa-solid fa-hard-drive menu-icon"></i>
|
||||
<div class="menu-content">
|
||||
@@ -336,42 +384,7 @@ const viewDetail = (kb: any) => {
|
||||
<span class="menu-desc">Vector & file storage</span>
|
||||
</div>
|
||||
<i v-if="createStep === 4" class="fa-solid fa-chevron-right menu-arrow"></i>
|
||||
</div>
|
||||
<div
|
||||
class="menu-item"
|
||||
:class="{ active: createStep === 5 }"
|
||||
@click="createStep = 5"
|
||||
>
|
||||
<i class="fa-solid fa-scissors menu-icon"></i>
|
||||
<div class="menu-content">
|
||||
<span class="menu-title">Chunking</span>
|
||||
<span class="menu-desc">Text chunking strategy</span>
|
||||
</div>
|
||||
<i v-if="createStep === 5" class="fa-solid fa-chevron-right menu-arrow"></i>
|
||||
</div>
|
||||
<div
|
||||
class="menu-item"
|
||||
:class="{ active: createStep === 6 }"
|
||||
@click="createStep = 6"
|
||||
>
|
||||
<i class="fa-solid fa-project-diagram menu-icon"></i>
|
||||
<div class="menu-content">
|
||||
<span class="menu-title">Knowledge Graph</span>
|
||||
<span class="menu-desc">Graph extraction settings</span>
|
||||
</div>
|
||||
<i v-if="createStep === 6" class="fa-solid fa-chevron-right menu-arrow"></i>
|
||||
</div>
|
||||
<div
|
||||
class="menu-item"
|
||||
:class="{ active: createStep === 7 }"
|
||||
@click="createStep = 7"
|
||||
>
|
||||
<i class="fa-solid fa-sliders menu-icon"></i>
|
||||
<div class="menu-content">
|
||||
<span class="menu-title">Advanced</span>
|
||||
<span class="menu-desc">Additional settings</span>
|
||||
</div>
|
||||
<i v-if="createStep === 7" class="fa-solid fa-chevron-right menu-arrow"></i>
|
||||
<i v-else-if="createStep > 4 && step4Valid" class="fa-solid fa-check-circle menu-check"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -400,31 +413,21 @@ const viewDetail = (kb: any) => {
|
||||
<span class="section-title">Model Config</span>
|
||||
</div>
|
||||
<el-form label-position="top" class="kb-form">
|
||||
<el-form-item label="Embedding Provider">
|
||||
<el-select v-model="embeddingConfig.provider" placeholder="Select embedding provider" class="w-full">
|
||||
<el-option label="OpenAI" value="openai">
|
||||
<div class="provider-option">
|
||||
<i class="fa-solid fa-robot"></i>
|
||||
<span>OpenAI</span>
|
||||
</div>
|
||||
</el-option>
|
||||
<el-option label="Ollama" value="ollama">
|
||||
<div class="provider-option">
|
||||
<i class="fa-solid fa-microchip"></i>
|
||||
<span>Ollama</span>
|
||||
</div>
|
||||
</el-option>
|
||||
<el-option label="Azure OpenAI" value="azure">
|
||||
<div class="provider-option">
|
||||
<i class="fa-brands fa-microsoft"></i>
|
||||
<span>Azure OpenAI</span>
|
||||
<el-form-item label="Embedding Model">
|
||||
<el-select v-model="embeddingConfig.modelId" placeholder="Select a configured model" class="w-full" popper-class="dark-select-dropdown">
|
||||
<el-option
|
||||
v-for="model in models"
|
||||
:key="model.id"
|
||||
:label="model.name"
|
||||
:value="model.id"
|
||||
>
|
||||
<div class="model-option">
|
||||
<span class="model-name">{{ model.name }}</span>
|
||||
<span class="model-info">{{ model.provider }} - {{ model.model }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Embedding Model">
|
||||
<el-input v-model="embeddingConfig.model" placeholder="e.g., text-embedding-ada-002" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
@@ -510,76 +513,25 @@ const viewDetail = (kb: any) => {
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- Step 5: Chunking -->
|
||||
<div v-if="createStep === 5" class="form-content">
|
||||
<div class="section-header">
|
||||
<i class="fa-solid fa-scissors section-icon"></i>
|
||||
<span class="section-title">Chunking</span>
|
||||
</div>
|
||||
<el-form label-position="top" class="kb-form">
|
||||
<el-form-item label="Chunk Size">
|
||||
<el-input type="number" placeholder="e.g., 500" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Chunk Overlap">
|
||||
<el-input type="number" placeholder="e.g., 50" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- Step 6: Knowledge Graph -->
|
||||
<div v-if="createStep === 6" class="form-content">
|
||||
<div class="section-header">
|
||||
<i class="fa-solid fa-project-diagram section-icon"></i>
|
||||
<span class="section-title">Knowledge Graph</span>
|
||||
</div>
|
||||
<el-form label-position="top" class="kb-form">
|
||||
<el-form-item label="Enable Knowledge Graph">
|
||||
<el-switch />
|
||||
</el-form-item>
|
||||
<el-form-item label="Graph Extraction">
|
||||
<el-select placeholder="Select extraction level" class="w-full">
|
||||
<el-option label="Entities Only" value="entities" />
|
||||
<el-option label="Entities & Relations" value="relations" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- Step 7: Advanced -->
|
||||
<div v-if="createStep === 7" class="form-content">
|
||||
<div class="section-header">
|
||||
<i class="fa-solid fa-sliders section-icon"></i>
|
||||
<span class="section-title">Advanced</span>
|
||||
</div>
|
||||
<el-form label-position="top" class="kb-form">
|
||||
<el-form-item label="Top K">
|
||||
<el-input type="number" placeholder="e.g., 5" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Score Threshold">
|
||||
<el-input type="number" placeholder="e.g., 0.7" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<div class="footer-left">
|
||||
<span class="step-hint">Step {{ createStep }} of 7</span>
|
||||
<span class="step-hint">Step {{ createStep }} of 4</span>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<el-button @click="cancelCreate" class="cancel-btn">Cancel</el-button>
|
||||
<el-button v-if="createStep > 1" @click="createStep--" class="prev-btn">
|
||||
<el-button v-if="createStep > 1" @click="prevStep" class="prev-btn">
|
||||
<i class="fa-solid fa-arrow-left"></i>
|
||||
Previous
|
||||
</el-button>
|
||||
<el-button v-if="createStep < 7" @click="createStep++" class="next-btn">
|
||||
<el-button v-if="createStep < 4" :disabled="!isCurrentStepValid" @click="nextStep" class="next-btn">
|
||||
Next
|
||||
<i class="fa-solid fa-arrow-right"></i>
|
||||
</el-button>
|
||||
<el-button v-if="createStep === 7" @click="createKnowledgeBase" class="confirm-btn">
|
||||
<el-button v-if="createStep === 4" :disabled="!isCurrentStepValid" @click="createKnowledgeBase" class="confirm-btn">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
Create
|
||||
</el-button>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useModelSettings } from './settings/useModelSettings'
|
||||
import FormDialog from '@/components/FormDialog.vue'
|
||||
import './settings/settings.css'
|
||||
import './settings/settings-parsing.css'
|
||||
import './settings/modelSettings.css'
|
||||
@@ -476,7 +477,7 @@ const saveStorageSettings = () => {
|
||||
{{ model.model }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="model-type-tag">{{ model.model_type }}</span>
|
||||
<span class="model-type-tag" :class="model.model_type">{{ model.model_type }}</span>
|
||||
</td>
|
||||
<td class="text-center text-sm">
|
||||
{{ model.base_url }}
|
||||
@@ -498,22 +499,28 @@ const saveStorageSettings = () => {
|
||||
</table>
|
||||
|
||||
<!-- 新增模型弹窗 -->
|
||||
<el-dialog
|
||||
v-model="showAddModelForm"
|
||||
<FormDialog
|
||||
:model-value="showAddModelForm"
|
||||
@update:model-value="showAddModelForm = $event"
|
||||
title="Add New Model"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
class="add-model-dialog"
|
||||
description="Configure your model settings"
|
||||
icon="fa-solid fa-brain"
|
||||
icon-class="bg-gradient-to-br from-primary-orange to-red-500"
|
||||
>
|
||||
<p class="dialog-desc">Configure your model settings</p>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Model Name</label>
|
||||
<input
|
||||
v-model="newModelForm.name"
|
||||
type="text"
|
||||
placeholder="e.g., My GPT-4 Model"
|
||||
class="input-field"
|
||||
>
|
||||
</div>
|
||||
|
||||
<el-form label-position="top" class="settings-form">
|
||||
<el-form-item label="Model Name">
|
||||
<el-input v-model="newModelForm.name" placeholder="e.g., My GPT-4 Model" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Model Type">
|
||||
<el-select v-model="newModelForm.modelType" placeholder="Select model type">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Model Type</label>
|
||||
<el-select v-model="newModelForm.modelType" placeholder="Select model type" class="w-full" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option
|
||||
v-for="type in modelTypeOptions"
|
||||
:key="type.value"
|
||||
@@ -521,10 +528,11 @@ const saveStorageSettings = () => {
|
||||
:value="type.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item label="Provider">
|
||||
<el-select v-model="newModelForm.provider" placeholder="Select provider">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Provider</label>
|
||||
<el-select v-model="newModelForm.provider" placeholder="Select provider" class="w-full" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option
|
||||
v-for="provider in providerOptions"
|
||||
:key="provider.value"
|
||||
@@ -532,60 +540,86 @@ const saveStorageSettings = () => {
|
||||
:value="provider.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Model">
|
||||
<el-input v-model="newModelForm.model" placeholder="e.g., gpt-4o (OpenAI) or llama3 (Ollama)" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="API Key">
|
||||
<el-input v-model="newModelForm.apiKey" type="password" placeholder="sk-xxxxx (required for OpenAI)" show-password />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Base URL">
|
||||
<el-input v-model="newModelForm.baseUrl" placeholder="https://api.openai.com/v1 (OpenAI) or http://localhost:11434 (Ollama)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button text @click="cancelAddModel">Cancel</el-button>
|
||||
<el-button
|
||||
:loading="testingConnection"
|
||||
@click="testConnection"
|
||||
class="test-btn"
|
||||
>
|
||||
<i v-if="!testingConnection" class="fa-solid fa-plug"></i>
|
||||
Test Connection
|
||||
</el-button>
|
||||
<el-button type="primary" @click="addModel">Confirm</el-button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Model</label>
|
||||
<input
|
||||
v-model="newModelForm.model"
|
||||
type="text"
|
||||
placeholder="e.g., gpt-4o (OpenAI) or llama3 (Ollama)"
|
||||
class="input-field"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">API Key</label>
|
||||
<input
|
||||
v-model="newModelForm.apiKey"
|
||||
type="password"
|
||||
placeholder="sk-xxxxx (required for OpenAI)"
|
||||
class="input-field"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Base URL</label>
|
||||
<input
|
||||
v-model="newModelForm.baseUrl"
|
||||
type="text"
|
||||
placeholder="https://api.openai.com/v1 (OpenAI) or http://localhost:11434 (Ollama)"
|
||||
class="input-field"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 连接状态提示 -->
|
||||
<div v-if="connectionStatus === 'success'" class="connection-status success">
|
||||
<i class="fa-solid fa-check-circle"></i> Connection successful
|
||||
</div>
|
||||
<div v-else-if="connectionStatus === 'error'" class="connection-status error">
|
||||
<i class="fa-solid fa-xmark-circle"></i> Connection failed
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<button class="btn-secondary" @click="cancelAddModel">Cancel</button>
|
||||
<button
|
||||
:disabled="testingConnection"
|
||||
class="btn-primary"
|
||||
@click="testConnection"
|
||||
>
|
||||
<i class="fa-solid fa-plug"></i>
|
||||
Test Connection
|
||||
</button>
|
||||
<button class="btn-primary" @click="addModel">
|
||||
Confirm
|
||||
</button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</FormDialog>
|
||||
|
||||
<!-- 编辑模型弹窗 -->
|
||||
<el-dialog
|
||||
v-model="showEditModelForm"
|
||||
<FormDialog
|
||||
:model-value="showEditModelForm"
|
||||
@update:model-value="showEditModelForm = $event"
|
||||
title="Edit Model"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
class="add-model-dialog"
|
||||
description="Update your model settings"
|
||||
icon="fa-solid fa-pen-to-square"
|
||||
icon-class="bg-gradient-to-br from-primary-cyan to-blue-500"
|
||||
>
|
||||
<p class="dialog-desc">Update your model settings</p>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Model Name</label>
|
||||
<input
|
||||
v-model="editForm.name"
|
||||
type="text"
|
||||
placeholder="e.g., My GPT-4 Model"
|
||||
class="input-field"
|
||||
>
|
||||
</div>
|
||||
|
||||
<el-form label-position="top" class="settings-form">
|
||||
<el-form-item label="Model Name">
|
||||
<el-input v-model="editForm.name" placeholder="e.g., My GPT-4 Model" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Provider">
|
||||
<el-select v-model="editForm.provider" placeholder="Select provider">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Provider</label>
|
||||
<el-select v-model="editForm.provider" placeholder="Select provider" class="w-full" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option
|
||||
v-for="option in providerOptions"
|
||||
:key="option.value"
|
||||
@@ -593,14 +627,21 @@ const saveStorageSettings = () => {
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item label="Model">
|
||||
<el-input v-model="editForm.model" placeholder="e.g., gpt-4, llama2" />
|
||||
</el-form-item>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Model</label>
|
||||
<input
|
||||
v-model="editForm.model"
|
||||
type="text"
|
||||
placeholder="e.g., gpt-4, llama2"
|
||||
class="input-field"
|
||||
>
|
||||
</div>
|
||||
|
||||
<el-form-item label="Model Type">
|
||||
<el-select v-model="editForm.modelType" placeholder="Select model type">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Model Type</label>
|
||||
<el-select v-model="editForm.modelType" placeholder="Select model type" class="w-full" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option
|
||||
v-for="option in modelTypeOptions"
|
||||
:key="option.value"
|
||||
@@ -608,38 +649,52 @@ const saveStorageSettings = () => {
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="API Key">
|
||||
<el-input v-model="editForm.apiKey" type="password" placeholder="sk-..." show-password />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Base URL">
|
||||
<el-input v-model="editForm.baseUrl" placeholder="e.g., https://api.openai.com/v1" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<button class="test-btn" @click="cancelEditModel">Cancel</button>
|
||||
<el-button
|
||||
:loading="testingEditConnection"
|
||||
@click="testConnectionEdit"
|
||||
class="test-btn"
|
||||
>
|
||||
<i v-if="!testingEditConnection" class="fa-solid fa-plug"></i>
|
||||
Test Connection
|
||||
</el-button>
|
||||
<el-button type="primary" @click="updateModel">Confirm</el-button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">API Key</label>
|
||||
<input
|
||||
v-model="editForm.apiKey"
|
||||
type="password"
|
||||
placeholder="sk-..."
|
||||
class="input-field"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Base URL</label>
|
||||
<input
|
||||
v-model="editForm.baseUrl"
|
||||
type="text"
|
||||
placeholder="e.g., https://api.openai.com/v1"
|
||||
class="input-field"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 连接状态提示 -->
|
||||
<div v-if="editConnectionStatus === 'success'" class="connection-status success">
|
||||
<i class="fa-solid fa-check-circle"></i> Connection successful
|
||||
</div>
|
||||
<div v-if="editConnectionStatus === 'error'" class="connection-status error">
|
||||
<i class="fa-solid fa-xmark-circle"></i> Connection failed
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<button class="btn-secondary" @click="cancelEditModel">Cancel</button>
|
||||
<button
|
||||
:disabled="testingEditConnection"
|
||||
class="btn-primary"
|
||||
@click="testConnectionEdit"
|
||||
>
|
||||
<i class="fa-solid fa-plug"></i>
|
||||
Test Connection
|
||||
</button>
|
||||
<button class="btn-primary" @click="updateModel">
|
||||
Confirm
|
||||
</button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</FormDialog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
border-color: #f97316;
|
||||
border-color: #ffffff;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
@@ -116,7 +116,7 @@
|
||||
}
|
||||
|
||||
.kb-dialog :deep(.el-dialog__headerbtn:hover .el-dialog__close) {
|
||||
color: #f97316;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.kb-dialog :deep(.el-dialog__body) {
|
||||
@@ -135,8 +135,8 @@
|
||||
}
|
||||
|
||||
.kb-dialog :deep(.el-button--primary) {
|
||||
background-color: #f97316;
|
||||
border-color: #f97316;
|
||||
background-color: #ffffff;
|
||||
border-color: #ffffff;
|
||||
}
|
||||
|
||||
.kb-dialog :deep(.el-button--primary:hover) {
|
||||
@@ -226,6 +226,20 @@
|
||||
border-color: rgba(54, 191, 250, 0.25);
|
||||
}
|
||||
|
||||
.menu-item.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.menu-item.disabled .menu-content {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.menu-check {
|
||||
color: #22c55e;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
@@ -241,9 +255,9 @@
|
||||
}
|
||||
|
||||
.menu-item.active .menu-icon {
|
||||
background: linear-gradient(135deg, #36bffa 0%, #0ea5e9 100%);
|
||||
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(54, 191, 250, 0.3);
|
||||
box-shadow: 0 4px 12px rgba(249, 115, 22, 0.3);
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
@@ -262,7 +276,7 @@
|
||||
}
|
||||
|
||||
.menu-item.active .menu-title {
|
||||
color: #36bffa;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.menu-desc {
|
||||
@@ -272,7 +286,7 @@
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 12px;
|
||||
color: #36bffa;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 右侧内容区 */
|
||||
@@ -311,11 +325,11 @@
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(135deg, rgba(54, 191, 250, 0.2) 0%, rgba(14, 165, 233, 0.12) 100%);
|
||||
background: linear-gradient(135deg, rgba(249, 115, 22, 0.2) 0%, rgba(234, 88, 12, 0.12) 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #36bffa;
|
||||
color: #ffffff;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
@@ -342,11 +356,11 @@
|
||||
}
|
||||
|
||||
.kb-form :deep(.el-input__wrapper:hover) {
|
||||
border-color: #36bffa;
|
||||
border-color: #ffffff;
|
||||
}
|
||||
|
||||
.kb-form :deep(.el-input__wrapper.is-focus) {
|
||||
border-color: #36bffa;
|
||||
border-color: #ffffff;
|
||||
box-shadow: 0 0 0 3px rgba(54, 191, 250, 0.15);
|
||||
}
|
||||
|
||||
@@ -370,11 +384,11 @@
|
||||
}
|
||||
|
||||
.kb-form :deep(.el-textarea__inner:hover) {
|
||||
border-color: #36bffa;
|
||||
border-color: #ffffff;
|
||||
}
|
||||
|
||||
.kb-form :deep(.el-textarea__inner:focus) {
|
||||
border-color: #36bffa;
|
||||
border-color: #ffffff;
|
||||
box-shadow: 0 0 0 3px rgba(54, 191, 250, 0.15);
|
||||
}
|
||||
|
||||
@@ -391,11 +405,11 @@
|
||||
}
|
||||
|
||||
.kb-form :deep(.el-select .el-input__wrapper:hover) {
|
||||
border-color: #36bffa;
|
||||
border-color: #ffffff;
|
||||
}
|
||||
|
||||
.kb-form :deep(.el-select .el-input__wrapper.is-focus) {
|
||||
border-color: #36bffa;
|
||||
border-color: #ffffff;
|
||||
box-shadow: 0 0 0 3px rgba(54, 191, 250, 0.15);
|
||||
}
|
||||
|
||||
@@ -408,7 +422,24 @@
|
||||
}
|
||||
|
||||
.provider-option i {
|
||||
color: #36bffa;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 模型选项 */
|
||||
.model-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
color: #e8eaed;
|
||||
}
|
||||
|
||||
.model-option .model-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.model-option .model-info {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Parsing 配置样式 */
|
||||
@@ -454,8 +485,8 @@
|
||||
|
||||
/* Switch 样式 */
|
||||
:deep(.el-switch.is-checked .el-switch__core) {
|
||||
background-color: #36bffa;
|
||||
border-color: #36bffa;
|
||||
background-color: #ffffff;
|
||||
border-color: #ffffff;
|
||||
}
|
||||
|
||||
.parsing-divider {
|
||||
@@ -566,7 +597,7 @@
|
||||
}
|
||||
|
||||
.next-btn {
|
||||
background: linear-gradient(135deg, #36bffa 0%, #0ea5e9 100%);
|
||||
background: linear-gradient(135deg, #ffffff 0%, #0ea5e9 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
@@ -580,7 +611,7 @@
|
||||
}
|
||||
|
||||
.next-btn:hover {
|
||||
background: linear-gradient(135deg, #4dc3ff 0%, #36bffa 100%);
|
||||
background: linear-gradient(135deg, #4dc3ff 0%, #ffffff 100%);
|
||||
box-shadow: 0 6px 16px rgba(54, 191, 250, 0.35);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,14 +65,33 @@
|
||||
|
||||
/* Model Type 标签 */
|
||||
.model-type-tag {
|
||||
background-color: rgba(249, 115, 22, 0.2);
|
||||
color: #f97316;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
/* 不同类型的不同颜色 */
|
||||
.model-type-tag.chat {
|
||||
background-color: rgba(59, 130, 246, 0.2);
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.model-type-tag.embedding {
|
||||
background-color: rgba(34, 197, 94, 0.2);
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.model-type-tag.rerank {
|
||||
background-color: rgba(168, 85, 247, 0.2);
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
.model-type-tag.vlm {
|
||||
background-color: rgba(249, 115, 22, 0.2);
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.btn-icon {
|
||||
padding: 6px;
|
||||
|
||||
@@ -27,6 +27,8 @@ export function useModelSettings() {
|
||||
|
||||
// 默认 Base URL 映射
|
||||
const defaultBaseUrls: Record<string, string> = {
|
||||
OpenAI: 'http://localhost:1234/v1',
|
||||
Ollama: 'http://localhost:11434',
|
||||
ali: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
}
|
||||
|
||||
@@ -120,6 +122,7 @@ export function useModelSettings() {
|
||||
body: JSON.stringify({
|
||||
provider: newModelForm.value.provider,
|
||||
model: newModelForm.value.model,
|
||||
model_type: newModelForm.value.modelType || 'chat',
|
||||
api_key: newModelForm.value.apiKey,
|
||||
base_url: newModelForm.value.baseUrl,
|
||||
api_endpoint: newModelForm.value.apiEndpoint,
|
||||
|
||||
Reference in New Issue
Block a user