feat: 全新智能体功能

- 基于先进智能体框架,增加智能体编排功能
- 增加智能体聊天,并对接持久化
This commit is contained in:
2026-05-25 11:42:48 +08:00
parent 6c3d98eaac
commit 72df00f25b
168 changed files with 22045 additions and 400 deletions

View File

@@ -0,0 +1,324 @@
import type {ServerSentEventMessage} from 'fetch-event-stream';
import type {
ChatTimelineItem as ChatTimelineItemType,
ChatTimelineMessageItem,
} from '@easyflow/common-ui';
import {ChatTimelineBuilder} from '@easyflow/common-ui';
import type {AgentInfo, AgentKnowledgeBinding, AgentToolBinding,} from '../types';
import {ref} from 'vue';
import {sseClient} from '#/api/request';
import {clearAgentDraftSession} from '../api';
import {useAgentTryoutRawRounds} from './useAgentTryoutRawRounds';
function resolveDraftSessionId(agent: AgentInfo) {
return `agent-draft-${agent.id || agent.localId || 'unsaved'}`;
}
function parseEventData(message: ServerSentEventMessage) {
const raw = message.data || '';
if (!raw) return {};
try {
return JSON.parse(raw);
} catch {
return { payload: { delta: raw } };
}
}
function resolveEnvelope(data: any) {
return {
domain: data.domain || data.eventDomain || data.typeDomain,
type: data.type || data.eventType || data.chatType || data.event,
payload: data.payload ?? data.data ?? data,
};
}
function asText(value: unknown) {
return value === null || value === undefined ? '' : String(value);
}
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === 'object' && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
}
function isEndOfRoundEvent(domain: string, type: string) {
return domain === 'SYSTEM' && type === 'DONE';
}
interface DraftRuntimeContext {
agent: AgentInfo;
knowledgeBindings: AgentKnowledgeBinding[];
toolBindings: AgentToolBinding[];
}
export function useAgentTryoutStream() {
const timelineItems = ref<ChatTimelineItemType[]>([]);
const loading = ref(false);
let rawRounds: ReturnType<typeof useAgentTryoutRawRounds> | undefined;
let activeRoundId = '';
let activeSessionId = '';
let userStopped = false;
function errorMessageOf(error: unknown) {
if (error instanceof Error) {
return `${error.name} ${error.message}`.trim();
}
if (typeof error === 'string') {
return error;
}
const value = asRecord(error);
const nested = asRecord(value.cause);
return [value.name, value.message, value.error, nested.name, nested.message]
.map((item) => asText(item).trim())
.filter(Boolean)
.join(' ');
}
function isAbortError(error: unknown) {
const message = errorMessageOf(error).toLowerCase();
return message.includes('abort');
}
function shouldIgnoreStoppedError(error: unknown) {
return userStopped && isAbortError(error);
}
function finishStoppedRun() {
finishAssistant();
rawRounds?.flush();
loading.value = false;
}
function rebuildTimeline() {
timelineItems.value = rawRounds?.buildTimelineItems() || [];
}
function syncDraftContext(payload: DraftRuntimeContext, restore = false) {
const sessionId = resolveDraftSessionId(payload.agent);
const sessionChanged = activeSessionId !== sessionId;
activeSessionId = sessionId;
if (!rawRounds || sessionChanged) {
rawRounds = useAgentTryoutRawRounds({
mode: 'draft',
sessionId,
});
activeRoundId = '';
}
if (restore && sessionChanged && !loading.value) {
rebuildTimeline();
}
}
function markRoundCompleted(roundId: string) {
if (!roundId) {
return;
}
rawRounds?.completeRound(roundId);
rebuildTimeline();
}
function finishAssistant() {
ChatTimelineBuilder.finalize(timelineItems.value);
}
function markToolApproving(payload: {
requestId?: string;
resumeToken?: string;
toolCallId?: string;
}) {
rawRounds?.recordEvent(activeRoundId, {
domain: 'TOOL',
payload,
type: 'FORM_APPROVING',
});
rawRounds?.flush();
rebuildTimeline();
}
function markToolRejected(payload: {
reason?: string;
requestId?: string;
resumeToken?: string;
toolCallId?: string;
}) {
rawRounds?.recordEvent(activeRoundId, {
domain: 'TOOL',
payload,
type: 'FORM_REJECTED',
});
rawRounds?.flush();
rebuildTimeline();
}
function handleMessage(message: ServerSentEventMessage) {
const data = parseEventData(message);
const envelope = resolveEnvelope(data);
const domain = String(envelope.domain || '').toUpperCase();
const type = String(envelope.type || '').toUpperCase();
const payload = envelope.payload || {};
if (activeRoundId) {
rawRounds?.recordEvent(activeRoundId, {
domain,
payload,
type,
});
rebuildTimeline();
}
if (domain === 'LLM' && type === 'MESSAGE') {
return;
}
if (domain === 'LLM' && type === 'THINKING') {
const text = asText(payload.reasoning ?? payload.delta ?? payload.text);
if (!text) return;
return;
}
if (domain === 'TOOL' && type === 'FORM_REQUEST') {
return;
}
if (domain === 'TOOL' && (type === 'TOOL_CALL' || type === 'TOOL_RESULT')) {
return;
}
if (domain === 'BUSINESS' && type === 'CITATIONS') {
return;
}
if (domain === 'BUSINESS' && type === 'STATUS') {
return;
}
if (isEndOfRoundEvent(domain, type)) {
markRoundCompleted(activeRoundId);
return;
}
if (type === 'ERROR' || domain === 'ERROR') {
const message = payload.message ?? payload.error ?? '试运行失败';
if (shouldIgnoreStoppedError(message)) {
return;
}
}
}
async function runDraft(payload: {
agent: AgentInfo;
knowledgeBindings: AgentKnowledgeBinding[];
prompt: string;
toolBindings: AgentToolBinding[];
}) {
syncDraftContext(payload);
if (!rawRounds) {
return;
}
activeRoundId = rawRounds.createRound(payload.prompt);
rebuildTimeline();
loading.value = true;
userStopped = false;
await sseClient.post(
'/api/v1/agent/chat/draft',
{
...payload,
sessionId: activeSessionId,
},
{
onMessage: handleMessage,
onError: (error) => {
if (shouldIgnoreStoppedError(error)) {
return;
}
rawRounds?.recordEvent(activeRoundId, {
domain: 'SYSTEM',
payload: {
message: error?.message ?? '试运行失败,请稍后再试',
},
type: 'ERROR',
});
rebuildTimeline();
finishAssistant();
rawRounds?.flush();
loading.value = false;
},
onFinished: () => {
if (userStopped) {
return;
}
finishAssistant();
markRoundCompleted(activeRoundId);
rawRounds?.flush();
loading.value = false;
},
},
);
}
async function sendDraft(payload: {
agent: AgentInfo;
knowledgeBindings: AgentKnowledgeBinding[];
prompt: string;
toolBindings: AgentToolBinding[];
}) {
await runDraft(payload);
}
async function regenerateDraft(item: ChatTimelineMessageItem) {
// 有状态 runtime 的历史重生成需要后端 session fork/rollback第一版先禁用入口。
void item;
}
function selectVariant(
item: ChatTimelineMessageItem,
direction: 'next' | 'previous',
) {
if (!item.roundId || !rawRounds?.canSwitch(item, direction)) {
return;
}
rawRounds.selectVariant(item.roundId, direction);
rebuildTimeline();
}
function copyMessageText(item: ChatTimelineMessageItem) {
return rawRounds?.copyText(item) || '';
}
async function clearDraftSession() {
if (loading.value) {
userStopped = true;
sseClient.abort();
loading.value = false;
}
const sessionId = activeSessionId;
rawRounds?.clear();
timelineItems.value = [];
activeRoundId = '';
if (sessionId) {
await clearAgentDraftSession(sessionId);
}
}
function stop() {
if (!loading.value) {
return;
}
userStopped = true;
sseClient.abort();
finishStoppedRun();
}
return {
loading,
clearDraftSession,
markToolApproving,
markToolRejected,
copyMessageText,
regenerateDraft,
selectVariant,
syncDraftContext,
timelineItems,
sendDraft,
stop,
};
}