fix: hide queue attachment action for messages without attachments

Derive attachment presence from saved queue inputs and only show the preview action when attachments exist. Cover queue metadata and button visibility with regression tests.

Release note: Queued messages without attachments no longer show the View attachments button.
This commit is contained in:
mesalogo
2026-09-23 23:44:08 +08:00
parent c71c0adf9e
commit d5f6dc6990
7 changed files with 44 additions and 2 deletions
@@ -4618,6 +4618,22 @@ describe('AssistantDatabase', () => {
}
])
for (const [payload, hasAttachments] of [
[{ prompt: 'text' }, false],
[{ input: { attachments: [] } }, false],
[{ input: { attachments: [{ id: 'attachment' }] } }, true],
[{ attachments: [{ id: 'attachment' }] }, true]
] as const) {
const queued = initial.enqueueConversationUserInput({
conversationId,
label: 'Attachment visibility',
payloadJson: JSON.stringify(payload)
})
expect(queued.hasAttachments).toBe(hasAttachments)
expect(initial.listConversationQueueItems(conversationId)[0]?.hasAttachments).toBe(hasAttachments)
initial.cancelConversationQueueItem(queued.id)
}
const first = initial.enqueueConversationUserInput({
conversationId,
label: '第一条消息',
+11
View File
@@ -782,7 +782,18 @@ function toSchedule(row: ScheduleWithTaskRow): AssistantSchedule {
function toConversationQueueItem(
row: ConversationQueueRow
): ConversationQueueItem {
let hasAttachments = false
if (row.source === 'user' && row.payload_json) {
try {
const payload = JSON.parse(row.payload_json)
const input = payload?.input ?? payload
hasAttachments = Array.isArray(input?.attachments) && input.attachments.length > 0
} catch {
// Invalid queued payloads are reported when previewed or dispatched.
}
}
return {
hasAttachments,
id: row.id,
conversationId: row.conversation_id,
source: row.source,
+1
View File
@@ -311,6 +311,7 @@ function sameConversationQueueItems(
item.id === candidate.id &&
item.conversationId === candidate.conversationId &&
item.source === candidate.source &&
item.hasAttachments === candidate.hasAttachments &&
item.label === candidate.label &&
item.createdAt === candidate.createdAt &&
item.scheduleRunId === candidate.scheduleRunId &&
@@ -81,6 +81,19 @@ describe('ConversationInputQueue', () => {
)
})
it.each([undefined, false, true])('shows attachment preview only for attached inputs (%s)', (hasAttachments) => {
render(
<ConversationInputQueue
items={[{ ...items[1]!, hasAttachments }]}
onError={vi.fn()}
onInterruptAndRun={vi.fn()}
onRemove={vi.fn()}
running={false}
/>
)
expect(screen.queryByTitle('查看附件') !== null).toBe(hasAttachments === true)
})
it('uses run-now wording while idle and reports action errors', async () => {
const onError = vi.fn()
render(
+1 -1
View File
@@ -93,7 +93,7 @@ export const ConversationInputQueue = memo(
{item.error && <small className="knowledge-inline-error" role="alert">{item.error}</small>}
</span>
<span className="conversation-input-queue__actions">
{item.source === 'user' && <button type="button" aria-label={`查看入队附件:${item.label}`} title="查看附件" onClick={() => setViewing(item.id)}><Paperclip aria-hidden="true" size={13} /><span>查看附件</span></button>}
{item.source === 'user' && item.hasAttachments && <button type="button" aria-label={`查看入队附件:${item.label}`} title="查看附件" onClick={() => setViewing(item.id)}><Paperclip aria-hidden="true" size={13} /><span>查看附件</span></button>}
{onRestore && item.source === 'user' && <button type="button" disabled={pendingAction !== undefined} aria-label={`恢复到草稿:${item.label}`} title="恢复到草稿" onClick={() => void runAction(item.id, 'restore', () => onRestore(item.id))}><Undo2 aria-hidden="true" size={13} /><span>恢复到草稿</span></button>}
<button
aria-label={t(
+1
View File
@@ -200,6 +200,7 @@ export type ConversationAttachment = z.infer<
export const conversationQueueItemSchema = z
.object({
hasAttachments: z.boolean().optional(),
error: z.string().max(1000).optional(),
id: assistantIdSchema,
conversationId: assistantIdSchema,