feat: 完善智能体图片聊天与会话恢复

- 增加私有图片上传、绑定、历史回显与生命周期清理

- 支持输入草稿恢复、图片交互和模型图片能力约束

- 修复旧脏会话幂等删除与前端会话恢复
This commit is contained in:
2026-07-17 19:54:26 +08:00
parent 62d763199f
commit 1e6158be77
62 changed files with 5333 additions and 189 deletions

View File

@@ -1,19 +1,24 @@
import type {ServerSentEventMessage} from 'fetch-event-stream';
import type { ServerSentEventMessage } from 'fetch-event-stream';
import type {
ChatImageAttachment,
ChatTimelineItem as ChatTimelineItemType,
ChatTimelineMessageItem,
} from '@easyflow/common-ui';
import {ChatTimelineBuilder} from '@easyflow/common-ui';
import { ChatTimelineBuilder } from '@easyflow/common-ui';
import type {AgentInfo, AgentKnowledgeBinding, AgentToolBinding,} from '../types';
import type {
AgentInfo,
AgentKnowledgeBinding,
AgentToolBinding,
} from '../types';
import {ref} from 'vue';
import { ref } from 'vue';
import {sseClient} from '#/api/request';
import { sseClient } from '#/api/request';
import {clearAgentDraftSession} from '../api';
import {useAgentTryoutRawRounds} from './useAgentTryoutRawRounds';
import { clearAgentDraftSession } from '../api';
import { useAgentTryoutRawRounds } from './useAgentTryoutRawRounds';
function resolveDraftSessionId(agent: AgentInfo) {
return `agent-draft-${agent.id || agent.localId || 'unsaved'}`;
@@ -99,8 +104,13 @@ export function useAgentTryoutStream() {
timelineItems.value = rawRounds?.buildTimelineItems() || [];
}
function syncDraftContext(payload: DraftRuntimeContext, restore = false) {
const sessionId = resolveDraftSessionId(payload.agent);
function syncDraftContext(
payload: DraftRuntimeContext,
restore = false,
requestedSessionId?: string,
) {
const sessionId =
requestedSessionId || resolveDraftSessionId(payload.agent);
const sessionChanged = activeSessionId !== sessionId;
activeSessionId = sessionId;
if (!rawRounds || sessionChanged) {
@@ -206,26 +216,44 @@ export function useAgentTryoutStream() {
async function runDraft(payload: {
agent: AgentInfo;
images?: ChatImageAttachment[];
imageUploadIds?: string[];
knowledgeBindings: AgentKnowledgeBinding[];
onAccepted?: () => void | Promise<void>;
prompt: string;
sessionId?: string;
toolBindings: AgentToolBinding[];
}) {
syncDraftContext(payload);
syncDraftContext(payload, false, payload.sessionId);
if (!rawRounds) {
return;
}
activeRoundId = rawRounds.createRound(payload.prompt);
activeRoundId = rawRounds.createRound(payload.prompt, payload.images);
rebuildTimeline();
loading.value = true;
userStopped = false;
let accepted = false;
await sseClient.post(
'/api/v1/agent/chat/draft',
{
...payload,
agent: payload.agent,
imageUploadIds: payload.imageUploadIds,
knowledgeBindings: payload.knowledgeBindings,
prompt: payload.prompt,
sessionId: activeSessionId,
toolBindings: payload.toolBindings,
},
{
onMessage: handleMessage,
onMessage: (message) => {
const envelope = resolveEnvelope(parseEventData(message));
const domain = String(envelope.domain || '').toUpperCase();
const type = String(envelope.type || '').toUpperCase();
if (!accepted && domain === 'SYSTEM' && type === 'INPUT_ACCEPTED') {
accepted = true;
void payload.onAccepted?.();
}
handleMessage(message);
},
onError: (error) => {
if (shouldIgnoreStoppedError(error)) {
return;
@@ -257,8 +285,12 @@ export function useAgentTryoutStream() {
async function sendDraft(payload: {
agent: AgentInfo;
images?: ChatImageAttachment[];
imageUploadIds?: string[];
knowledgeBindings: AgentKnowledgeBinding[];
onAccepted?: () => void | Promise<void>;
prompt: string;
sessionId?: string;
toolBindings: AgentToolBinding[];
}) {
await runDraft(payload);