feat: 完善智能体图片聊天与会话恢复
- 增加私有图片上传、绑定、历史回显与生命周期清理 - 支持输入草稿恢复、图片交互和模型图片能力约束 - 修复旧脏会话幂等删除与前端会话恢复
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import type {
|
||||
ChatImageAttachment,
|
||||
ChatTimelineItem,
|
||||
ChatTimelineKnowledgeHit,
|
||||
ChatTimelineMessageItem,
|
||||
ChatTimelineToolStatus,
|
||||
} from '@easyflow/common-ui';
|
||||
import {ChatTimelineBuilder} from '@easyflow/common-ui';
|
||||
import { ChatTimelineBuilder } from '@easyflow/common-ui';
|
||||
|
||||
interface AgentTryoutRuntimeEvent {
|
||||
createdAt: number;
|
||||
@@ -25,6 +26,7 @@ interface AgentTryoutRawVariant {
|
||||
|
||||
interface AgentTryoutRawRound {
|
||||
createdAt: number;
|
||||
images?: ChatImageAttachment[];
|
||||
prompt: string;
|
||||
roundId: string;
|
||||
selectedVariantIndex: number;
|
||||
@@ -110,7 +112,9 @@ function createVariant(variantIndex: number): AgentTryoutRawVariant {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeRuntimeEvent(value: any): AgentTryoutRuntimeEvent | undefined {
|
||||
function normalizeRuntimeEvent(
|
||||
value: any,
|
||||
): AgentTryoutRuntimeEvent | undefined {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
@@ -135,8 +139,9 @@ function normalizeVariant(value: any, index: number) {
|
||||
? value.runtimeEvents
|
||||
.map((item: any) => normalizeRuntimeEvent(item))
|
||||
.filter(
|
||||
(item: AgentTryoutRuntimeEvent | undefined): item is AgentTryoutRuntimeEvent =>
|
||||
Boolean(item),
|
||||
(
|
||||
item: AgentTryoutRuntimeEvent | undefined,
|
||||
): item is AgentTryoutRuntimeEvent => Boolean(item),
|
||||
)
|
||||
: [];
|
||||
return {
|
||||
@@ -157,7 +162,8 @@ function normalizeRound(value: any): AgentTryoutRawRound | undefined {
|
||||
}
|
||||
const prompt = asText(value.prompt);
|
||||
const roundId = asText(value.roundId);
|
||||
if (!prompt || !roundId) {
|
||||
const images = Array.isArray(value.images) ? value.images.slice(0, 5) : [];
|
||||
if ((!prompt && images.length === 0) || !roundId) {
|
||||
return undefined;
|
||||
}
|
||||
const variants = Array.isArray(value.variants)
|
||||
@@ -174,6 +180,7 @@ function normalizeRound(value: any): AgentTryoutRawRound | undefined {
|
||||
);
|
||||
return {
|
||||
createdAt: Number(value.createdAt || Date.now()),
|
||||
images,
|
||||
prompt,
|
||||
roundId,
|
||||
selectedVariantIndex,
|
||||
@@ -210,7 +217,10 @@ function restoreSession(mode: string, sessionId: string) {
|
||||
.map((item) => normalizeRound(item))
|
||||
.filter((item): item is AgentTryoutRawRound => Boolean(item))
|
||||
: [];
|
||||
memorySessions.set(key, rounds.map((item) => clone(item)));
|
||||
memorySessions.set(
|
||||
key,
|
||||
rounds.map((item) => clone(item)),
|
||||
);
|
||||
return rounds;
|
||||
} catch {
|
||||
return [];
|
||||
@@ -228,7 +238,10 @@ function persistSession(
|
||||
sessionId,
|
||||
version: STORAGE_VERSION,
|
||||
};
|
||||
memorySessions.set(key, snapshot.rounds.map((item) => clone(item)));
|
||||
memorySessions.set(
|
||||
key,
|
||||
snapshot.rounds.map((item) => clone(item)),
|
||||
);
|
||||
const storage = safeSessionStorage();
|
||||
if (!storage) {
|
||||
return;
|
||||
@@ -269,7 +282,9 @@ function visibleText(item: ChatTimelineMessageItem) {
|
||||
.join('');
|
||||
}
|
||||
|
||||
function isUserMessage(item: ChatTimelineItem): item is ChatTimelineMessageItem {
|
||||
function isUserMessage(
|
||||
item: ChatTimelineItem,
|
||||
): item is ChatTimelineMessageItem {
|
||||
return item.type === 'message' && item.role === 'user';
|
||||
}
|
||||
|
||||
@@ -326,7 +341,10 @@ function normalizeAssistantPartIds(
|
||||
const segment = assistantSegmentIndex(items, roundId);
|
||||
const latest = [...items]
|
||||
.reverse()
|
||||
.find((item): item is ChatTimelineMessageItem => isAssistantMessage(item) && item.roundId === roundId);
|
||||
.find(
|
||||
(item): item is ChatTimelineMessageItem =>
|
||||
isAssistantMessage(item) && item.roundId === roundId,
|
||||
);
|
||||
if (!latest) {
|
||||
return;
|
||||
}
|
||||
@@ -468,7 +486,9 @@ function projectEventToTimeline(
|
||||
metadata: payload.metadata,
|
||||
requestId: asText(payload.requestId),
|
||||
resumeToken: asText(payload.resumeToken),
|
||||
toolCallId: asText(payload.toolCallId ?? payload.tool_call_id ?? payload.id),
|
||||
toolCallId: asText(
|
||||
payload.toolCallId ?? payload.tool_call_id ?? payload.id,
|
||||
),
|
||||
toolDisplayName: asText(payload.toolDisplayName),
|
||||
toolName: asText(payload.toolName),
|
||||
toolType: asText(payload.toolType),
|
||||
@@ -482,7 +502,9 @@ function projectEventToTimeline(
|
||||
ChatTimelineBuilder.markToolApproving(items, {
|
||||
requestId: asText(payload.requestId),
|
||||
resumeToken: asText(payload.resumeToken),
|
||||
toolCallId: asText(payload.toolCallId ?? payload.tool_call_id ?? payload.id),
|
||||
toolCallId: asText(
|
||||
payload.toolCallId ?? payload.tool_call_id ?? payload.id,
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -491,7 +513,9 @@ function projectEventToTimeline(
|
||||
reason: asText(payload.reason),
|
||||
requestId: asText(payload.requestId),
|
||||
resumeToken: asText(payload.resumeToken),
|
||||
toolCallId: asText(payload.toolCallId ?? payload.tool_call_id ?? payload.id),
|
||||
toolCallId: asText(
|
||||
payload.toolCallId ?? payload.tool_call_id ?? payload.id,
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -508,8 +532,12 @@ function projectEventToTimeline(
|
||||
ChatTimelineBuilder.upsertToolCall(items, {
|
||||
input: payload.input ?? payload.toolInput,
|
||||
output: asyncTool
|
||||
? payload.summary ?? payload.label ?? payload.output ?? payload.result ?? payload.text
|
||||
: payload.output ?? payload.result ?? payload.text,
|
||||
? (payload.summary ??
|
||||
payload.label ??
|
||||
payload.output ??
|
||||
payload.result ??
|
||||
payload.text)
|
||||
: (payload.output ?? payload.result ?? payload.text),
|
||||
status: asyncTool
|
||||
? asyncToolTimelineStatus(payload)
|
||||
: type === 'TOOL_RESULT'
|
||||
@@ -521,8 +549,17 @@ function projectEventToTimeline(
|
||||
variantIndex,
|
||||
'knowledge-retrieval',
|
||||
),
|
||||
toolCallId: asText(payload.toolCallId ?? payload.taskId ?? payload.tool_call_id ?? payload.id),
|
||||
toolName: asyncTool ? displayToolName : isHiddenToolName(rawToolName) ? rawToolName : displayToolName,
|
||||
toolCallId: asText(
|
||||
payload.toolCallId ??
|
||||
payload.taskId ??
|
||||
payload.tool_call_id ??
|
||||
payload.id,
|
||||
),
|
||||
toolName: asyncTool
|
||||
? displayToolName
|
||||
: isHiddenToolName(rawToolName)
|
||||
? rawToolName
|
||||
: displayToolName,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -568,7 +605,9 @@ function projectEventToTimeline(
|
||||
}
|
||||
}
|
||||
|
||||
function asyncToolTimelineStatus(payload: Record<string, unknown>): ChatTimelineToolStatus {
|
||||
function asyncToolTimelineStatus(
|
||||
payload: Record<string, unknown>,
|
||||
): ChatTimelineToolStatus {
|
||||
const status = asText(payload.status).toUpperCase();
|
||||
if (status === 'SUCCEEDED') return 'success';
|
||||
if (status === 'FAILED' || status === 'TIMEOUT' || status === 'CANCELLED') {
|
||||
@@ -612,7 +651,10 @@ export function useAgentTryoutRawRounds(options: {
|
||||
|
||||
function schedulePersist() {
|
||||
const key = storageKey(options.mode, options.sessionId);
|
||||
memorySessions.set(key, [...rounds.values()].map((item) => clone(item)));
|
||||
memorySessions.set(
|
||||
key,
|
||||
[...rounds.values()].map((item) => clone(item)),
|
||||
);
|
||||
if (persistTimer) {
|
||||
return;
|
||||
}
|
||||
@@ -630,11 +672,12 @@ export function useAgentTryoutRawRounds(options: {
|
||||
removeStoredSession(options.mode, options.sessionId);
|
||||
}
|
||||
|
||||
function createRound(prompt: string) {
|
||||
function createRound(prompt: string, images: ChatImageAttachment[] = []) {
|
||||
const now = Date.now();
|
||||
const roundId = createRoundId();
|
||||
rounds.set(roundId, {
|
||||
createdAt: now,
|
||||
images: clone(images),
|
||||
prompt,
|
||||
roundId,
|
||||
selectedVariantIndex: 1,
|
||||
@@ -738,6 +781,7 @@ export function useAgentTryoutRawRounds(options: {
|
||||
for (const round of sortedRounds(rounds)) {
|
||||
ChatTimelineBuilder.appendUserMessage(items, round.prompt, {
|
||||
id: `user-${round.roundId}`,
|
||||
images: round.images,
|
||||
roundId: round.roundId,
|
||||
});
|
||||
const variant = selectedVariant(round);
|
||||
@@ -745,7 +789,12 @@ export function useAgentTryoutRawRounds(options: {
|
||||
continue;
|
||||
}
|
||||
for (const event of variant.runtimeEvents) {
|
||||
projectEventToTimeline(items, event, round.roundId, variant.variantIndex);
|
||||
projectEventToTimeline(
|
||||
items,
|
||||
event,
|
||||
round.roundId,
|
||||
variant.variantIndex,
|
||||
);
|
||||
}
|
||||
if (variant.status === 'completed' || variant.status === 'error') {
|
||||
ChatTimelineBuilder.finalize(items);
|
||||
@@ -761,10 +810,7 @@ export function useAgentTryoutRawRounds(options: {
|
||||
return items;
|
||||
}
|
||||
|
||||
function selectVariant(
|
||||
roundId: string,
|
||||
direction: 'next' | 'previous',
|
||||
) {
|
||||
function selectVariant(roundId: string, direction: 'next' | 'previous') {
|
||||
const round = rounds.get(roundId);
|
||||
if (!round) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user