fix: 完成系统向智能体数据链路切换

- 切换工作台、聊天历史、资源候选与公共调用到 Agent

- 加固资源绑定、删除保护及发布运行并发控制

- 隔离旧 Bot 专属服务和组件并保留兼容入口
This commit is contained in:
2026-07-31 14:24:15 +08:00
parent f0aba1eddd
commit f872eac1f9
114 changed files with 5997 additions and 874 deletions

View File

@@ -21,6 +21,7 @@ import { hasPermission } from '#/api/common/hasPermission';
import { api } from '#/api/request';
import { $t } from '#/locales';
import { router } from '#/router';
import AgentApprovalSnapshotPreview from '#/views/system/approval/components/AgentApprovalSnapshotPreview.vue';
import BotApprovalSnapshotPreview from '#/views/system/approval/components/BotApprovalSnapshotPreview.vue';
import KnowledgeApprovalSnapshotPreview from '#/views/system/approval/components/KnowledgeApprovalSnapshotPreview.vue';
import WorkflowApprovalSnapshotPreview from '#/views/system/approval/components/WorkflowApprovalSnapshotPreview.vue';
@@ -33,6 +34,7 @@ const detail = ref<any>(null);
const approvalActionLoading = ref<'approve' | 'reject' | 'revoke' | null>(null);
const resourceLabelMap: Record<string, string> = {
AGENT: $t('approval.resource.agent'),
BOT: $t('approval.resource.bot'),
KNOWLEDGE: $t('approval.resource.knowledge'),
WORKFLOW: $t('approval.resource.workflow'),
@@ -91,6 +93,13 @@ const botSnapshot = computed(() => {
return detail.value?.snapshotJson?.resourceSnapshot || null;
});
const agentSnapshot = computed(() => {
if (detail.value?.resourceType !== 'AGENT') {
return null;
}
return detail.value?.snapshotJson?.resourceSnapshot || null;
});
onMounted(() => {
void loadDetail();
});
@@ -498,8 +507,12 @@ function formatEventInfo(row: Record<string, any>) {
<div class="approval-detail__panel-header">
<h3>{{ $t('approval.section.snapshot') }}</h3>
</div>
<AgentApprovalSnapshotPreview
v-if="agentSnapshot"
:snapshot="agentSnapshot"
/>
<WorkflowApprovalSnapshotPreview
v-if="workflowSnapshot"
v-else-if="workflowSnapshot"
:title="workflowSnapshot.title"
:description="workflowSnapshot.description"
:content="workflowSnapshot.content"

View File

@@ -41,7 +41,7 @@ defineExpose({
openDialog,
});
type ResourceType = '' | 'BOT' | 'KNOWLEDGE' | 'WORKFLOW';
type ResourceType = '' | 'AGENT' | 'BOT' | 'KNOWLEDGE' | 'WORKFLOW';
type ActionType = '' | 'DELETE' | 'OFFLINE' | 'PUBLISH';
type ScopeType = 'CATEGORY' | 'DEPT';
type FlowStatus = 'DISABLED' | 'ENABLED';
@@ -88,7 +88,7 @@ interface FlowFormModel {
}
const RESOURCE_OPTIONS = [
{ label: $t('approval.resource.bot'), value: 'BOT' },
{ label: $t('approval.resource.agent'), value: 'AGENT' },
{ label: $t('approval.resource.workflow'), value: 'WORKFLOW' },
{ label: $t('approval.resource.knowledge'), value: 'KNOWLEDGE' },
];
@@ -128,6 +128,7 @@ const deptTreeOptions = ref<any[]>([]);
const roleOptions = ref<SelectOption[]>([]);
const accountOptions = ref<SelectOption[]>([]);
const categoryOptions = ref<Record<Exclude<ResourceType, ''>, SelectOption[]>>({
AGENT: [],
BOT: [],
KNOWLEDGE: [],
WORKFLOW: [],
@@ -188,7 +189,7 @@ function buildDefaultForm(): FlowFormModel {
name: '',
priority: 100,
remark: '',
resourceType: 'BOT',
resourceType: 'AGENT',
scopes: [],
status: 'ENABLED',
steps: [buildDefaultStep()],
@@ -236,7 +237,7 @@ async function openDialog(row: any = {}) {
name: res.data?.name || '',
priority: Number(res.data?.priority || 100),
remark: res.data?.remark || '',
resourceType: res.data?.resourceType || 'BOT',
resourceType: res.data?.resourceType || 'AGENT',
scopes: (res.data?.scopes || []).map((item: any) => ({
id: item.id,
includeChildren: item.includeChildren === 1,
@@ -279,8 +280,8 @@ async function ensureCategoryOptions() {
if (categoryLoaded.value) {
return;
}
const [botRes, workflowRes, knowledgeRes] = await Promise.all([
api.get('/api/v1/botCategory/list', {
const [agentRes, workflowRes, knowledgeRes] = await Promise.all([
api.get('/api/v1/agentCategory/list', {
params: { sortKey: 'sortNo', sortType: 'asc' },
}),
api.get('/api/v1/workflowCategory/list', {
@@ -291,7 +292,8 @@ async function ensureCategoryOptions() {
}),
]);
categoryOptions.value = {
BOT: normalizeCategoryOptions(botRes.data, 'categoryName'),
AGENT: normalizeCategoryOptions(agentRes.data, 'categoryName'),
BOT: [],
KNOWLEDGE: normalizeCategoryOptions(knowledgeRes.data, 'categoryName'),
WORKFLOW: normalizeCategoryOptions(workflowRes.data, 'categoryName'),
};

View File

@@ -35,6 +35,7 @@ import ApprovalFlowModal from './ApprovalFlowModal.vue';
type ApprovalTabName = 'flow' | 'initiated' | 'pending' | 'processed';
const RESOURCE_OPTIONS = [
{ label: $t('approval.resource.agent'), value: 'AGENT' },
{ label: $t('approval.resource.bot'), value: 'BOT' },
{ label: $t('approval.resource.workflow'), value: 'WORKFLOW' },
{ label: $t('approval.resource.knowledge'), value: 'KNOWLEDGE' },

View File

@@ -0,0 +1,297 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { ElButton, ElTag } from 'element-plus';
import { $t } from '#/locales';
const props = defineProps<{
snapshot?: null | Record<string, any>;
}>();
const agent = computed(() => props.snapshot || {});
const expandedPrompt = ref(false);
const modelItems = computed(() => {
const model = agent.value.modelSummary || {};
return [
{
label: $t('approval.snapshot.modelName'),
value: model.title || model.modelName,
},
];
});
const promptPreview = computed(
() =>
agent.value.promptSummary?.systemPrompt ||
agent.value.promptConfigJson?.systemPrompt ||
'',
);
const bindingGroups = computed(() => [
{
items: Array.isArray(agent.value.knowledgeSummaries)
? agent.value.knowledgeSummaries
: [],
key: 'knowledge',
label: $t('approval.snapshot.knowledgeBindings'),
},
{
items: Array.isArray(agent.value.toolSummaries)
? agent.value.toolSummaries
: [],
key: 'tool',
label: $t('approval.snapshot.agentBindings'),
},
]);
function formatValue(value?: null | number | string) {
if (value === null || value === undefined || value === '') {
return $t('approval.snapshot.notConfigured');
}
return String(value);
}
function getBindingName(item: Record<string, any>) {
return (
item.title ||
item.toolName ||
item.name ||
$t('approval.snapshot.notConfigured')
);
}
</script>
<template>
<div class="agent-snapshot">
<div class="agent-snapshot__hero">
<div class="agent-snapshot__avatar">
<img
v-if="agent.avatar"
:src="agent.avatar"
:alt="agent.name || $t('approval.resource.agent')"
/>
<span v-else>{{ $t('approval.resource.agent').slice(0, 1) }}</span>
</div>
<div class="agent-snapshot__hero-copy">
<div class="agent-snapshot__title">
{{ agent.name || $t('approval.snapshot.untitledAgent') }}
</div>
<div v-if="agent.description" class="agent-snapshot__description">
{{ agent.description }}
</div>
</div>
</div>
<section class="agent-snapshot__section">
<div class="agent-snapshot__section-title">
{{ $t('approval.snapshot.agentModelConfig') }}
</div>
<div class="agent-snapshot__info-grid">
<div
v-for="item in modelItems"
:key="item.label"
class="agent-snapshot__info-item"
>
<span>{{ item.label }}</span>
<strong>{{ formatValue(item.value) }}</strong>
</div>
</div>
</section>
<section class="agent-snapshot__section">
<div class="agent-snapshot__section-head">
<div class="agent-snapshot__section-title">
{{ $t('approval.snapshot.systemPrompt') }}
</div>
<ElButton
v-if="promptPreview"
link
type="primary"
@click="expandedPrompt = !expandedPrompt"
>
{{
expandedPrompt
? $t('approval.snapshot.collapsePrompt')
: $t('approval.snapshot.expandPrompt')
}}
</ElButton>
</div>
<div
v-if="promptPreview"
class="agent-snapshot__prompt"
:class="{ 'is-expanded': expandedPrompt }"
>
{{ promptPreview }}
</div>
<div v-else class="agent-snapshot__empty">
{{ $t('approval.snapshot.notConfigured') }}
</div>
</section>
<section class="agent-snapshot__section">
<div class="agent-snapshot__section-title">
{{ $t('approval.snapshot.agentBindings') }}
</div>
<div class="agent-snapshot__binding-grid">
<div
v-for="group in bindingGroups"
:key="group.key"
class="agent-snapshot__binding-group"
>
<div class="agent-snapshot__binding-head">
<span>{{ group.label }}</span>
<ElTag round effect="plain" type="info">
{{ group.items.length }}
</ElTag>
</div>
<div v-if="group.items.length" class="agent-snapshot__binding-list">
<div
v-for="item in group.items"
:key="`${group.key}-${item.bindingId || item.id || item.targetId || item.knowledgeId}`"
class="agent-snapshot__binding-item"
>
<span>{{ getBindingName(item) }}</span>
<ElTag
v-if="item.toolType || item.retrievalMode"
round
effect="plain"
type="primary"
>
{{ item.toolType || item.retrievalMode }}
</ElTag>
</div>
</div>
<div v-else class="agent-snapshot__empty">
{{ $t('approval.snapshot.noBindings') }}
</div>
</div>
</div>
</section>
</div>
</template>
<style scoped>
.agent-snapshot {
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.agent-snapshot__hero,
.agent-snapshot__section {
padding: var(--space-4);
border: 1px solid hsl(var(--divider-faint) / 0.64);
border-radius: var(--radius-panel);
background: hsl(var(--surface-panel) / 0.88);
}
.agent-snapshot__hero,
.agent-snapshot__section-head,
.agent-snapshot__binding-head,
.agent-snapshot__binding-item {
display: flex;
align-items: center;
}
.agent-snapshot__hero {
gap: var(--space-3);
}
.agent-snapshot__avatar {
display: grid;
width: 48px;
height: 48px;
flex: none;
place-items: center;
overflow: hidden;
border-radius: var(--radius-control);
background: hsl(var(--primary) / 0.1);
color: hsl(var(--primary));
font-weight: 600;
}
.agent-snapshot__avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.agent-snapshot__hero-copy,
.agent-snapshot__section,
.agent-snapshot__binding-group,
.agent-snapshot__binding-list {
display: flex;
min-width: 0;
flex-direction: column;
gap: var(--space-2);
}
.agent-snapshot__title,
.agent-snapshot__section-title {
color: hsl(var(--foreground));
font-weight: 600;
}
.agent-snapshot__description,
.agent-snapshot__empty {
color: hsl(var(--text-muted));
font-size: 13px;
}
.agent-snapshot__section-head,
.agent-snapshot__binding-head,
.agent-snapshot__binding-item {
justify-content: space-between;
gap: var(--space-3);
}
.agent-snapshot__info-grid,
.agent-snapshot__binding-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: var(--space-3);
}
.agent-snapshot__info-item,
.agent-snapshot__binding-group {
padding: var(--space-3);
border-radius: var(--radius-control);
background: hsl(var(--surface-contrast-soft));
}
.agent-snapshot__info-item {
display: flex;
flex-direction: column;
gap: var(--space-1);
color: hsl(var(--text-muted));
font-size: 13px;
}
.agent-snapshot__info-item strong {
color: hsl(var(--foreground));
font-weight: 500;
}
.agent-snapshot__prompt {
display: -webkit-box;
overflow: hidden;
color: hsl(var(--foreground));
font-size: 13px;
line-height: 1.6;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
white-space: pre-wrap;
}
.agent-snapshot__prompt.is-expanded {
display: block;
}
.agent-snapshot__binding-item {
min-height: 32px;
color: hsl(var(--foreground));
font-size: 13px;
}
</style>

View File

@@ -17,7 +17,13 @@ defineExpose({
openDialog,
});
type ResourceType = 'BOT' | 'KNOWLEDGE' | 'PLUGIN' | 'RESOURCE' | 'WORKFLOW';
type ResourceType =
| 'AGENT'
| 'BOT'
| 'KNOWLEDGE'
| 'PLUGIN'
| 'RESOURCE'
| 'WORKFLOW';
interface CategoryScopeItem {
categoryIds: Array<number | string>;
@@ -40,6 +46,7 @@ const RESOURCE_SCOPE_GROUPS: Array<{
label: string;
resourceType: ResourceType;
}> = [
{ resourceType: 'AGENT', label: $t('menus.ai.agents') },
{ resourceType: 'BOT', label: $t('bot.chatAssistant') },
{ resourceType: 'PLUGIN', label: $t('menus.ai.plugin') },
{ resourceType: 'WORKFLOW', label: $t('menus.ai.workflow') },
@@ -54,6 +61,7 @@ const entity = ref<any>(buildDefaultEntity());
const categoryScopeLoaded = ref(false);
const categoryScopeEditable = ref(false);
const categoryOptions = ref<Record<ResourceType, CategoryOption[]>>({
AGENT: [],
BOT: [],
KNOWLEDGE: [],
PLUGIN: [],
@@ -186,6 +194,9 @@ async function ensureCategoryOptions() {
return;
}
const requests = [
api.get('/api/v1/agentCategory/list', {
params: { sortKey: 'sortNo', sortType: 'asc' },
}),
api.get('/api/v1/botCategory/list', {
params: { sortKey: 'sortNo', sortType: 'asc' },
}),
@@ -201,9 +212,10 @@ async function ensureCategoryOptions() {
}),
];
const [botRes, pluginRes, workflowRes, knowledgeRes, resourceRes] =
const [agentRes, botRes, pluginRes, workflowRes, knowledgeRes, resourceRes] =
await Promise.all(requests);
categoryOptions.value = {
AGENT: normalizeCategoryOptions(agentRes.data, 'categoryName'),
BOT: normalizeCategoryOptions(botRes.data, 'categoryName'),
KNOWLEDGE: normalizeCategoryOptions(knowledgeRes.data, 'categoryName'),
PLUGIN: normalizeCategoryOptions(pluginRes.data, 'name'),