- 更新了 KnowledgeBaseManager 以支持新的文件管理方法。 - 在 workspace_router 中为知识库文件操作新增了端点,包括文件树列表、文件预览和文件下载。 - 在 workspace_api.js 中添加了相应的 API 调用,用于知识库交互。 - 增强了 AgentFilePreview 组件,支持预览变体之间的切换。 - 更新了导航逻辑,重定向至知识管理标签页,而非数据库视图。 - 修改了 WorkspaceView 以处理知识库条目和预览,包括加载和展示知识文件。 - 通过在文件操作期间提供加载与错误状态的反馈,改善了用户体验。
1774 lines
48 KiB
Vue
1774 lines
48 KiB
Vue
<template>
|
||
<div class="agent-config-sidebar" :class="{ open: isOpen }">
|
||
<!-- 侧边栏头部 -->
|
||
<div class="sidebar-header">
|
||
<div class="header-top-row">
|
||
<div v-if="selectedAgentId" class="config-title-row">
|
||
<span class="config-title-text">{{ currentConfigName }}</span>
|
||
</div>
|
||
<div class="header-actions">
|
||
<a-tooltip
|
||
v-if="!isEmptyConfig && userStore.isAdmin"
|
||
:title="isCurrentDefault ? '当前已是默认配置' : '设为默认配置'"
|
||
>
|
||
<button
|
||
type="button"
|
||
class="header-icon-action"
|
||
:class="{ 'is-default': isCurrentDefault }"
|
||
@click="setAsDefault"
|
||
>
|
||
<Star :size="18" :fill="isCurrentDefault ? 'currentColor' : 'none'" />
|
||
</button>
|
||
</a-tooltip>
|
||
|
||
<button type="button" @click="closeSidebar" class="header-icon-action">
|
||
<X :size="16" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 侧边栏内容 -->
|
||
<div class="sidebar-content">
|
||
<div class="agent-info" v-if="selectedAgent">
|
||
<!-- <div class="agent-basic-info">
|
||
<p class="agent-description">{{ selectedAgent.description }}</p>
|
||
</div> -->
|
||
|
||
<!-- <a-divider /> -->
|
||
|
||
<div class="config-segment" v-if="!isEmptyConfig">
|
||
<a-segmented v-model:value="currentSegment" :options="segmentOptions" block />
|
||
</div>
|
||
|
||
<div
|
||
v-if="selectedAgentId && configurableItems"
|
||
class="config-form-content"
|
||
:class="{ 'is-readonly': isReadOnlyConfig }"
|
||
>
|
||
<!-- 配置表单 -->
|
||
<a-form :model="agentConfig" layout="vertical" class="config-form">
|
||
<a-alert
|
||
v-if="isEmptyConfig"
|
||
type="warning"
|
||
message="该智能体没有配置项"
|
||
show-icon
|
||
class="config-alert"
|
||
/>
|
||
<!-- 统一显示所有配置项 -->
|
||
<template v-for="(value, key) in filteredConfigurableItems" :key="key">
|
||
<a-form-item :label="getConfigLabel(key, value)" :name="key" class="config-item">
|
||
<p v-if="value.description" class="config-description">{{ value.description }}</p>
|
||
|
||
<!-- <div>{{ value }}</div> -->
|
||
<!-- 模型选择 -->
|
||
<div
|
||
v-if="value.kind === 'llm'"
|
||
class="model-selector"
|
||
:class="{ 'is-readonly': isReadOnlyConfig }"
|
||
>
|
||
<ModelSelectorComponent
|
||
@select-model="(spec) => handleModelChange(key, spec)"
|
||
:model_spec="agentConfig[key] || ''"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 系统提示词 -->
|
||
<div
|
||
v-else-if="value.kind === 'prompt'"
|
||
class="system-prompt-container"
|
||
>
|
||
<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">
|
||
{{ isReadOnlyConfig ? '查看' : '点击查看并编辑' }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 布尔类型 -->
|
||
<a-switch
|
||
v-else-if="typeof agentConfig[key] === 'boolean'"
|
||
:checked="agentConfig[key]"
|
||
:disabled="isReadOnlyConfig"
|
||
@update:checked="(val) => updateConfigValue(key, val)"
|
||
/>
|
||
|
||
<!-- 单选 -->
|
||
<a-select
|
||
v-else-if="
|
||
value?.options.length > 0 && (value?.type === 'str' || value?.type === 'select')
|
||
"
|
||
:value="agentConfig[key]"
|
||
:disabled="isReadOnlyConfig"
|
||
@update:value="(val) => updateConfigValue(key, val)"
|
||
class="config-select"
|
||
>
|
||
<a-select-option v-for="option in value.options" :key="option" :value="option">
|
||
{{ option.label || option }}
|
||
</a-select-option>
|
||
</a-select>
|
||
|
||
<!-- 多选 / 工具列表 (统一处理) -->
|
||
<div v-else-if="isListConfig(key, value)" class="list-config-container">
|
||
<!-- Case 1: <= 5 options, inline list -->
|
||
<div v-if="getConfigOptions(value).length <= 5" class="multi-select-cards">
|
||
<div class="multi-select-label">
|
||
<span
|
||
>已选择 {{ getSelectedCount(key) }} 项 | 共
|
||
{{ getConfigOptions(value).length }} 项</span
|
||
>
|
||
<div v-if="!isReadOnlyConfig" class="label-actions">
|
||
<a-button
|
||
type="link"
|
||
size="small"
|
||
class="clear-btn"
|
||
@click="clearSelection(key)"
|
||
v-if="getSelectedCount(key) > 0"
|
||
>
|
||
清空
|
||
</a-button>
|
||
<template v-if="isToolsKind(value.kind)">
|
||
<a-divider type="vertical" />
|
||
<a-button
|
||
type="link"
|
||
size="small"
|
||
@click="refreshConfigOptions(key, value.kind)"
|
||
class="inline-action-btn lucide-icon-btn"
|
||
>
|
||
<RotateCw :size="12" />
|
||
刷新
|
||
</a-button>
|
||
<a-button
|
||
type="link"
|
||
size="small"
|
||
@click="navigateToConfigPage(value.kind)"
|
||
class="inline-action-btn lucide-icon-btn"
|
||
>
|
||
<Settings :size="12" />
|
||
配置
|
||
</a-button>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="options-grid">
|
||
<div
|
||
v-for="option in isReadOnlyConfig
|
||
? getConfigOptions(value).filter((opt) =>
|
||
isOptionSelected(key, getOptionValue(opt))
|
||
)
|
||
: getConfigOptions(value)"
|
||
:key="getOptionValue(option)"
|
||
class="option-card"
|
||
:class="{
|
||
selected: isOptionSelected(key, getOptionValue(option)),
|
||
unselected: !isOptionSelected(key, getOptionValue(option)),
|
||
readonly: isReadOnlyConfig
|
||
}"
|
||
@click="!isReadOnlyConfig && toggleOption(key, getOptionValue(option))"
|
||
>
|
||
<div class="option-content">
|
||
<span class="option-text">{{ getOptionLabel(option) }}</span>
|
||
<div class="option-indicator">
|
||
<Check
|
||
v-if="isOptionSelected(key, getOptionValue(option))"
|
||
:size="16"
|
||
/>
|
||
<Plus v-else :size="16" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Case 2: > 5 options, Modal trigger -->
|
||
|
||
<div v-else class="selection-container">
|
||
<div class="selection-summary">
|
||
<div class="selection-summary-info">
|
||
<span class="selection-count"
|
||
>已选择 {{ getSelectedCount(key) }} 项 | 共
|
||
{{ getConfigOptions(value).length }} 项</span
|
||
>
|
||
|
||
<a-button
|
||
v-if="!isReadOnlyConfig && getSelectedCount(key) > 0"
|
||
type="link"
|
||
size="small"
|
||
class="clear-btn"
|
||
@click="clearSelection(key)"
|
||
>
|
||
清空
|
||
</a-button>
|
||
</div>
|
||
|
||
<a-button
|
||
v-if="!isReadOnlyConfig"
|
||
type="primary"
|
||
size="small"
|
||
class="selection-trigger-btn"
|
||
@click="openSelectionModal(key)"
|
||
>
|
||
选择...
|
||
</a-button>
|
||
</div>
|
||
|
||
<!-- Selected Preview Tags -->
|
||
|
||
<div v-if="getSelectedCount(key) > 0" class="selection-preview">
|
||
<a-tag
|
||
v-for="val in agentConfig[key]"
|
||
:key="val"
|
||
:closable="!isReadOnlyConfig"
|
||
@close="toggleOption(key, val)"
|
||
class="selection-tag"
|
||
>
|
||
{{ getOptionLabelFromValue(key, val) }}
|
||
</a-tag>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 数字 -->
|
||
<a-input-number
|
||
v-else-if="
|
||
value?.type === 'number' || value?.type === 'int' || value?.type === 'float'
|
||
"
|
||
:value="agentConfig[key]"
|
||
:disabled="isReadOnlyConfig"
|
||
@update:value="(val) => updateConfigValue(key, val)"
|
||
:placeholder="getPlaceholder(key, value)"
|
||
class="config-input-number"
|
||
/>
|
||
|
||
<!-- 滑块 -->
|
||
<a-slider
|
||
v-else-if="value?.type === 'slider'"
|
||
:value="agentConfig[key]"
|
||
:disabled="isReadOnlyConfig"
|
||
@update:value="(val) => updateConfigValue(key, val)"
|
||
:min="value.min"
|
||
:max="value.max"
|
||
:step="value.step"
|
||
class="config-slider"
|
||
/>
|
||
|
||
<!-- 其他类型 -->
|
||
<a-input
|
||
v-else
|
||
:value="agentConfig[key]"
|
||
:disabled="isReadOnlyConfig"
|
||
@update:value="(val) => updateConfigValue(key, val)"
|
||
:placeholder="getPlaceholder(key, value)"
|
||
class="config-input"
|
||
/>
|
||
</a-form-item>
|
||
</template>
|
||
</a-form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 固定在底部的操作按钮 -->
|
||
<div class="sidebar-footer" v-if="userStore.isAdmin && selectedAgentId">
|
||
<div class="form-actions">
|
||
<a-button
|
||
type="primary"
|
||
@click="saveConfig"
|
||
class="footer-main-btn save-btn"
|
||
:class="{ changed: agentStore.hasConfigChanges }"
|
||
:disabled="isSavingConfig || isEmptyConfig"
|
||
>
|
||
保存
|
||
</a-button>
|
||
|
||
<a-tooltip v-if="!isEmptyConfig" title="删除配置">
|
||
<button
|
||
type="button"
|
||
class="footer-icon-btn"
|
||
@click="confirmDeleteConfig"
|
||
:disabled="isDeletingConfig"
|
||
>
|
||
<Trash2 :size="16" />
|
||
</button>
|
||
</a-tooltip>
|
||
</div>
|
||
</div>
|
||
|
||
<a-modal
|
||
v-model:open="selectionModalOpen"
|
||
:title="`选择${configurableItems[currentConfigKey]?.name || '项目'}`"
|
||
:width="800"
|
||
:footer="null"
|
||
:maskClosable="false"
|
||
class="selection-modal"
|
||
>
|
||
<div class="selection-modal-content">
|
||
<div class="selection-search">
|
||
<a-input
|
||
v-model:value="selectionSearchText"
|
||
placeholder="搜索..."
|
||
allow-clear
|
||
class="search-input"
|
||
>
|
||
<template #prefix>
|
||
<Search :size="16" class="search-icon" />
|
||
</template>
|
||
</a-input>
|
||
<template v-if="!isReadOnlyConfig && isToolsKind(currentConfigKind)">
|
||
<a-button
|
||
type="text"
|
||
size="small"
|
||
@click="refreshConfigOptions(currentConfigKey, currentConfigKind)"
|
||
class="inline-action-btn lucide-icon-btn"
|
||
title="刷新列表"
|
||
>
|
||
<RotateCw :size="14" />
|
||
刷新
|
||
</a-button>
|
||
<a-button
|
||
type="text"
|
||
size="small"
|
||
@click="navigateToConfigPage(currentConfigKind)"
|
||
class="inline-action-btn lucide-icon-btn"
|
||
title="跳转配置"
|
||
>
|
||
<Settings :size="14" />
|
||
配置
|
||
</a-button>
|
||
</template>
|
||
</div>
|
||
|
||
<div class="selection-list">
|
||
<div
|
||
v-for="option in filteredOptions"
|
||
:key="getOptionValue(option)"
|
||
class="selection-item"
|
||
:class="{ selected: tempSelectedValues.includes(getOptionValue(option)) }"
|
||
@click="!isReadOnlyConfig && toggleModalSelection(getOptionValue(option))"
|
||
>
|
||
<div class="selection-item-content">
|
||
<div class="selection-item-header">
|
||
<span class="selection-item-name">{{ getOptionLabel(option) }}</span>
|
||
|
||
<div class="selection-item-indicator">
|
||
<Check v-if="tempSelectedValues.includes(getOptionValue(option))" :size="16" />
|
||
|
||
<Plus v-else :size="16" />
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="getOptionDescription(option)" class="selection-item-description">
|
||
{{ getOptionDescription(option) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="selection-modal-footer">
|
||
<div class="selected-count">已选择 {{ tempSelectedValues.length }} 项</div>
|
||
|
||
<div class="modal-actions">
|
||
<a-button @click="closeSelectionModal">取消</a-button>
|
||
|
||
<a-button v-if="!isReadOnlyConfig" type="primary" @click="confirmSelection">
|
||
确认
|
||
</a-button>
|
||
</div>
|
||
</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"
|
||
:disabled="isReadOnlyConfig"
|
||
:placeholder="systemPromptModalPlaceholder"
|
||
class="system-prompt-modal-input"
|
||
/>
|
||
</div>
|
||
|
||
<template #footer>
|
||
<a-button @click="closeSystemPromptModal">{{
|
||
isReadOnlyConfig ? '关闭' : '取消'
|
||
}}</a-button>
|
||
<a-button v-if="!isReadOnlyConfig" type="primary" @click="saveSystemPrompt">
|
||
保存
|
||
</a-button>
|
||
</template>
|
||
</a-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
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'
|
||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue'
|
||
import { useAgentStore } from '@/stores/agent'
|
||
import { useUserStore } from '@/stores/user'
|
||
import { useDatabaseStore } from '@/stores/database'
|
||
import { mcpApi } from '@/apis/mcp_api'
|
||
import { skillApi } from '@/apis/skill_api'
|
||
import { subagentApi } from '@/apis/subagent_api'
|
||
import { toolApi } from '@/apis/tool_api'
|
||
import { storeToRefs } from 'pinia'
|
||
|
||
// Props
|
||
const props = defineProps({
|
||
isOpen: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
disabled: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
})
|
||
|
||
// Emits
|
||
const emit = defineEmits(['close'])
|
||
|
||
// Store 管理
|
||
const agentStore = useAgentStore()
|
||
const userStore = useUserStore()
|
||
const databaseStore = useDatabaseStore()
|
||
const router = useRouter()
|
||
|
||
watch(
|
||
() => props.isOpen,
|
||
async (val) => {
|
||
if (val) {
|
||
// 强制刷新以获取最新数据
|
||
databaseStore.loadDatabases(true).catch(() => {})
|
||
loadLiveSkillOptions(true).catch(() => {})
|
||
loadMcpOptions(true).catch(() => {})
|
||
loadSubagentOptions(true).catch(() => {})
|
||
loadToolOptions(true).catch(() => {})
|
||
if (selectedAgentId.value) {
|
||
agentStore.fetchAgentConfigs(selectedAgentId.value).catch((error) => {
|
||
console.error('刷新智能体配置列表失败:', error)
|
||
})
|
||
try {
|
||
await agentStore.fetchAgentDetail(selectedAgentId.value, true)
|
||
} catch (error) {
|
||
console.error('刷新智能体配置项失败:', error)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
)
|
||
|
||
const {
|
||
selectedAgent,
|
||
selectedAgentId,
|
||
selectedAgentConfigId,
|
||
selectedConfigSummary,
|
||
agentConfigs,
|
||
agentConfig,
|
||
configurableItems
|
||
} = storeToRefs(agentStore)
|
||
|
||
// console.log(availableTools.value)
|
||
|
||
// 本地状态
|
||
const selectionModalOpen = ref(false)
|
||
const currentConfigKey = ref(null)
|
||
const tempSelectedValues = ref([])
|
||
const selectionSearchText = ref('')
|
||
const systemPromptModalOpen = ref(false)
|
||
const currentSystemPromptKey = ref(null)
|
||
const systemPromptDraft = ref('')
|
||
const liveSkillOptions = ref([])
|
||
const liveMcpOptions = ref([])
|
||
const liveSubagentOptions = ref([])
|
||
const toolOptionsFromApi = ref([])
|
||
const currentSegment = ref('model')
|
||
const segmentOptions = [
|
||
{ label: '模型', value: 'model' },
|
||
{ label: '工具', value: 'tools' },
|
||
{ label: '其他', value: 'other' }
|
||
]
|
||
|
||
const isEmptyConfig = computed(() => {
|
||
return !selectedAgentId.value || Object.keys(configurableItems.value).length === 0
|
||
})
|
||
|
||
const isCurrentDefault = computed(() => {
|
||
return !!selectedConfigSummary.value?.is_default
|
||
})
|
||
|
||
const currentConfigName = computed(() => {
|
||
return selectedConfigSummary.value?.name || '当前配置'
|
||
})
|
||
|
||
const isReadOnlyConfig = computed(() => !userStore.isAdmin)
|
||
|
||
const isSavingConfig = ref(false)
|
||
const isDeletingConfig = ref(false)
|
||
|
||
const segmentConfigKeys = computed(() => {
|
||
const keys = Object.keys(configurableItems.value)
|
||
return {
|
||
model: keys.filter((key) => {
|
||
const meta = configurableItems.value[key]?.kind
|
||
return meta === 'llm' || meta === 'prompt'
|
||
}),
|
||
tools: keys.filter((key) => {
|
||
const meta = configurableItems.value[key]?.kind
|
||
return ['tools', 'knowledges', 'mcps', 'skills', 'subagents'].includes(meta)
|
||
}),
|
||
other: keys.filter((key) => {
|
||
const meta = configurableItems.value[key]?.kind
|
||
return !['llm', 'prompt', 'tools', 'knowledges', 'mcps', 'skills', 'subagents'].includes(meta)
|
||
})
|
||
}
|
||
})
|
||
|
||
const filteredConfigurableItems = computed(() => {
|
||
if (isEmptyConfig.value) return {}
|
||
const keys = segmentConfigKeys.value[currentSegment.value] || []
|
||
const filtered = {}
|
||
keys.forEach((key) => {
|
||
filtered[key] = configurableItems.value[key]
|
||
})
|
||
return filtered
|
||
})
|
||
|
||
const loadLiveSkillOptions = async (force = false) => {
|
||
if (!userStore.isAdmin) {
|
||
liveSkillOptions.value = []
|
||
return
|
||
}
|
||
// 如果不是强制刷新且已有数据,则跳过
|
||
if (!force && liveSkillOptions.value.length > 0) {
|
||
return
|
||
}
|
||
try {
|
||
const result = await skillApi.listSkills()
|
||
const rows = result?.data || []
|
||
liveSkillOptions.value = rows.map((item) => ({
|
||
id: item.slug,
|
||
name: item.slug,
|
||
description: item.description || ''
|
||
}))
|
||
} catch (error) {
|
||
console.warn('加载实时 Skills 列表失败:', error)
|
||
}
|
||
}
|
||
|
||
const loadMcpOptions = async (force = false) => {
|
||
if (!userStore.isAdmin) {
|
||
liveMcpOptions.value = []
|
||
return
|
||
}
|
||
if (!force && liveMcpOptions.value.length > 0) {
|
||
return
|
||
}
|
||
try {
|
||
const result = await mcpApi.getMcpServers()
|
||
const rows = result?.data || []
|
||
liveMcpOptions.value = rows
|
||
.filter((item) => item?.enabled !== false)
|
||
.map((item) => ({
|
||
id: item.name,
|
||
name: item.name,
|
||
description: item.description || ''
|
||
}))
|
||
} catch (error) {
|
||
console.warn('加载 MCP 列表失败:', error)
|
||
}
|
||
}
|
||
|
||
const loadToolOptions = async (force = false) => {
|
||
if (!userStore.isAdmin) {
|
||
toolOptionsFromApi.value = []
|
||
return
|
||
}
|
||
// 如果不是强制刷新且已有数据,则跳过
|
||
if (!force && toolOptionsFromApi.value.length > 0) {
|
||
return
|
||
}
|
||
try {
|
||
const result = await toolApi.getTools()
|
||
toolOptionsFromApi.value = (result?.data || []).map((item) => ({
|
||
id: item.id,
|
||
name: item.name || item.id,
|
||
description: item.description || ''
|
||
}))
|
||
} catch (error) {
|
||
console.warn('加载工具列表失败:', error)
|
||
}
|
||
}
|
||
|
||
const loadSubagentOptions = async (force = false) => {
|
||
if (!userStore.isAdmin) {
|
||
liveSubagentOptions.value = []
|
||
return
|
||
}
|
||
if (!force && liveSubagentOptions.value.length > 0) {
|
||
return
|
||
}
|
||
try {
|
||
const result = await subagentApi.getSubAgents()
|
||
const rows = result?.data || []
|
||
liveSubagentOptions.value = rows
|
||
.filter((item) => item?.enabled !== false)
|
||
.map((item) => ({
|
||
id: item.name,
|
||
name: item.name,
|
||
description: item.description || ''
|
||
}))
|
||
} catch (error) {
|
||
console.warn('加载 Subagents 列表失败:', error)
|
||
}
|
||
}
|
||
|
||
// 判断是否为需要跳转的配置类型
|
||
const isToolsKind = (kind) => {
|
||
return ['knowledges', 'tools', 'mcps', 'skills', 'subagents'].includes(kind)
|
||
}
|
||
|
||
// 强制刷新对应配置项的选项列表
|
||
const refreshConfigOptions = async (_key, kind) => {
|
||
if (isReadOnlyConfig.value) return
|
||
try {
|
||
switch (kind) {
|
||
case 'knowledges':
|
||
await databaseStore.loadDatabases(true)
|
||
message.success('知识库列表已刷新')
|
||
break
|
||
case 'tools':
|
||
await loadToolOptions(true)
|
||
message.success('工具列表已刷新')
|
||
break
|
||
case 'skills':
|
||
await loadLiveSkillOptions(true)
|
||
message.success('Skills 列表已刷新')
|
||
break
|
||
case 'subagents':
|
||
await loadSubagentOptions(true)
|
||
message.success('Subagents 列表已刷新')
|
||
break
|
||
case 'mcps':
|
||
await loadMcpOptions(true)
|
||
message.success('MCP 列表已刷新')
|
||
break
|
||
}
|
||
} catch (error) {
|
||
console.error('刷新配置选项失败:', error)
|
||
message.error('刷新失败')
|
||
}
|
||
}
|
||
|
||
// 跳转到对应管理页面
|
||
const navigateToConfigPage = (kind) => {
|
||
if (isReadOnlyConfig.value) return
|
||
// 先关闭选择弹窗
|
||
closeSelectionModal()
|
||
// 延迟跳转,确保弹窗先关闭
|
||
setTimeout(() => {
|
||
switch (kind) {
|
||
case 'knowledges':
|
||
router.push({ path: '/extensions', query: { tab: 'knowledge' } })
|
||
break
|
||
case 'tools':
|
||
router.push({ path: '/extensions', query: { tab: 'tools' } })
|
||
break
|
||
case 'mcps':
|
||
router.push({ path: '/extensions', query: { tab: 'mcp' } })
|
||
break
|
||
case 'skills':
|
||
router.push({ path: '/extensions', query: { tab: 'skills' } })
|
||
break
|
||
case 'subagents':
|
||
router.push({ path: '/extensions', query: { tab: 'subagents' } })
|
||
break
|
||
}
|
||
}, 100)
|
||
}
|
||
|
||
// 通用选项获取与处理
|
||
const resolveOptionValue = (option) => {
|
||
if (typeof option === 'object' && option !== null) {
|
||
return option.id || option.value || option.name || option.db_id || option.slug
|
||
}
|
||
return option
|
||
}
|
||
|
||
const getConfigOptions = (value) => {
|
||
if (value?.kind === 'tools') {
|
||
return toolOptionsFromApi.value || []
|
||
}
|
||
if (value?.kind === 'knowledges') {
|
||
return databaseStore.databases || []
|
||
}
|
||
if (value?.kind === 'mcps') {
|
||
return liveMcpOptions.value || []
|
||
}
|
||
if (value?.kind === 'skills') {
|
||
return liveSkillOptions.value.length > 0 ? liveSkillOptions.value : value?.options || []
|
||
}
|
||
if (value?.kind === 'subagents') {
|
||
return liveSubagentOptions.value || []
|
||
}
|
||
return value?.options || []
|
||
}
|
||
|
||
const isListConfig = (key, value) => {
|
||
const isTools = value?.kind === 'tools'
|
||
const isList = value?.type === 'list'
|
||
return isTools || isList || key === 'skills' || key === 'subagents'
|
||
}
|
||
|
||
const getOptionValue = (option) => {
|
||
return resolveOptionValue(option)
|
||
}
|
||
|
||
const getOptionLabel = (option) => {
|
||
if (typeof option === 'object' && option !== null) {
|
||
return option.name || option.label || option.id || option.db_id || option.slug
|
||
}
|
||
return option
|
||
}
|
||
|
||
const getOptionDescription = (option) => {
|
||
if (typeof option === 'object' && option !== null) {
|
||
return option.description || '暂无描述'
|
||
}
|
||
return null
|
||
}
|
||
|
||
const currentConfigKind = computed(() => {
|
||
if (!currentConfigKey.value) return null
|
||
return configurableItems.value[currentConfigKey.value]?.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
|
||
const configItem = configurableItems.value[key]
|
||
const options = getConfigOptions(configItem)
|
||
|
||
if (!selectionSearchText.value) return options
|
||
|
||
const search = selectionSearchText.value.toLowerCase()
|
||
return options.filter((opt) => {
|
||
const label = String(getOptionLabel(opt)).toLowerCase()
|
||
const desc = String(getOptionDescription(opt) || '').toLowerCase()
|
||
return label.includes(search) || desc.includes(search)
|
||
})
|
||
})
|
||
|
||
// 方法
|
||
const updateConfigValue = (key, value) => {
|
||
if (isReadOnlyConfig.value) return
|
||
agentStore.updateAgentConfig({
|
||
[key]: value
|
||
})
|
||
}
|
||
|
||
const closeSidebar = () => {
|
||
emit('close')
|
||
}
|
||
|
||
const getConfigLabel = (key, value) => {
|
||
// console.log(configurableItems)
|
||
if (value.description && value.name !== key) {
|
||
return `${value.name}`
|
||
// return `${value.name}(${key})`;
|
||
}
|
||
return key
|
||
}
|
||
|
||
const getPlaceholder = (_key, value) => {
|
||
return `(默认: ${value.default})`
|
||
}
|
||
|
||
const handleModelChange = (key, spec) => {
|
||
if (isReadOnlyConfig.value) return
|
||
if (typeof spec !== 'string' || !spec) return
|
||
agentStore.updateAgentConfig({
|
||
[key]: spec
|
||
})
|
||
}
|
||
|
||
// 多选相关方法
|
||
const ensureArray = (key) => {
|
||
const config = agentConfig.value || {}
|
||
if (
|
||
config[key] === null &&
|
||
configurableItems.value[key]?.template_metadata?.kind === 'knowledges'
|
||
) {
|
||
return getConfigOptions(configurableItems.value[key]).map((option) => getOptionValue(option))
|
||
}
|
||
if (!config[key] || !Array.isArray(config[key])) {
|
||
return []
|
||
}
|
||
return config[key]
|
||
}
|
||
|
||
const isOptionSelected = (key, option) => {
|
||
const currentOptions = ensureArray(key)
|
||
return currentOptions.includes(option)
|
||
}
|
||
|
||
const getSelectedCount = (key) => {
|
||
const currentOptions = ensureArray(key)
|
||
return currentOptions.length
|
||
}
|
||
|
||
const toggleOption = (key, option) => {
|
||
if (isReadOnlyConfig.value) return
|
||
const currentOptions = [...ensureArray(key)]
|
||
const index = currentOptions.indexOf(option)
|
||
|
||
if (index > -1) {
|
||
currentOptions.splice(index, 1)
|
||
} else {
|
||
currentOptions.push(option)
|
||
}
|
||
|
||
agentStore.updateAgentConfig({
|
||
[key]: currentOptions
|
||
})
|
||
}
|
||
|
||
const clearSelection = (key) => {
|
||
if (isReadOnlyConfig.value) return
|
||
agentStore.updateAgentConfig({
|
||
[key]: []
|
||
})
|
||
}
|
||
|
||
// 统一选择弹窗相关方法
|
||
const getOptionLabelFromValue = (key, val) => {
|
||
const options = getConfigOptions(configurableItems.value[key])
|
||
const option = options.find((opt) => getOptionValue(opt) === val)
|
||
return option ? getOptionLabel(option) : val
|
||
}
|
||
|
||
const openSelectionModal = async (key) => {
|
||
if (isReadOnlyConfig.value) return
|
||
currentConfigKey.value = key
|
||
// 如果是工具,从 API 刷新工具列表
|
||
if (configurableItems.value[key]?.kind === 'tools') {
|
||
await loadToolOptions()
|
||
}
|
||
// 如果是知识库,需要获取知识库列表
|
||
if (configurableItems.value[key]?.kind === 'knowledges') {
|
||
try {
|
||
await databaseStore.loadDatabases()
|
||
} catch (error) {
|
||
console.error('加载知识库列表失败:', error)
|
||
}
|
||
}
|
||
if (configurableItems.value[key]?.kind === 'skills') {
|
||
await loadLiveSkillOptions()
|
||
}
|
||
if (configurableItems.value[key]?.kind === 'subagents') {
|
||
await loadSubagentOptions()
|
||
}
|
||
tempSelectedValues.value = [...ensureArray(key)]
|
||
selectionModalOpen.value = true
|
||
}
|
||
|
||
const toggleModalSelection = (optionValue) => {
|
||
if (isReadOnlyConfig.value) return
|
||
const index = tempSelectedValues.value.indexOf(optionValue)
|
||
if (index > -1) {
|
||
tempSelectedValues.value.splice(index, 1)
|
||
} else {
|
||
tempSelectedValues.value.push(optionValue)
|
||
}
|
||
}
|
||
|
||
const confirmSelection = () => {
|
||
if (isReadOnlyConfig.value) {
|
||
closeSelectionModal()
|
||
return
|
||
}
|
||
if (currentConfigKey.value) {
|
||
agentStore.updateAgentConfig({
|
||
[currentConfigKey.value]: [...tempSelectedValues.value]
|
||
})
|
||
}
|
||
closeSelectionModal()
|
||
}
|
||
|
||
const closeSelectionModal = () => {
|
||
selectionModalOpen.value = false
|
||
currentConfigKey.value = null
|
||
tempSelectedValues.value = []
|
||
selectionSearchText.value = ''
|
||
}
|
||
|
||
// 系统提示词弹窗编辑相关方法
|
||
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 (isReadOnlyConfig.value) return
|
||
if (!currentSystemPromptKey.value) return
|
||
agentStore.updateAgentConfig({
|
||
[currentSystemPromptKey.value]: systemPromptDraft.value
|
||
})
|
||
closeSystemPromptModal()
|
||
}
|
||
|
||
// 验证和过滤配置项
|
||
const validateAndFilterConfig = () => {
|
||
const validatedConfig = { ...agentConfig.value }
|
||
const configItems = configurableItems.value
|
||
|
||
// 遍历所有配置项
|
||
Object.keys(configItems).forEach((key) => {
|
||
const configItem = configItems[key]
|
||
const currentValue = validatedConfig[key]
|
||
|
||
if (
|
||
Array.isArray(currentValue) &&
|
||
(configItem.kind === 'tools' || configItem.type === 'list')
|
||
) {
|
||
const options = getConfigOptions(configItem)
|
||
const validValues = new Set(options.map((opt) => String(getOptionValue(opt))))
|
||
if (validValues.size === 0) return
|
||
|
||
validatedConfig[key] = currentValue.filter((value) => validValues.has(String(value)))
|
||
if (validatedConfig[key].length !== currentValue.length) {
|
||
console.warn(`配置项 ${key} 中包含无效选项,已自动过滤`)
|
||
}
|
||
}
|
||
})
|
||
|
||
return validatedConfig
|
||
}
|
||
|
||
// 配置保存和重置
|
||
const saveConfig = async () => {
|
||
if (!selectedAgentId.value) {
|
||
message.error('没有选择智能体')
|
||
return
|
||
}
|
||
|
||
if (!agentStore.hasConfigChanges) return
|
||
|
||
try {
|
||
isSavingConfig.value = true
|
||
// 验证和过滤配置
|
||
const validatedConfig = validateAndFilterConfig()
|
||
|
||
// 如果配置有变化,先更新到store
|
||
if (JSON.stringify(validatedConfig) !== JSON.stringify(agentConfig.value)) {
|
||
agentStore.updateAgentConfig(validatedConfig)
|
||
message.info('检测到无效配置项,已自动过滤')
|
||
}
|
||
|
||
await agentStore.saveAgentConfig()
|
||
message.success('配置已保存到服务器')
|
||
} catch (error) {
|
||
console.error('保存配置到服务器出错:', error)
|
||
message.error('保存配置到服务器失败')
|
||
} finally {
|
||
isSavingConfig.value = false
|
||
}
|
||
}
|
||
|
||
const setAsDefault = async () => {
|
||
if (!selectedAgentId.value || !selectedAgentConfigId.value) return
|
||
try {
|
||
await agentStore.setSelectedAgentConfigDefault()
|
||
message.success('已设为默认配置')
|
||
} catch (error) {
|
||
console.error('设置默认配置出错:', error)
|
||
message.error('设置默认配置失败')
|
||
}
|
||
}
|
||
|
||
const confirmDeleteConfig = async () => {
|
||
if (!selectedAgentId.value || !selectedAgentConfigId.value) return
|
||
|
||
const currentName = selectedConfigSummary.value?.name || '当前配置'
|
||
const list = agentConfigs.value[selectedAgentId.value] || []
|
||
const content =
|
||
list.length <= 1
|
||
? `将删除「${currentName}」。删除后系统会自动创建一个新的默认配置。`
|
||
: `将删除「${currentName}」。`
|
||
|
||
Modal.confirm({
|
||
title: '确认删除配置?',
|
||
content,
|
||
okText: '删除',
|
||
okType: 'danger',
|
||
cancelText: '取消',
|
||
onOk: async () => {
|
||
isDeletingConfig.value = true
|
||
try {
|
||
await agentStore.deleteSelectedAgentConfigProfile()
|
||
message.success('配置已删除')
|
||
} catch (error) {
|
||
console.error('删除配置出错:', error)
|
||
message.error('删除配置失败')
|
||
} finally {
|
||
isDeletingConfig.value = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.agent-config-sidebar {
|
||
position: relative;
|
||
width: 0;
|
||
height: 100vh;
|
||
background: var(--gray-0);
|
||
border-left: 1px solid var(--gray-200);
|
||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-shrink: 0;
|
||
|
||
&.open {
|
||
width: 360px;
|
||
}
|
||
|
||
.sidebar-header {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 12px;
|
||
height: var(--header-height);
|
||
border-bottom: 1px solid var(--gray-150);
|
||
background: var(--gray-0);
|
||
flex-shrink: 0;
|
||
min-width: 360px;
|
||
z-index: 10;
|
||
|
||
.header-top-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
|
||
.config-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.config-title-text {
|
||
min-width: 0;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--gray-900);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
margin-left: auto;
|
||
}
|
||
}
|
||
|
||
.header-icon-action {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 28px;
|
||
height: 28px;
|
||
padding: 0;
|
||
border: none;
|
||
background: transparent;
|
||
border-radius: 6px;
|
||
color: var(--gray-600);
|
||
transition:
|
||
color 0.2s ease,
|
||
background-color 0.2s ease;
|
||
cursor: pointer;
|
||
|
||
&:hover:not(:disabled) {
|
||
color: var(--main-600);
|
||
background: var(--gray-100);
|
||
}
|
||
|
||
&.is-default,
|
||
&.is-default:hover:not(:disabled) {
|
||
color: var(--color-warning-500);
|
||
}
|
||
|
||
&:disabled {
|
||
cursor: not-allowed;
|
||
background: transparent;
|
||
color: var(--gray-400);
|
||
|
||
&.is-default {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
.sidebar-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 10px 12px 8px;
|
||
min-width: 360px;
|
||
|
||
.agent-info {
|
||
.agent-basic-info {
|
||
.agent-description {
|
||
margin: 0 0 12px 0;
|
||
font-size: 14px;
|
||
color: var(--gray-700);
|
||
line-height: 1.5;
|
||
}
|
||
}
|
||
}
|
||
|
||
.config-segment {
|
||
margin: 0 auto;
|
||
margin-bottom: 6px;
|
||
padding: 4px 0;
|
||
width: 80%;
|
||
}
|
||
|
||
.config-form-content {
|
||
margin-bottom: 20px;
|
||
|
||
&.is-readonly {
|
||
.config-item {
|
||
background: var(--gray-20);
|
||
|
||
.model-selector.is-readonly {
|
||
opacity: 0.78;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.system-prompt-display {
|
||
cursor: default;
|
||
|
||
&:hover {
|
||
border-color: var(--gray-200);
|
||
background: transparent;
|
||
|
||
.edit-hint {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.edit-hint {
|
||
color: var(--gray-500);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.option-card.readonly {
|
||
cursor: default;
|
||
|
||
&:hover {
|
||
border-color: var(--gray-300);
|
||
background: var(--gray-0);
|
||
}
|
||
|
||
&.selected:hover {
|
||
border-color: var(--main-color);
|
||
background: var(--main-10);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.config-form {
|
||
.config-alert {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.config-item {
|
||
background-color: var(--gray-25);
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--gray-100);
|
||
// box-shadow: 0px 0px 2px var(--shadow-3);
|
||
|
||
:deep(.ant-form-item-label > label) {
|
||
font-weight: 600;
|
||
}
|
||
|
||
:deep(label.form_item_model) {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.config-description {
|
||
margin: 4px 0 8px 0;
|
||
font-size: 12px;
|
||
color: var(--gray-600);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.model-selector {
|
||
width: 100%;
|
||
}
|
||
|
||
.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;
|
||
transition: all 0.2s ease;
|
||
|
||
&:hover {
|
||
border-color: var(--main-color);
|
||
background: var(--gray-25);
|
||
|
||
.edit-hint {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.system-prompt-content {
|
||
white-space: pre-line;
|
||
word-break: break-word;
|
||
line-height: 1.5;
|
||
color: var(--gray-900);
|
||
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);
|
||
font-style: italic;
|
||
}
|
||
}
|
||
|
||
.edit-hint {
|
||
position: absolute;
|
||
top: -32px;
|
||
right: 0px;
|
||
font-size: 12px;
|
||
color: var(--main-800);
|
||
opacity: 0;
|
||
transition: opacity 0.2s ease;
|
||
background: var(--gray-0);
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
}
|
||
}
|
||
|
||
.config-select,
|
||
.config-input,
|
||
.config-input-number {
|
||
width: 100%;
|
||
}
|
||
|
||
.config-slider {
|
||
width: 100%;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.sidebar-footer {
|
||
padding: 8px 12px;
|
||
border-top: 1px solid var(--gray-100);
|
||
background: var(--gray-0);
|
||
// min-width: 400px;
|
||
z-index: 10;
|
||
flex-shrink: 0; // Ensure footer doesn't shrink
|
||
|
||
.form-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
|
||
.footer-main-btn {
|
||
flex: 1;
|
||
height: 36px;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition:
|
||
opacity 0.2s ease,
|
||
border-color 0.2s ease,
|
||
background-color 0.2s ease,
|
||
color 0.2s ease;
|
||
}
|
||
|
||
.save-btn {
|
||
background-color: var(--gray-100);
|
||
border: 1px solid var(--gray-200);
|
||
color: var(--gray-600);
|
||
|
||
&.changed {
|
||
background-color: var(--main-color);
|
||
color: var(--gray-0);
|
||
border-color: var(--main-color);
|
||
}
|
||
|
||
&:hover:not(:disabled) {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
&:disabled {
|
||
cursor: not-allowed;
|
||
background-color: var(--gray-100);
|
||
border-color: var(--gray-200);
|
||
color: var(--gray-400);
|
||
}
|
||
}
|
||
|
||
.footer-icon-btn {
|
||
width: 36px;
|
||
height: 36px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 8px;
|
||
background: var(--gray-0);
|
||
color: var(--gray-500);
|
||
cursor: pointer;
|
||
transition:
|
||
color 0.2s ease,
|
||
border-color 0.2s ease,
|
||
background-color 0.2s ease;
|
||
|
||
&:hover:not(:disabled) {
|
||
color: var(--color-error-700);
|
||
border-color: var(--color-error-100);
|
||
background: var(--color-error-50);
|
||
}
|
||
|
||
&:disabled {
|
||
cursor: not-allowed;
|
||
color: var(--gray-400);
|
||
background: var(--gray-50);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 选择器样式
|
||
.selection-container {
|
||
.selection-summary {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 4px 10px;
|
||
background: var(--gray-0);
|
||
border-radius: 8px;
|
||
border: 1px solid var(--gray-150);
|
||
margin-bottom: 8px;
|
||
|
||
.selection-summary-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 13px;
|
||
color: var(--gray-900);
|
||
|
||
.selection-count {
|
||
color: var(--gray-900);
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.selection-trigger-btn {
|
||
border-radius: 4px;
|
||
height: 28px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.selection-preview {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
|
||
.selection-tag {
|
||
margin: 0;
|
||
padding: 4px 8px;
|
||
border-radius: 8px;
|
||
background: var(--gray-150);
|
||
border: none;
|
||
color: var(--gray-900);
|
||
font-size: 12px;
|
||
|
||
:deep(.anticon-close) {
|
||
color: var(--gray-600);
|
||
margin-left: 4px;
|
||
|
||
&:hover {
|
||
color: var(--gray-900);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 多选卡片样式
|
||
.multi-select-cards {
|
||
.multi-select-label {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
font-size: 12px;
|
||
color: var(--gray-600);
|
||
|
||
.label-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
}
|
||
|
||
.options-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 8px;
|
||
}
|
||
|
||
.option-card {
|
||
border: 1px solid var(--gray-300);
|
||
border-radius: 8px;
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
background: var(--gray-0);
|
||
|
||
&:hover {
|
||
border-color: var(--main-color);
|
||
}
|
||
|
||
&.selected {
|
||
border-color: var(--main-color);
|
||
background: var(--main-10);
|
||
|
||
.option-indicator {
|
||
color: var(--main-color);
|
||
}
|
||
|
||
.option-text {
|
||
color: var(--main-color);
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
&.unselected {
|
||
.option-indicator {
|
||
color: var(--gray-400);
|
||
}
|
||
|
||
.option-text {
|
||
color: var(--gray-700);
|
||
}
|
||
}
|
||
|
||
.option-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 8px;
|
||
|
||
.option-text {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.option-indicator {
|
||
flex-shrink: 0;
|
||
font-size: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 选择弹窗样式
|
||
.selection-modal {
|
||
.selection-modal-content {
|
||
.selection-search {
|
||
margin-bottom: 16px;
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--gray-300);
|
||
height: 36px;
|
||
font-size: 14px;
|
||
transition: all 0.2s ease;
|
||
background: var(--gray-0);
|
||
|
||
.search-icon {
|
||
color: var(--gray-500);
|
||
font-size: 16px;
|
||
}
|
||
|
||
&:focus-within {
|
||
border-color: var(--main-color);
|
||
box-shadow: 0 0 0 2px rgba(1, 97, 121, 0.1);
|
||
|
||
.search-icon {
|
||
color: var(--main-color);
|
||
}
|
||
}
|
||
|
||
&:hover {
|
||
border-color: var(--gray-400);
|
||
}
|
||
}
|
||
}
|
||
|
||
.selection-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
gap: 12px;
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
border-radius: 8px;
|
||
margin-bottom: 16px;
|
||
|
||
// 在小屏幕下调整为单列布局
|
||
@media (max-width: 480px) {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.selection-item {
|
||
padding: 12px 16px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
border-radius: 8px;
|
||
background: var(--gray-0);
|
||
border: 1px solid var(--gray-200);
|
||
|
||
&:hover {
|
||
border-color: var(--gray-300);
|
||
background: var(--gray-20);
|
||
}
|
||
.selection-item-content {
|
||
.selection-item-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
|
||
.selection-item-name {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--gray-900);
|
||
line-height: 1.3;
|
||
flex: 1;
|
||
}
|
||
|
||
.selection-item-indicator {
|
||
color: var(--gray-400);
|
||
font-size: 16px;
|
||
transition: all 0.2s ease;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
.selection-item-description {
|
||
font-size: 12px;
|
||
color: var(--gray-600);
|
||
line-height: 1.4;
|
||
margin-top: 6px;
|
||
display: -webkit-box;
|
||
line-clamp: 2;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
}
|
||
|
||
&.selected {
|
||
background: var(--main-10);
|
||
border-color: var(--main-color);
|
||
|
||
.selection-item-content {
|
||
.selection-item-name {
|
||
color: var(--main-800);
|
||
}
|
||
.selection-item-indicator {
|
||
color: var(--main-800);
|
||
}
|
||
}
|
||
.selection-item-description {
|
||
color: var(--gray-900);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.selection-modal-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding-top: 16px;
|
||
border-top: 1px solid var(--gray-200);
|
||
|
||
.selected-count {
|
||
font-size: 14px;
|
||
color: var(--gray-700);
|
||
font-weight: 500;
|
||
padding: 6px 12px;
|
||
background: var(--gray-50);
|
||
border-radius: 8px;
|
||
border: 1px solid var(--gray-200);
|
||
}
|
||
|
||
.modal-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
|
||
:deep(.ant-btn) {
|
||
border-radius: 8px;
|
||
height: 36px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
padding: 0 16px;
|
||
transition: all 0.2s ease;
|
||
|
||
&.ant-btn-default {
|
||
border: 1px solid var(--gray-300);
|
||
color: var(--gray-700);
|
||
background: var(--gray-0);
|
||
|
||
&:hover {
|
||
border-color: var(--main-color);
|
||
color: var(--main-color);
|
||
}
|
||
}
|
||
|
||
&.ant-btn-primary {
|
||
background: var(--main-color);
|
||
border: none;
|
||
color: var(--gray-0);
|
||
|
||
&:hover {
|
||
background: var(--main-color);
|
||
opacity: 0.9;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.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;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--main-700);
|
||
|
||
&:hover {
|
||
color: var(--main-800);
|
||
}
|
||
}
|
||
|
||
.inline-action-btn {
|
||
padding: 2px 6px;
|
||
height: auto;
|
||
line-height: 1;
|
||
font-size: 12px;
|
||
color: var(--gray-600);
|
||
white-space: nowrap;
|
||
|
||
&:hover {
|
||
color: var(--main-color);
|
||
}
|
||
}
|
||
|
||
.selection-search .inline-action-btn {
|
||
font-size: 13px;
|
||
}
|
||
|
||
// 响应式适配
|
||
@media (max-width: 768px) {
|
||
.agent-config-sidebar.open {
|
||
width: 100%;
|
||
}
|
||
|
||
.sidebar-header,
|
||
.sidebar-content {
|
||
min-width: 100% !important;
|
||
}
|
||
}
|
||
</style>
|