fix: 完成系统向智能体数据链路切换
- 切换工作台、聊天历史、资源候选与公共调用到 Agent - 加固资源绑定、删除保护及发布运行并发控制 - 隔离旧 Bot 专属服务和组件并保留兼容入口
This commit is contained in:
@@ -78,7 +78,7 @@ export interface AgentChatCapabilityPayload {
|
||||
}
|
||||
|
||||
export function getPublishedAgents() {
|
||||
return api.get<RequestResult<AgentInfo[]>>('/api/v1/agent/list', {
|
||||
return api.get<RequestResult<AgentInfo[]>>('/api/v1/agent/options', {
|
||||
params: { publishedOnly: true },
|
||||
});
|
||||
}
|
||||
@@ -95,10 +95,7 @@ export function getAgentSession(sessionId: number | string) {
|
||||
|
||||
export function getPublishedKnowledges() {
|
||||
return api.get<RequestResult<AgentChatKnowledgeView[]>>(
|
||||
'/api/v1/documentCollection/list',
|
||||
{
|
||||
params: { publishedOnly: true },
|
||||
},
|
||||
'/api/v1/agent/knowledgeOptions',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,8 @@ import {
|
||||
|
||||
import {
|
||||
getAgentDetail,
|
||||
getAgentModels,
|
||||
getMcpPage,
|
||||
getMcpTools,
|
||||
getPublishedKnowledgeList,
|
||||
getAgentMcpToolOptions,
|
||||
getAgentResourceOptions,
|
||||
saveAgent,
|
||||
submitAgentOfflineApproval,
|
||||
submitAgentPublishApproval,
|
||||
@@ -76,7 +74,7 @@ const workflows = ref<AgentOption[]>([]);
|
||||
const pluginTools = ref<AgentOption[]>([]);
|
||||
const mcps = ref<AgentOption[]>([]);
|
||||
const fetchMcpToolResource = createMcpToolLoader(async (id) => {
|
||||
const res = await getMcpTools(id);
|
||||
const res = await getAgentMcpToolOptions(id);
|
||||
return res.errorCode === 0 ? res.data : undefined;
|
||||
});
|
||||
|
||||
@@ -128,7 +126,6 @@ const offlineDisabled = computed(() => {
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
void loadDeferredOptions();
|
||||
try {
|
||||
await Promise.all([loadCriticalOptions(), loadAgent()]);
|
||||
} finally {
|
||||
@@ -213,11 +210,11 @@ function syncNavTitle(title: string, options: { force?: boolean } = {}) {
|
||||
}
|
||||
|
||||
async function loadCriticalOptions() {
|
||||
const [categoryResult, modelResult] = await Promise.allSettled([
|
||||
const [categoryResult, resourceResult] = await Promise.allSettled([
|
||||
api.get('/api/v1/agentCategory/visibleList', {
|
||||
params: { sortKey: 'sortNo', sortType: 'asc' },
|
||||
}),
|
||||
getAgentModels(),
|
||||
getAgentResourceOptions(),
|
||||
]);
|
||||
|
||||
if (categoryResult.status === 'fulfilled') {
|
||||
@@ -227,71 +224,33 @@ async function loadCriticalOptions() {
|
||||
raw: item,
|
||||
}));
|
||||
}
|
||||
if (modelResult.status === 'fulfilled') {
|
||||
models.value = (modelResult.value.data || []).map((item: any) => ({
|
||||
if (resourceResult.status === 'fulfilled') {
|
||||
const resources = resourceResult.value.data;
|
||||
if (resourceResult.value.errorCode !== 0 || !resources) {
|
||||
return;
|
||||
}
|
||||
models.value = (resources.models || []).map((item: any) => ({
|
||||
label: item.title || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDeferredOptions() {
|
||||
const [knowledgeResult, workflowResult, pluginResult, mcpResult] =
|
||||
await Promise.allSettled([
|
||||
getPublishedKnowledgeList(),
|
||||
api.get('/api/v1/workflow/page', {
|
||||
params: { pageNumber: 1, pageSize: 200 },
|
||||
}),
|
||||
api.get('/api/v1/plugin/pageByCategory', {
|
||||
params: { pageNumber: 1, pageSize: 200, category: 0 },
|
||||
}),
|
||||
getMcpPage(),
|
||||
]);
|
||||
|
||||
if (knowledgeResult.status === 'fulfilled') {
|
||||
knowledges.value = (knowledgeResult.value.data || []).map((item: any) => ({
|
||||
knowledges.value = (resources.knowledges || []).map((item: any) => ({
|
||||
label: item.title || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
}
|
||||
if (workflowResult.status === 'fulfilled') {
|
||||
workflows.value = (
|
||||
(workflowResult.value.data?.records ||
|
||||
workflowResult.value.data ||
|
||||
[]) as any[]
|
||||
).map((item) => ({
|
||||
workflows.value = (resources.workflows || []).map((item: any) => ({
|
||||
label: item.title || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
pluginTools.value = (resources.pluginTools || []).map((item: any) => ({
|
||||
label: item.name || item.title,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
mcps.value = mapMcpOptions(resources.mcps || []);
|
||||
}
|
||||
if (pluginResult.status === 'fulfilled') {
|
||||
pluginTools.value = flattenPluginTools(
|
||||
pluginResult.value.data?.records || pluginResult.value.data || [],
|
||||
);
|
||||
}
|
||||
if (mcpResult.status === 'fulfilled') {
|
||||
mcps.value = mapMcpOptions(
|
||||
mcpResult.value.data?.records || mcpResult.value.data || [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function flattenPluginTools(list: any[]): AgentOption[] {
|
||||
const result: AgentOption[] = [];
|
||||
list.forEach((plugin) => {
|
||||
const tools = Array.isArray(plugin.tools) ? plugin.tools : [];
|
||||
tools.forEach((tool: any) => {
|
||||
result.push({
|
||||
label: tool.name || tool.title,
|
||||
value: String(tool.id),
|
||||
raw: { ...tool, pluginName: plugin.name || plugin.title },
|
||||
});
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function mapMcpOptions(list: any[]): AgentOption[] {
|
||||
@@ -325,8 +284,7 @@ async function loadMcpToolsForOption(id: number | string) {
|
||||
const currentOption = mcps.value.find((item) => String(item.value) === key);
|
||||
const mergedResource = {
|
||||
...currentOption?.raw,
|
||||
...resource,
|
||||
tools: Array.isArray(resource.tools) ? resource.tools : [],
|
||||
tools: Array.isArray(resource) ? resource : [],
|
||||
};
|
||||
if (currentOption) {
|
||||
mcps.value = mcps.value.map((item) =>
|
||||
@@ -334,7 +292,7 @@ async function loadMcpToolsForOption(id: number | string) {
|
||||
? {
|
||||
...item,
|
||||
label:
|
||||
resource.title || resource.name || currentOption.label || 'MCP',
|
||||
currentOption.label || 'MCP',
|
||||
raw: mergedResource,
|
||||
}
|
||||
: item,
|
||||
|
||||
@@ -12,7 +12,7 @@ import {Delete, Edit, Plus, Promotion} from '@element-plus/icons-vue';
|
||||
import {ElMessage, ElMessageBox, ElTag} from 'element-plus';
|
||||
import {tryit} from 'radash';
|
||||
|
||||
import defaultAvatar from '#/assets/ai/bot/defaultBotAvatar.png';
|
||||
import defaultAgentAvatar from '#/assets/defaultUserAvatar.png';
|
||||
import HeaderSearch from '#/components/headerSearch/HeaderSearch.vue';
|
||||
import PageData from '#/components/page/PageData.vue';
|
||||
import PageSide from '#/components/page/PageSide.vue';
|
||||
@@ -262,7 +262,7 @@ async function handleDeleteAction(row: AgentInfo) {
|
||||
<CardList
|
||||
title-field="name"
|
||||
icon-field="avatar"
|
||||
:default-icon="defaultAvatar"
|
||||
:default-icon="defaultAgentAvatar"
|
||||
:data="pageList"
|
||||
:primary-action="primaryAction"
|
||||
:actions="actions"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type {AgentInfo, AgentKnowledgeBinding, AgentToolBinding,} from './types';
|
||||
import type {
|
||||
AgentInfo,
|
||||
AgentKnowledgeBinding,
|
||||
AgentToolBinding,
|
||||
} from './types';
|
||||
|
||||
import {api} from '#/api/request';
|
||||
import { api } from '#/api/request';
|
||||
|
||||
export interface RequestResult<T = any> {
|
||||
data: T;
|
||||
@@ -22,10 +26,6 @@ export function updateAgent(agent: AgentInfo) {
|
||||
return api.post<RequestResult<AgentInfo>>('/api/v1/agent/update', agent);
|
||||
}
|
||||
|
||||
export function removeAgent(id: number | string) {
|
||||
return api.post<RequestResult>('/api/v1/agent/remove', { id });
|
||||
}
|
||||
|
||||
export function updateAgentToolBindings(
|
||||
agentId: number | string,
|
||||
bindings: AgentToolBinding[],
|
||||
@@ -98,24 +98,22 @@ export function getAgentCategories() {
|
||||
});
|
||||
}
|
||||
|
||||
export function getAgentModels() {
|
||||
return api.get<RequestResult<any[]>>('/api/v1/model/list', {
|
||||
params: { modelType: 'chatModel', added: true },
|
||||
});
|
||||
export interface AgentResourceOptions {
|
||||
knowledges: any[];
|
||||
mcps: any[];
|
||||
models: any[];
|
||||
pluginTools: any[];
|
||||
workflows: any[];
|
||||
}
|
||||
|
||||
export function getPublishedKnowledgeList() {
|
||||
return api.get<RequestResult<any[]>>('/api/v1/documentCollection/list', {
|
||||
params: { publishedOnly: true },
|
||||
});
|
||||
export function getAgentResourceOptions() {
|
||||
return api.get<RequestResult<AgentResourceOptions>>(
|
||||
'/api/v1/agent/resourceOptions',
|
||||
);
|
||||
}
|
||||
|
||||
export function getMcpPage() {
|
||||
return api.get<RequestResult<any>>('/api/v1/mcp/page', {
|
||||
params: { pageNumber: 1, pageSize: 200, status: 1 },
|
||||
export function getAgentMcpToolOptions(id: number | string) {
|
||||
return api.get<RequestResult<any[]>>('/api/v1/agent/mcpToolOptions', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export function getMcpTools(id: number | string) {
|
||||
return api.post<RequestResult<any>>('/api/v1/mcp/getMcpTools', { id });
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import pageSource from './index.vue?raw';
|
||||
|
||||
describe('管理端智能体聊天历史契约', () => {
|
||||
it('使用智能体候选接口并保留 assistantId 查询参数', () => {
|
||||
expect(pageSource).toContain("'/api/v1/agent/list'");
|
||||
expect(pageSource).toContain("'/api/v1/agent/options'");
|
||||
expect(pageSource).not.toContain('/api/v1/bot/list');
|
||||
expect(pageSource).toContain('label: item.name');
|
||||
expect(pageSource).toContain('assistantId: query.value.assistantId');
|
||||
|
||||
@@ -98,7 +98,7 @@ onMounted(async () => {
|
||||
|
||||
async function fetchAgents() {
|
||||
agentLoading.value = true;
|
||||
const [error, res] = await tryit(api.get)('/api/v1/agent/list');
|
||||
const [error, res] = await tryit(api.get)('/api/v1/agent/options');
|
||||
agentLoading.value = false;
|
||||
if (error || res?.errorCode !== 0) {
|
||||
agentOptions.value = [];
|
||||
|
||||
@@ -360,11 +360,11 @@ const submitOfflineAction = async (item: any) => {
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
impactRes.data?.hasBotBindings
|
||||
impactRes.data?.hasAgentBindings
|
||||
? buildOfflineImpactMessage(
|
||||
$t('documentCollection.offlineImpactBoundBotsIntro'),
|
||||
impactRes.data.botBindings,
|
||||
$t('documentCollection.offlineImpactBoundBotsFooter'),
|
||||
$t('documentCollection.offlineImpactBoundAgentsIntro'),
|
||||
impactRes.data.agentBindings,
|
||||
$t('documentCollection.offlineImpactBoundAgentsFooter'),
|
||||
)
|
||||
: $t('documentCollection.submitOfflineApprovalConfirm'),
|
||||
$t('message.noticeTitle'),
|
||||
|
||||
@@ -7,10 +7,10 @@ export interface OfflineImpactBinding {
|
||||
|
||||
export interface OfflineImpactCheck {
|
||||
canProceed: boolean;
|
||||
hasBotBindings: boolean;
|
||||
hasAgentBindings: boolean;
|
||||
hasPluginBindings: boolean;
|
||||
hasWorkflowUsages: boolean;
|
||||
botBindings: OfflineImpactBinding[];
|
||||
agentBindings: OfflineImpactBinding[];
|
||||
pluginBindings: OfflineImpactBinding[];
|
||||
workflowUsages: OfflineImpactBinding[];
|
||||
message?: string;
|
||||
@@ -33,7 +33,13 @@ export function buildOfflineImpactMessage(
|
||||
h('p', intro),
|
||||
h(
|
||||
'ul',
|
||||
items.map((item) => h('li', { key: String(item.id || item.title || '') }, resolveTitle(item))),
|
||||
items.map((item) =>
|
||||
h(
|
||||
'li',
|
||||
{ key: String(item.id || item.title || '') },
|
||||
resolveTitle(item),
|
||||
),
|
||||
),
|
||||
),
|
||||
footer ? h('p', footer) : null,
|
||||
]);
|
||||
|
||||
@@ -797,15 +797,15 @@ async function submitOfflineAction(row: any) {
|
||||
}
|
||||
try {
|
||||
const sections = [];
|
||||
let offlineImpactFooter = $t('aiWorkflow.offlineImpactBoundBotsFooter');
|
||||
if (impactRes.data?.hasBotBindings) {
|
||||
let offlineImpactFooter = $t('aiWorkflow.offlineImpactBoundAgentsFooter');
|
||||
if (impactRes.data?.hasAgentBindings) {
|
||||
sections.push(
|
||||
buildOfflineImpactMessage(
|
||||
$t('aiWorkflow.offlineImpactBoundBotsIntro'),
|
||||
impactRes.data.botBindings,
|
||||
$t('aiWorkflow.offlineImpactBoundAgentsIntro'),
|
||||
impactRes.data.agentBindings,
|
||||
impactRes.data?.hasPluginBindings
|
||||
? undefined
|
||||
: $t('aiWorkflow.offlineImpactBoundBotsFooter'),
|
||||
: $t('aiWorkflow.offlineImpactBoundAgentsFooter'),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -814,13 +814,13 @@ async function submitOfflineAction(row: any) {
|
||||
buildOfflineImpactMessage(
|
||||
$t('aiWorkflow.offlineImpactBoundPluginsIntro'),
|
||||
impactRes.data.pluginBindings,
|
||||
impactRes.data?.hasBotBindings
|
||||
impactRes.data?.hasAgentBindings
|
||||
? undefined
|
||||
: $t('aiWorkflow.offlineImpactBoundPluginsFooter'),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (impactRes.data?.hasBotBindings && impactRes.data?.hasPluginBindings) {
|
||||
if (impactRes.data?.hasAgentBindings && impactRes.data?.hasPluginBindings) {
|
||||
offlineImpactFooter = $t('aiWorkflow.offlineImpactBoundMixedFooter');
|
||||
} else if (impactRes.data?.hasPluginBindings) {
|
||||
offlineImpactFooter = $t('aiWorkflow.offlineImpactBoundPluginsFooter');
|
||||
|
||||
@@ -136,8 +136,11 @@ function createDefaultEntity(row: Partial<Entity> = {}): Entity {
|
||||
}
|
||||
|
||||
function renderPermissionLabel(item: ResourcePermission) {
|
||||
if (item.requestInterface === '/public-api/bot/chat') {
|
||||
return '聊天助手调用';
|
||||
if (
|
||||
item.requestInterface === '/public-api/agent/chat' ||
|
||||
item.requestInterface === '/public-api/bot/chat'
|
||||
) {
|
||||
return '智能体调用';
|
||||
}
|
||||
if (
|
||||
item.requestInterface === '/v1/chat/completions' ||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import pageSource from './index.vue?raw';
|
||||
|
||||
describe('管理端工作台智能体数据契约', () => {
|
||||
it('使用 Agent 候选接口和名称字段', () => {
|
||||
expect(pageSource).toContain("'/api/v1/agent/options'");
|
||||
expect(pageSource).not.toContain('/api/v1/bot/list');
|
||||
expect(pageSource).toContain('label: item.name');
|
||||
expect(pageSource).not.toContain('publishedOnly');
|
||||
});
|
||||
|
||||
it('使用 Agent 总数字段', () => {
|
||||
expect(pageSource).toContain('summary.value.agentTotal');
|
||||
expect(pageSource).not.toContain('summary.value.botTotal');
|
||||
});
|
||||
});
|
||||
@@ -114,7 +114,7 @@ const trendModeOptions: Array<{ label: string; value: DashboardTrendMode }> = [
|
||||
const emptySummary: DashboardSummary = {
|
||||
activeAssistantTotal: 0,
|
||||
activeUserTotal: 0,
|
||||
botTotal: 0,
|
||||
agentTotal: 0,
|
||||
chatActiveUserTotal: 0,
|
||||
chatMessageTotal: 0,
|
||||
chatSessionTotal: 0,
|
||||
@@ -178,7 +178,7 @@ const summaryCards = computed<SummaryCardItem[]>(() => [
|
||||
chatAvailable.value,
|
||||
),
|
||||
},
|
||||
{ label: '智能体总数', value: formatCount(summary.value.botTotal) },
|
||||
{ label: '智能体总数', value: formatCount(summary.value.agentTotal) },
|
||||
{
|
||||
label: '知识库总数',
|
||||
value: formatCount(summary.value.knowledgeBaseTotal),
|
||||
@@ -330,15 +330,14 @@ async function loadOverview() {
|
||||
async function loadAssistantOptions() {
|
||||
assistantOptionsLoading.value = true;
|
||||
try {
|
||||
const bots = await requestClient.get<
|
||||
Array<{ id?: number | string; title?: string }>
|
||||
>('/api/v1/bot/list', {
|
||||
params: { status: 1 },
|
||||
});
|
||||
const agents =
|
||||
await requestClient.get<Array<{ id?: number | string; name?: string }>>(
|
||||
'/api/v1/agent/options',
|
||||
);
|
||||
const nextOptions: AssistantOptionItem[] = [
|
||||
{ label: '全部智能体', value: '' },
|
||||
...(bots || []).map((item) => ({
|
||||
label: item.title?.trim() || '未命名智能体',
|
||||
...(agents || []).map((item) => ({
|
||||
label: item.name?.trim() || '未命名智能体',
|
||||
value: item.id === undefined || item.id === null ? '' : String(item.id),
|
||||
})),
|
||||
];
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'),
|
||||
};
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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>
|
||||
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user