Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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


/**
Expand Down Expand Up @@ -491,6 +493,8 @@ fun AIChatPanel(
}
var pendingAttachments by remember { mutableStateOf<List<PendingUploadAttachment>>(emptyList()) }
var uploadingCount by remember { mutableStateOf(0) }
// 串行化附件上传:两次上传并发时 pendingAttachments 的读-改-写会互相覆盖,导致先选的附件预览丢失。
val attachmentUploadMutex = remember { Mutex() }
var messageForMenu by remember { mutableStateOf<AgentUIMessage?>(null) }
var editingMessage by remember { mutableStateOf<AgentUIMessage?>(null) }
val listState = rememberLazyListState()
Expand Down Expand Up @@ -709,20 +713,23 @@ fun AIChatPanel(
scope.launch {
var successCount = 0
val failures = mutableListOf<String>()
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
) {
Expand Down
Loading