From 2fa5ae263d324f40d6d72f3a38b776789a3cb11d Mon Sep 17 00:00:00 2001 From: Rely-xcy <179817675+Rely-xcy@users.noreply.github.com> Date: Sat, 26 Sep 2026 10:57:01 +0000 Subject: [PATCH] =?UTF-8?q?fix(agent):=20=E9=99=84=E4=BB=B6=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E4=B8=B2=E8=A1=8C=E5=8C=96=E4=BF=AE=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E4=B8=A2=E9=99=84=E4=BB=B6=20+=20=E7=BC=A9?= =?UTF-8?q?=E7=95=A5=E5=9B=BE=E5=8C=BA=E8=87=AA=E5=8A=A8=E6=BB=9A=E5=88=B0?= =?UTF-8?q?=E6=9C=80=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - handlePickedAttachments 用 Mutex 串行化: 两次上传并发时 pendingAttachments 读-改-写互相覆盖, 先选附件预览丢失 - PendingAttachmentPreviewList 保持横向滑动, 新附件加入自动滚到最右保证可见 --- .../presentation/component/AIChatPanel.kt | 31 ++++++++++++------- .../component/ChatInputAttachments.kt | 8 ++++- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/aicode/feature/agent/presentation/component/AIChatPanel.kt b/app/src/main/java/com/aicode/feature/agent/presentation/component/AIChatPanel.kt index 603e939c..f4811943 100644 --- a/app/src/main/java/com/aicode/feature/agent/presentation/component/AIChatPanel.kt +++ b/app/src/main/java/com/aicode/feature/agent/presentation/component/AIChatPanel.kt @@ -91,6 +91,8 @@ import java.io.File import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock /** @@ -491,6 +493,8 @@ fun AIChatPanel( } var pendingAttachments by remember { mutableStateOf>(emptyList()) } var uploadingCount by remember { mutableStateOf(0) } + // 串行化附件上传:两次上传并发时 pendingAttachments 的读-改-写会互相覆盖,导致先选的附件预览丢失。 + val attachmentUploadMutex = remember { Mutex() } var messageForMenu by remember { mutableStateOf(null) } var editingMessage by remember { mutableStateOf(null) } val listState = rememberLazyListState() @@ -709,20 +713,23 @@ fun AIChatPanel( scope.launch { var successCount = 0 val failures = mutableListOf() - uploadingCount = selected.size - try { - selected.forEach { uri -> - runCatching { - copyUriToWorkspace(context, uri, viewModel.fileAccess, includeImageData = images) - }.onSuccess { uploaded -> - pendingAttachments = pendingAttachments + uploaded.toPendingAttachment() - successCount += 1 - }.onFailure { error -> - failures += (error.message ?: uploadFallbackError(context)) + // 串行化:并发上传会让 pendingAttachments 的读-改-写互相覆盖,先选的附件预览丢失。 + attachmentUploadMutex.withLock { + uploadingCount = selected.size + try { + selected.forEach { uri -> + runCatching { + copyUriToWorkspace(context, uri, viewModel.fileAccess, includeImageData = images) + }.onSuccess { uploaded -> + pendingAttachments = pendingAttachments + uploaded.toPendingAttachment() + successCount += 1 + }.onFailure { error -> + failures += (error.message ?: uploadFallbackError(context)) + } } + } finally { + uploadingCount = 0 } - } finally { - uploadingCount = 0 } // 结果提示:全失败展示首个错误;有文件被上限截断或上传失败时用 partial 文案;全成功用 success 文案。 val skipped = uris.size - selected.size diff --git a/app/src/main/java/com/aicode/feature/agent/presentation/component/ChatInputAttachments.kt b/app/src/main/java/com/aicode/feature/agent/presentation/component/ChatInputAttachments.kt index dd857db2..7a65e385 100644 --- a/app/src/main/java/com/aicode/feature/agent/presentation/component/ChatInputAttachments.kt +++ b/app/src/main/java/com/aicode/feature/agent/presentation/component/ChatInputAttachments.kt @@ -32,6 +32,7 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember @@ -148,10 +149,15 @@ internal fun PendingAttachmentPreviewList( ) { if (attachments.isEmpty()) return + val scrollState = rememberScrollState() + // 新附件加入时自动滚到最右,保证刚上传的附件可见(横向滑动,不改竖向)。 + LaunchedEffect(attachments.size) { + scrollState.animateScrollTo(scrollState.maxValue) + } Row( modifier = Modifier .fillMaxWidth() - .horizontalScroll(rememberScrollState()) + .horizontalScroll(scrollState) .padding(horizontal = Spacing.xs, vertical = Spacing.xs), horizontalArrangement = Arrangement.spacedBy(Spacing.sm) ) {