refactor(ui): 优化系统提示词编辑功能,添加弹窗支持和相关逻辑

This commit is contained in:
Wenjie Zhang 2026-03-18 20:01:09 +08:00
parent 29ebeedd4e
commit 1ae2974fad
4 changed files with 88 additions and 51 deletions

View File

@ -259,7 +259,7 @@ async def toggle_mcp_server_route(
return {
"success": True,
"enabled": is_enabled,
"message": f"服务器 '{name}'{'启用' if is_enabled else '禁用'}",
"message": f"MCP '{name}'{'启用' if is_enabled else '禁用'}",
}
except ValueError as ve:
raise HTTPException(status_code=404, detail=str(ve))

View File

@ -61,26 +61,14 @@
v-else-if="value.template_metadata.kind === 'prompt'"
class="system-prompt-container"
>
<!-- 编辑模式 -->
<a-textarea
v-if="systemPromptEditMode"
:value="agentConfig[key]"
@update:value="(val) => agentStore.updateAgentConfig({ [key]: val })"
:rows="10"
:placeholder="getPlaceholder(key, value)"
class="system-prompt-input"
@blur="systemPromptEditMode = false"
ref="systemPromptTextarea"
/>
<!-- 显示模式 -->
<div v-else class="system-prompt-display" @click="enterEditMode">
<div class="system-prompt-display" @click="openSystemPromptModal(key)">
<div
class="system-prompt-content"
:class="{ 'is-placeholder': !agentConfig[key] }"
>
{{ agentConfig[key] || getPlaceholder(key, value) }}
</div>
<div class="edit-hint">点击编辑</div>
<div class="edit-hint">点击查看并编辑</div>
</div>
</div>
@ -407,11 +395,34 @@
</div>
</div>
</a-modal>
<a-modal
v-model:open="systemPromptModalOpen"
:title="systemPromptModalTitle"
:width="620"
:maskClosable="false"
@cancel="closeSystemPromptModal"
class="system-prompt-modal"
>
<div class="system-prompt-modal-content">
<a-textarea
v-model:value="systemPromptDraft"
:rows="14"
:placeholder="systemPromptModalPlaceholder"
class="system-prompt-modal-input"
/>
</div>
<template #footer>
<a-button @click="closeSystemPromptModal">取消</a-button>
<a-button type="primary" @click="saveSystemPrompt">保存</a-button>
</template>
</a-modal>
</div>
</template>
<script setup>
import { ref, computed, nextTick, watch } from 'vue'
import { ref, computed, watch } from 'vue'
import { message, Modal } from 'ant-design-vue'
import { useRouter } from 'vue-router'
import { X, Trash2, Check, Plus, Search, Star, RotateCw, Settings } from 'lucide-vue-next'
@ -480,7 +491,9 @@ const selectionModalOpen = ref(false)
const currentConfigKey = ref(null)
const tempSelectedValues = ref([])
const selectionSearchText = ref('')
const systemPromptEditMode = ref(false)
const systemPromptModalOpen = ref(false)
const currentSystemPromptKey = ref(null)
const systemPromptDraft = ref('')
const activeTab = ref('basic')
const liveSkillOptions = ref([])
const toolOptionsFromApi = ref([])
@ -673,6 +686,18 @@ const currentConfigKind = computed(() => {
return configurableItems.value[currentConfigKey.value]?.template_metadata?.kind
})
const systemPromptModalTitle = computed(() => {
if (!currentSystemPromptKey.value) return 'System Prompt'
return configurableItems.value[currentSystemPromptKey.value]?.name || currentSystemPromptKey.value
})
const systemPromptModalPlaceholder = computed(() => {
if (!currentSystemPromptKey.value) return '请输入系统提示词'
const currentItem = configurableItems.value[currentSystemPromptKey.value]
if (!currentItem) return '请输入系统提示词'
return getPlaceholder(currentSystemPromptKey.value, currentItem)
})
const filteredOptions = computed(() => {
if (!currentConfigKey.value) return []
const key = currentConfigKey.value
@ -830,16 +855,25 @@ const closeSelectionModal = () => {
selectionSearchText.value = ''
}
//
const enterEditMode = () => {
systemPromptEditMode.value = true
// 使 nextTick DOM
nextTick(() => {
const textarea = document.querySelector('.system-prompt-input')
if (textarea) {
textarea.focus()
}
//
const openSystemPromptModal = (key) => {
currentSystemPromptKey.value = key
systemPromptDraft.value = agentConfig.value[key] || ''
systemPromptModalOpen.value = true
}
const closeSystemPromptModal = () => {
systemPromptModalOpen.value = false
currentSystemPromptKey.value = null
systemPromptDraft.value = ''
}
const saveSystemPrompt = () => {
if (!currentSystemPromptKey.value) return
agentStore.updateAgentConfig({
[currentSystemPromptKey.value]: systemPromptDraft.value
})
closeSystemPromptModal()
}
//
@ -1041,24 +1075,14 @@ const confirmDeleteConfig = async () => {
width: 100%;
}
.system-prompt-input {
resize: vertical;
background: var(--gray-50);
border: 1px solid var(--gray-200);
padding: 6px 10px;
font-size: 12px;
&:focus {
outline: none;
}
}
.system-prompt-container {
width: 100%;
}
.system-prompt-display {
min-height: 60px;
border: 1px solid var(--gray-200);
padding: 10px 12px;
border-radius: 6px;
cursor: pointer;
position: relative;
@ -1074,13 +1098,16 @@ const confirmDeleteConfig = async () => {
}
.system-prompt-content {
white-space: pre-wrap;
white-space: pre-line;
word-break: break-word;
line-height: 1.5;
color: var(--gray-900);
font-size: 12px;
max-height: 500px;
overflow: scroll;
font-size: 13px;
display: -webkit-box;
line-clamp: 4;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
&.is-placeholder {
color: var(--gray-400);
@ -1497,6 +1524,7 @@ const confirmDeleteConfig = async () => {
line-height: 1.4;
margin-top: 6px;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
@ -1579,6 +1607,17 @@ const confirmDeleteConfig = async () => {
}
}
.system-prompt-modal {
.system-prompt-modal-content {
.system-prompt-modal-input {
resize: vertical;
font-size: 13px;
line-height: 1.6;
border-radius: 8px;
}
}
}
.clear-btn {
padding: 0;
height: auto;

View File

@ -2,12 +2,7 @@
<div class="file-table-container">
<div class="panel-header">
<div class="upload-btn-group">
<a-button
type="primary"
size="small"
class="upload-btn"
@click="showAddFilesModal()"
>
<a-button type="primary" size="small" class="upload-btn" @click="showAddFilesModal()">
<FileUp size="14" />
上传
</a-button>

View File

@ -476,7 +476,7 @@
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { notification, Modal } from 'ant-design-vue'
import { message, notification, Modal } from 'ant-design-vue'
import {
Search,
Plug,
@ -536,7 +536,10 @@ const form = reactive({
//
const filteredServers = computed(() => {
const sorted = [...servers.value].sort((a, b) => {
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
return String(a.name || '').localeCompare(String(b.name || ''), 'zh-Hans-CN', {
sensitivity: 'base',
numeric: true
})
})
if (!searchQuery.value) return sorted
const q = searchQuery.value.toLowerCase()
@ -823,7 +826,7 @@ const handleToggleServer = async (server) => {
toggleLoading.value = server.name
const result = await mcpApi.toggleMcpServer(server.name)
if (result.success) {
notification.success({ message: result.message })
message.success(result.message)
await fetchServers()
} else {
notification.error({ message: result.message || '操作失败' })