feat: 完善智能体图片聊天与会话恢复
- 增加私有图片上传、绑定、历史回显与生命周期清理 - 支持输入草稿恢复、图片交互和模型图片能力约束 - 修复旧脏会话幂等删除与前端会话恢复
This commit is contained in:
@@ -18,6 +18,8 @@ import { BrushCleaning } from '@easyflow/icons';
|
||||
import { ElButton, ElMessage } from 'element-plus';
|
||||
|
||||
import AiChatPanel from '#/components/ai-chat/AiChatPanel.vue';
|
||||
import { loadAgentChatImage } from '#/components/ai-chat/mediaApi';
|
||||
import { useAgentComposerDraft } from '#/components/ai-chat/useAgentComposerDraft';
|
||||
|
||||
import { approveAgentRun, rejectAgentRun } from '../api';
|
||||
import { useAgentTryoutStream } from '../composables/useAgentTryoutStream';
|
||||
@@ -26,6 +28,7 @@ import AgentWelcomeState from './AgentWelcomeState.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
agent: AgentInfo;
|
||||
imageEnabled?: boolean;
|
||||
knowledgeBindings: AgentKnowledgeBinding[];
|
||||
toolBindings: AgentToolBinding[];
|
||||
}>();
|
||||
@@ -45,6 +48,7 @@ const {
|
||||
stop,
|
||||
} = useAgentTryoutStream();
|
||||
const approvalLoading = ref(false);
|
||||
const composer = useAgentComposerDraft('DRAFT');
|
||||
const interactionDisplay = computed(() =>
|
||||
resolveInteractionDisplay(props.agent),
|
||||
);
|
||||
@@ -58,18 +62,27 @@ function getDraftContext() {
|
||||
}
|
||||
|
||||
function syncCurrentDraftContext(restore = false) {
|
||||
syncDraftContext(getDraftContext(), restore);
|
||||
syncDraftContext(getDraftContext(), restore, composer.sessionId.value);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
syncCurrentDraftContext(true);
|
||||
});
|
||||
async function activateComposer() {
|
||||
const agentId = String(props.agent.id || '');
|
||||
if (!agentId) return;
|
||||
try {
|
||||
await composer.activate(agentId, `agent-draft-${agentId}`);
|
||||
syncCurrentDraftContext(true);
|
||||
} catch (error) {
|
||||
ElMessage.warning(
|
||||
error instanceof Error ? error.message : '输入草稿恢复失败',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => void activateComposer());
|
||||
|
||||
watch(
|
||||
() => [props.agent.id, props.agent.localId],
|
||||
() => {
|
||||
syncCurrentDraftContext(true);
|
||||
},
|
||||
() => void activateComposer(),
|
||||
);
|
||||
|
||||
watch(
|
||||
@@ -82,12 +95,35 @@ watch(
|
||||
|
||||
async function handleSend(prompt: string) {
|
||||
if (loading.value || approvalLoading.value) return;
|
||||
if (composer.images.uploading.value) {
|
||||
ElMessage.warning('图片上传完成后再发送');
|
||||
return;
|
||||
}
|
||||
const failedImage = composer.images.items.value.find(
|
||||
(item) => item.status === 'error',
|
||||
);
|
||||
if (failedImage) {
|
||||
ElMessage.error(failedImage.error || '请处理上传失败的图片');
|
||||
return;
|
||||
}
|
||||
await composer.flush();
|
||||
await sendDraft({
|
||||
...getDraftContext(),
|
||||
prompt,
|
||||
imageUploadIds: composer.images.uploadIds.value,
|
||||
images: composer.images.readyItems.value.map((item) => ({ ...item })),
|
||||
sessionId: composer.sessionId.value,
|
||||
onAccepted: () =>
|
||||
composer.markAccepted().catch(() => {
|
||||
ElMessage.warning('消息已发送,草稿将在过期后自动清理');
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function handleDraftTextInput() {
|
||||
composer.scheduleSave();
|
||||
}
|
||||
|
||||
function handleSuggestedQuestion(question: string) {
|
||||
void handleSend(question);
|
||||
}
|
||||
@@ -137,12 +173,49 @@ function handleSelectNextVariant(item: ChatTimelineMessageItem) {
|
||||
async function handleClearSession() {
|
||||
try {
|
||||
await clearDraftSession();
|
||||
await composer.clear();
|
||||
await activateComposer();
|
||||
ElMessage.success('已清理会话');
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '清理会话失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddFiles(files: File[]) {
|
||||
if (!props.agent.id) {
|
||||
ElMessage.warning('请先保存智能体');
|
||||
return;
|
||||
}
|
||||
await composer.ensureSession();
|
||||
const rejected = await composer.images.addFiles(files, {
|
||||
agentId: String(props.agent.id),
|
||||
mode: 'DRAFT',
|
||||
sessionId: composer.sessionId.value,
|
||||
});
|
||||
if (rejected > 0) {
|
||||
ElMessage.warning('每次最多添加 5 张图片');
|
||||
}
|
||||
composer.scheduleSave();
|
||||
}
|
||||
|
||||
async function handleRetryImage(item: any) {
|
||||
await composer.images.retry(item, {
|
||||
agentId: String(props.agent.id),
|
||||
mode: 'DRAFT',
|
||||
sessionId: composer.sessionId.value,
|
||||
});
|
||||
composer.scheduleSave();
|
||||
}
|
||||
|
||||
async function handleRemoveImage(item: any) {
|
||||
try {
|
||||
await composer.images.remove(item);
|
||||
composer.scheduleSave();
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '图片删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
function handleStop() {
|
||||
if (!loading.value) {
|
||||
return;
|
||||
@@ -189,14 +262,22 @@ async function handleReject(payload: ChatTimelineToolApprovalPayload) {
|
||||
|
||||
<template>
|
||||
<AiChatPanel
|
||||
v-model="composer.text.value"
|
||||
:title="agent.name || '草稿试运行'"
|
||||
empty-text="输入问题试运行当前智能体"
|
||||
closable
|
||||
:messages="[]"
|
||||
:loading="loading"
|
||||
:images="composer.images.items.value"
|
||||
:image-enabled="imageEnabled"
|
||||
:image-loader="loadAgentChatImage"
|
||||
:placeholder="interactionDisplay.inputPlaceholder"
|
||||
:approval-loading="approvalLoading"
|
||||
@send="handleSend"
|
||||
@update:model-value="handleDraftTextInput"
|
||||
@add-files="handleAddFiles"
|
||||
@remove-image="handleRemoveImage"
|
||||
@retry-image="handleRetryImage"
|
||||
@stop="handleStop"
|
||||
@approve="handleApprove"
|
||||
@reject="handleReject"
|
||||
@@ -226,6 +307,7 @@ async function handleReject(payload: ChatTimelineToolApprovalPayload) {
|
||||
<ChatTimeline
|
||||
v-else
|
||||
:items="timelineItems"
|
||||
:image-loader="loadAgentChatImage"
|
||||
empty-text="输入问题试运行当前智能体"
|
||||
:approval-loading="approvalLoading"
|
||||
:copyable="canCopyMessage"
|
||||
|
||||
Reference in New Issue
Block a user