feat: 先进智能体功能上线
- 基于 agent-runtime 打造,默认 ReAct agent - 支持 agent 能力对接,已对接工作流、插件、知识库等 tool 能力 - 全新 agent 编排界面,支持可视化便捷配置 agent - 全新 agent 聊天界面,支持快捷操作、额外知识库选择等
This commit is contained in:
@@ -34,6 +34,8 @@ import {useAgentDesignerState} from './composables/useAgentDesignerState';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const AGENT_TAB_PAGE_KEY = '/ai/agents';
|
||||
const DEFAULT_AGENT_TITLE = '未命名智能体';
|
||||
const {
|
||||
state,
|
||||
addKnowledgeNode,
|
||||
@@ -52,6 +54,7 @@ const {
|
||||
|
||||
const pageLoading = ref(false);
|
||||
const saveLoading = ref(false);
|
||||
const offlineLoading = ref(false);
|
||||
const publishLoading = ref(false);
|
||||
const issues = ref<AgentValidationIssue[]>([]);
|
||||
const categories = ref<AgentOption[]>([]);
|
||||
@@ -62,14 +65,6 @@ const pluginTools = ref<AgentOption[]>([]);
|
||||
|
||||
const isNew = computed(() => String(route.params.id || '') === 'new');
|
||||
const publishText = computed(() => {
|
||||
if (
|
||||
canAiResourceOffline(
|
||||
state.agent.displayPublishStatus,
|
||||
state.agent.publishStatus,
|
||||
)
|
||||
) {
|
||||
return '下线';
|
||||
}
|
||||
if (
|
||||
canAiResourceRepublish(
|
||||
state.agent.displayPublishStatus,
|
||||
@@ -81,6 +76,13 @@ const publishText = computed(() => {
|
||||
return '发布';
|
||||
});
|
||||
|
||||
const offlineVisible = computed(() =>
|
||||
canAiResourceOffline(
|
||||
state.agent.displayPublishStatus,
|
||||
state.agent.publishStatus,
|
||||
),
|
||||
);
|
||||
|
||||
const publishDisabled = computed(() => {
|
||||
if (!state.agent.id) return true;
|
||||
if (
|
||||
@@ -99,14 +101,15 @@ const publishDisabled = computed(() => {
|
||||
canAiResourceRepublish(
|
||||
state.agent.displayPublishStatus,
|
||||
state.agent.publishStatus,
|
||||
) ||
|
||||
canAiResourceOffline(
|
||||
state.agent.displayPublishStatus,
|
||||
state.agent.publishStatus,
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const offlineDisabled = computed(() => {
|
||||
if (!state.agent.id) return true;
|
||||
return !offlineVisible.value;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
pageLoading.value = true;
|
||||
try {
|
||||
@@ -119,14 +122,55 @@ onMounted(async () => {
|
||||
async function loadAgent() {
|
||||
if (isNew.value) {
|
||||
reset();
|
||||
syncNavTitle(DEFAULT_AGENT_TITLE, { force: true });
|
||||
return;
|
||||
}
|
||||
const [, res] = await tryit(getAgentDetail)(String(route.params.id));
|
||||
if (res?.errorCode === 0) {
|
||||
reset(res.data);
|
||||
syncNavTitle(resolveAgentTitle(res.data), { force: !hasNavTitle() });
|
||||
}
|
||||
}
|
||||
|
||||
function hasNavTitle() {
|
||||
const navTitle = Array.isArray(route.query.navTitle)
|
||||
? route.query.navTitle[0]
|
||||
: route.query.navTitle;
|
||||
return typeof navTitle === 'string' && navTitle.trim();
|
||||
}
|
||||
|
||||
function resolveAgentTitle(agent = state.agent) {
|
||||
return String(agent.name || '').trim() || DEFAULT_AGENT_TITLE;
|
||||
}
|
||||
|
||||
function syncNavTitle(title: string, options: { force?: boolean } = {}) {
|
||||
const normalizedTitle = String(title || '').trim() || DEFAULT_AGENT_TITLE;
|
||||
const query = route.query as Record<string, any>;
|
||||
const currentNavTitle = Array.isArray(query.navTitle)
|
||||
? query.navTitle[0]
|
||||
: query.navTitle;
|
||||
const currentPageKey = Array.isArray(query.pageKey)
|
||||
? query.pageKey[0]
|
||||
: query.pageKey;
|
||||
|
||||
if (
|
||||
!options.force &&
|
||||
currentNavTitle === normalizedTitle &&
|
||||
currentPageKey === AGENT_TAB_PAGE_KEY
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
...query,
|
||||
pageKey: AGENT_TAB_PAGE_KEY,
|
||||
navTitle: normalizedTitle,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function loadOptions() {
|
||||
const [categoryRes, modelRes, knowledgeRes, workflowRes, pluginRes] =
|
||||
await Promise.all([
|
||||
@@ -245,8 +289,18 @@ async function handleSave(showMessage = true) {
|
||||
id,
|
||||
};
|
||||
state.dirty = false;
|
||||
const title = resolveAgentTitle();
|
||||
if (isNew.value) {
|
||||
await router.replace(`/ai/agents/designer/${id}`);
|
||||
await router.replace({
|
||||
path: `/ai/agents/designer/${id}`,
|
||||
query: {
|
||||
...route.query,
|
||||
pageKey: AGENT_TAB_PAGE_KEY,
|
||||
navTitle: title,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
syncNavTitle(title, { force: true });
|
||||
}
|
||||
if (showMessage) {
|
||||
ElMessage.success('已保存');
|
||||
@@ -262,29 +316,19 @@ async function handlePublish() {
|
||||
const saved = await handleSave(false);
|
||||
if (!saved) return;
|
||||
|
||||
const offline = canAiResourceOffline(
|
||||
state.agent.displayPublishStatus,
|
||||
state.agent.publishStatus,
|
||||
);
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
offline ? '确认提交下线审批?' : '确认提交发布审批?',
|
||||
'提示',
|
||||
{
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: offline ? 'warning' : 'info',
|
||||
},
|
||||
);
|
||||
await ElMessageBox.confirm('确认提交发布审批?', '提示', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info',
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
publishLoading.value = true;
|
||||
try {
|
||||
const res = offline
|
||||
? await submitAgentOfflineApproval(String(state.agent.id))
|
||||
: await submitAgentPublishApproval(String(state.agent.id));
|
||||
const res = await submitAgentPublishApproval(String(state.agent.id));
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message || '已提交');
|
||||
await loadAgent();
|
||||
@@ -294,6 +338,33 @@ async function handlePublish() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOffline() {
|
||||
if (!state.agent.id) return;
|
||||
const saved = await handleSave(false);
|
||||
if (!saved) return;
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm('确认提交下线审批?', '提示', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
offlineLoading.value = true;
|
||||
try {
|
||||
const res = await submitAgentOfflineApproval(String(state.agent.id));
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message || '已提交');
|
||||
await loadAgent();
|
||||
}
|
||||
} finally {
|
||||
offlineLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleTryout() {
|
||||
if (!runValidation()) return;
|
||||
openTryout();
|
||||
@@ -330,8 +401,12 @@ function handleCloseTryout() {
|
||||
:publish-loading="publishLoading"
|
||||
:publish-disabled="publishDisabled"
|
||||
:publish-text="publishText"
|
||||
:offline-disabled="offlineDisabled"
|
||||
:offline-loading="offlineLoading"
|
||||
:offline-visible="offlineVisible"
|
||||
@add="handleAdd"
|
||||
@save="handleSave()"
|
||||
@offline="handleOffline"
|
||||
@publish="handlePublish"
|
||||
@tryout="handleTryout"
|
||||
/>
|
||||
|
||||
@@ -36,6 +36,8 @@ import {
|
||||
const router = useRouter();
|
||||
const pageDataRef = ref();
|
||||
const sideList = ref<any[]>([]);
|
||||
const AGENT_TAB_PAGE_KEY = '/ai/agents';
|
||||
const DEFAULT_AGENT_TITLE = '未命名智能体';
|
||||
|
||||
const headerButtons = [
|
||||
{
|
||||
@@ -53,7 +55,13 @@ const primaryAction: CardPrimaryAction = {
|
||||
text: '编排',
|
||||
permission: '/api/v1/agent/update',
|
||||
onClick(row: AgentInfo) {
|
||||
router.push(`/ai/agents/designer/${row.id}`);
|
||||
router.push({
|
||||
path: `/ai/agents/designer/${row.id}`,
|
||||
query: {
|
||||
pageKey: AGENT_TAB_PAGE_KEY,
|
||||
navTitle: resolveNavTitle(row),
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -106,10 +114,20 @@ function handleSearch(keyword: string) {
|
||||
|
||||
function handleButtonClick(payload: any) {
|
||||
if (payload?.key === 'create' || payload?.data?.action === 'create') {
|
||||
router.push('/ai/agents/designer/new');
|
||||
router.push({
|
||||
path: '/ai/agents/designer/new',
|
||||
query: {
|
||||
pageKey: AGENT_TAB_PAGE_KEY,
|
||||
navTitle: DEFAULT_AGENT_TITLE,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resolveNavTitle(row: AgentInfo) {
|
||||
return String(row.name || '').trim() || DEFAULT_AGENT_TITLE;
|
||||
}
|
||||
|
||||
function changeCategory(category: any) {
|
||||
pageDataRef.value?.setQuery({ categoryId: category.id });
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type {AgentCapabilityKind} from '../types';
|
||||
|
||||
import {onBeforeUnmount, onMounted, ref} from 'vue';
|
||||
import {computed, onBeforeUnmount, onMounted, ref} from 'vue';
|
||||
|
||||
import {
|
||||
Connection,
|
||||
Files,
|
||||
Loading,
|
||||
Plus,
|
||||
Promotion,
|
||||
Share,
|
||||
VideoPlay,
|
||||
} from '@element-plus/icons-vue';
|
||||
import {Connection, Files, Loading, Plus, Share, VideoPlay,} from '@element-plus/icons-vue';
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
offlineDisabled?: boolean;
|
||||
offlineLoading?: boolean;
|
||||
offlineVisible?: boolean;
|
||||
publishDisabled?: boolean;
|
||||
publishLoading?: boolean;
|
||||
publishText: string;
|
||||
@@ -21,8 +16,11 @@ defineProps<{
|
||||
tryoutDisabled?: boolean;
|
||||
}>();
|
||||
|
||||
const isRepublish = computed(() => props.publishText === '重新发布');
|
||||
|
||||
const emit = defineEmits<{
|
||||
add: [kind: AgentCapabilityKind];
|
||||
offline: [];
|
||||
publish: [];
|
||||
save: [];
|
||||
tryout: [];
|
||||
@@ -120,13 +118,23 @@ onBeforeUnmount(() => {
|
||||
</button>
|
||||
<div class="agent-command-bar__divider"></div>
|
||||
<button
|
||||
v-if="offlineVisible"
|
||||
class="agent-command-bar__button agent-command-bar__button--ghost"
|
||||
type="button"
|
||||
:disabled="offlineDisabled || offlineLoading || publishLoading"
|
||||
@click="emit('offline')"
|
||||
>
|
||||
<Loading v-if="offlineLoading" class="is-loading" />
|
||||
<span>下线</span>
|
||||
</button>
|
||||
<button
|
||||
class="agent-command-bar__button agent-command-bar__button--ghost"
|
||||
:class="{ 'agent-command-bar__button--republish': isRepublish }"
|
||||
type="button"
|
||||
:disabled="publishDisabled || publishLoading"
|
||||
@click="emit('publish')"
|
||||
>
|
||||
<Loading v-if="publishLoading" class="is-loading" />
|
||||
<Promotion v-else />
|
||||
<span>{{ publishText }}</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -280,11 +288,21 @@ onBeforeUnmount(() => {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.agent-command-bar__button--republish {
|
||||
color: var(--el-color-warning);
|
||||
background: var(--el-color-warning-light-9);
|
||||
}
|
||||
|
||||
.agent-command-bar__button:hover:not(:disabled) {
|
||||
color: var(--el-text-color-primary);
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
.agent-command-bar__button--republish:hover:not(:disabled) {
|
||||
color: var(--el-color-warning);
|
||||
background: var(--el-color-warning-light-8);
|
||||
}
|
||||
|
||||
.agent-command-bar__button--primary:hover:not(:disabled) {
|
||||
color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-8);
|
||||
|
||||
@@ -98,6 +98,37 @@ describe('useAgentTryoutRawRounds', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('AgentScope context_reload 工具事件不进入页面时间线', () => {
|
||||
const store = useAgentTryoutRawRounds({
|
||||
mode: 'draft',
|
||||
sessionId: 'session-raw-context-reload',
|
||||
});
|
||||
const roundId = store.createRound('展开第一层');
|
||||
|
||||
store.recordEvent(roundId, {
|
||||
domain: 'TOOL',
|
||||
payload: {
|
||||
input: { working_context_offload_uuid: 'context-id' },
|
||||
toolCallId: 'context-reload-1',
|
||||
toolName: 'context_reload',
|
||||
},
|
||||
type: 'TOOL_CALL',
|
||||
});
|
||||
store.recordEvent(roundId, {
|
||||
domain: 'TOOL',
|
||||
payload: {
|
||||
output: 'context',
|
||||
toolCallId: 'context-reload-1',
|
||||
toolName: 'context_reload',
|
||||
},
|
||||
type: 'TOOL_RESULT',
|
||||
});
|
||||
|
||||
expect(store.buildTimelineItems().map((item) => item.type)).toEqual([
|
||||
'message',
|
||||
]);
|
||||
});
|
||||
|
||||
it('刷新后能从 raw rounds 恢复 timeline', () => {
|
||||
const first = useAgentTryoutRawRounds({
|
||||
mode: 'draft',
|
||||
|
||||
@@ -75,7 +75,11 @@ function normalizeToolName(value: unknown) {
|
||||
|
||||
function isHiddenToolName(value: unknown) {
|
||||
const normalizedName = normalizeToolName(value);
|
||||
return normalizedName === 'retrieve_knowledge' || normalizedName === '__fragment__';
|
||||
return (
|
||||
normalizedName === 'retrieve_knowledge' ||
|
||||
normalizedName === 'context_reload' ||
|
||||
normalizedName === '__fragment__'
|
||||
);
|
||||
}
|
||||
|
||||
function clone<T>(value: T): T {
|
||||
|
||||
Reference in New Issue
Block a user