节点新增字段和提示词弹窗

This commit is contained in:
2026-06-04 15:04:01 +08:00
parent 937d1e9373
commit 05a0e35891
4 changed files with 705 additions and 32 deletions

View File

@@ -37,6 +37,8 @@ export interface NodeLibraryItem {
nodeName: string;
modelType: number;
skillOption: boolean;
promptOption: boolean;
isSaveFile: boolean;
formConfig: NodeLibraryFormItem[];
modelConfig: NodeLibraryModelConfig[];
}

View File

@@ -23,6 +23,7 @@ export interface PromptListParams {
pageNum?: number;
pageSize?: number;
keyword?: string;
nodeType?: string;
}
export interface CreatePromptParams {
@@ -65,6 +66,17 @@ export function getNodeLibraryList() {
});
}
/**
* 获取提示词列表(根据节点类型)
*/
export function getPromptList(params: PromptListParams) {
return request<PromptListResponse>({
url: '/ai-agent/node/prompt/list',
method: 'get',
params,
});
}
/**
* 获取当前用户提示词列表
*/

View File

@@ -0,0 +1,242 @@
<template>
<el-dialog
v-model="visible"
title="选择提示词"
width="900px"
:close-on-click-modal="false"
destroy-on-close
@close="handleClose"
>
<div class="prompt-selector-dialog">
<div class="prompt-header">
<div class="search-bar">
<el-input v-model="searchParams.keyword" placeholder="搜索提示词内容" clearable @clear="handleSearch" @keyup.enter="handleSearch">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-button type="primary" @click="handleSearch">搜索</el-button>
</div>
</div>
<div class="prompt-content" v-loading="loading">
<el-empty v-if="!loading && promptList.length === 0" description="暂无提示词数据" :image-size="100" />
<el-table v-else :data="promptList" height="360" border stripe style="width: 100%" @row-click="handleSelectPrompt">
<el-table-column label="节点类型" width="120" prop="nodeType" />
<el-table-column label="提示词内容" prop="prompt" show-overflow-tooltip />
<el-table-column label="来源" width="80">
<template #default="{ row }">
<el-tag :type="row.sourceType === 1 ? 'success' : 'info'" size="small">
{{ row.sourceType === 1 ? '公共' : '自定义' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="选择" width="60" align="center">
<template #default="{ row }">
<el-icon v-if="selectedPrompt?.id === row.id" class="check-icon" color="#67c23a">
<CircleCheck />
</el-icon>
</template>
</el-table-column>
</el-table>
</div>
<div v-if="shouldRenderPagination" class="pagination-panel">
<div class="pagination-summary"> {{ displayTotal }} </div>
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:total="displayTotal"
:page-sizes="[10, 20, 50]"
layout="sizes, prev, pager, next, jumper"
background
:hide-on-single-page="false"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
</div>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :disabled="!selectedPrompt" @click="handleConfirm">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import { Search, CircleCheck } from '@element-plus/icons-vue';
import { getPromptList, type PromptItem } from '/@/api/settings/promptManager';
interface Props {
modelValue: boolean;
defaultPrompt?: PromptItem | null;
nodeType?: string;
}
interface Emits {
(e: 'update:modelValue', value: boolean): void;
(e: 'confirm', prompt: PromptItem): void;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
defaultPrompt: null,
nodeType: '',
});
const emit = defineEmits<Emits>();
const visible = ref(false);
const loading = ref(false);
const promptList = ref<PromptItem[]>([]);
const selectedPrompt = ref<PromptItem | null>(null);
const searchParams = reactive({ keyword: '' });
const pagination = reactive({ pageNum: 1, pageSize: 10, total: 0 });
const displayTotal = computed(() => {
if (pagination.total > 0) return pagination.total;
if (promptList.value.length > 0) return promptList.value.length;
return 0;
});
const shouldRenderPagination = computed(() => displayTotal.value > 0);
watch(
() => props.modelValue,
(val) => {
visible.value = val;
if (val) {
selectedPrompt.value = props.defaultPrompt || null;
pagination.pageNum = 1;
fetchPromptList();
}
}
);
watch(
() => props.nodeType,
() => {
if (visible.value) {
pagination.pageNum = 1;
fetchPromptList();
}
}
);
watch(visible, (val) => {
if (!val) {
emit('update:modelValue', false);
}
});
const resolvePromptList = (payload: any): PromptItem[] => {
if (Array.isArray(payload?.data?.list)) return payload.data.list;
if (Array.isArray(payload?.list)) return payload.list;
if (Array.isArray(payload?.rows)) return payload.rows;
return [];
};
const resolvePromptTotal = (payload: any, list: PromptItem[]) => {
const totalCandidates = [payload?.data?.total, payload?.total, payload?.data?.count, payload?.count];
const validTotal = totalCandidates.find((item) => typeof item === 'number' && !Number.isNaN(item));
if (typeof validTotal === 'number') return validTotal;
return list.length;
};
const fetchPromptList = async () => {
loading.value = true;
try {
const params = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
keyword: searchParams.keyword || undefined,
nodeType: props.nodeType || undefined,
};
const res = await getPromptList(params);
const list = resolvePromptList(res);
promptList.value = list;
pagination.total = resolvePromptTotal(res, list);
} catch (error) {
promptList.value = [];
pagination.total = 0;
} finally {
loading.value = false;
}
};
const handleSearch = () => {
pagination.pageNum = 1;
fetchPromptList();
};
const handlePageChange = () => {
fetchPromptList();
};
const handleSizeChange = (size: number) => {
pagination.pageSize = size;
pagination.pageNum = 1;
fetchPromptList();
};
const handleSelectPrompt = (prompt: PromptItem) => {
selectedPrompt.value = prompt;
};
const handleConfirm = () => {
if (!selectedPrompt.value) return;
emit('confirm', selectedPrompt.value);
handleClose();
};
const handleClose = () => {
visible.value = false;
selectedPrompt.value = null;
};
</script>
<style scoped lang="scss">
.prompt-selector-dialog {
display: flex;
flex-direction: column;
gap: 16px;
}
.prompt-header {
flex-shrink: 0;
}
.search-bar {
display: flex;
gap: 12px;
}
.search-bar :deep(.el-input) {
flex: 1;
}
.prompt-content {
min-height: 360px;
}
.pagination-panel {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding-top: 8px;
border-top: 1px solid var(--el-border-color-lighter);
}
.pagination-summary {
font-size: 14px;
color: var(--el-text-color-regular);
white-space: nowrap;
}
.pagination-panel :deep(.el-pagination) {
margin-left: auto;
}
</style>

File diff suppressed because it is too large Load Diff