Skip to content

Commit b021497

Browse files
committed
fix(desktop): show a staging spinner in the edit composer while OS drops upload
The message-edit composer staged dropped OS files asynchronously with no visible state, so confirming the edit before the upload resolved could send the message without the gateway-side ref (helix4u review note on #43109). Add a staging flag: while uploadOsDropRefs is in flight, show a small spinner pill in the bubble and block submit (disabled send button + submitEdit guard) so the edit can't outrace the ref insertion. New `attachingFile` i18n string across en/zh/zh-hant/ja.
1 parent 891c9a6 commit b021497

6 files changed

Lines changed: 32 additions & 11 deletions

File tree

apps/desktop/src/components/assistant-ui/thread.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,10 @@ const UserEditComposer: FC<UserEditComposerProps> = ({ cwd, gateway, sessionId }
971971
const [triggerPlacement, setTriggerPlacement] = useState<'bottom' | 'top'>('top')
972972
const [focusRequestId, setFocusRequestId] = useState(0)
973973
const [submitting, setSubmitting] = useState(false)
974+
// True while OS-drop files are being staged/uploaded into the session. Blocks
975+
// submit and shows a spinner so confirming the edit can't race the async
976+
// upload and drop the gateway-side ref before it lands in the draft.
977+
const [staging, setStaging] = useState(false)
974978
const expanded = draft.includes('\n')
975979
const canSubmit = draft.trim().length > 0
976980
const at = useAtCompletions({ cwd, gateway, sessionId })
@@ -1324,11 +1328,14 @@ const UserEditComposer: FC<UserEditComposerProps> = ({ cwd, gateway, sessionId }
13241328
}
13251329

13261330
if (osDrops.length) {
1327-
void uploadOsDropRefs(osDrops).then(refs => {
1328-
if (insertRefStrings(refs)) {
1329-
triggerHaptic('selection')
1330-
}
1331-
})
1331+
setStaging(true)
1332+
void uploadOsDropRefs(osDrops)
1333+
.then(refs => {
1334+
if (insertRefStrings(refs)) {
1335+
triggerHaptic('selection')
1336+
}
1337+
})
1338+
.finally(() => setStaging(false))
13321339
}
13331340
}
13341341

@@ -1360,7 +1367,7 @@ const UserEditComposer: FC<UserEditComposerProps> = ({ cwd, gateway, sessionId }
13601367
const submitEdit = (editor: HTMLDivElement) => {
13611368
const nextDraft = syncDraftFromEditor(editor)
13621369

1363-
if (submitting || !nextDraft.trim()) {
1370+
if (submitting || staging || !nextDraft.trim()) {
13641371
return
13651372
}
13661373

@@ -1517,10 +1524,19 @@ const UserEditComposer: FC<UserEditComposerProps> = ({ cwd, gateway, sessionId }
15171524
suppressContentEditableWarning
15181525
/>
15191526
<ComposerPrimitive.Input className="sr-only" tabIndex={-1} unstable_focusOnScrollToBottom={false} />
1527+
{staging && (
1528+
<span
1529+
className="pointer-events-none absolute bottom-2 left-2 inline-flex items-center gap-1 rounded-full bg-background/80 px-1.5 py-0.5 text-[0.62rem] text-muted-foreground backdrop-blur-[1px]"
1530+
data-slot="aui_edit-staging"
1531+
>
1532+
<Loader2Icon className="size-3 animate-spin" />
1533+
{copy.attachingFile}
1534+
</span>
1535+
)}
15201536
<button
15211537
aria-label={copy.sendEdited}
15221538
className={cn('absolute right-2 bottom-2 size-5', USER_ACTION_ICON_BUTTON_CLASS)}
1523-
disabled={!canSubmit || submitting}
1539+
disabled={!canSubmit || submitting || staging}
15241540
onClick={() => {
15251541
const editor = editorRef.current
15261542

apps/desktop/src/i18n/en.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1622,7 +1622,8 @@ export const en: Translations = {
16221622
restoreCheckpoint: 'Restore checkpoint',
16231623
restoreNext: 'Restore next checkpoint',
16241624
goForward: 'Go forward',
1625-
sendEdited: 'Send edited message'
1625+
sendEdited: 'Send edited message',
1626+
attachingFile: 'Attaching…'
16261627
},
16271628
approval: {
16281629
gatewayDisconnected: 'Hermes gateway is not connected',

apps/desktop/src/i18n/ja.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1766,7 +1766,8 @@ export const ja = defineLocale({
17661766
restoreCheckpoint: 'チェックポイントを復元',
17671767
restoreNext: '次のチェックポイントに戻す',
17681768
goForward: '進む',
1769-
sendEdited: '編集済みメッセージを送信'
1769+
sendEdited: '編集済みメッセージを送信',
1770+
attachingFile: '添付中…'
17701771
},
17711772
approval: {
17721773
gatewayDisconnected: 'Hermes ゲートウェイが接続されていません',

apps/desktop/src/i18n/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,7 @@ export interface Translations {
12931293
restoreNext: string
12941294
goForward: string
12951295
sendEdited: string
1296+
attachingFile: string
12961297
}
12971298
approval: {
12981299
gatewayDisconnected: string

apps/desktop/src/i18n/zh-hant.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1727,7 +1727,8 @@ export const zhHant = defineLocale({
17271727
restoreCheckpoint: '還原檢查點',
17281728
restoreNext: '還原至下一個檢查點',
17291729
goForward: '前進',
1730-
sendEdited: '傳送編輯後的訊息'
1730+
sendEdited: '傳送編輯後的訊息',
1731+
attachingFile: '正在附加…'
17311732
},
17321733
approval: {
17331734
gatewayDisconnected: 'Hermes 閘道未連線',

apps/desktop/src/i18n/zh.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1802,7 +1802,8 @@ export const zh: Translations = {
18021802
restoreCheckpoint: '恢复检查点',
18031803
restoreNext: '恢复下一个检查点',
18041804
goForward: '前进',
1805-
sendEdited: '发送编辑后的消息'
1805+
sendEdited: '发送编辑后的消息',
1806+
attachingFile: '正在附加…'
18061807
},
18071808
approval: {
18081809
gatewayDisconnected: 'Hermes 网关未连接',

0 commit comments

Comments
 (0)