Skip to content

Commit d97fade

Browse files
zzj3720codex-3720
andauthored
Keep inaccessible file refs visible in web chat (#35)
Co-authored-by: codex-3720 <codex-3720@users.noreply.github.com>
1 parent e3446d0 commit d97fade

10 files changed

Lines changed: 670 additions & 40 deletions

File tree

web/src/embedded/chat/components/messages/assistant-message.tsx

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import { CueStreamdownVideo } from '../markdown/overrides/video';
2424
import { AttachmentImage } from './attachment-image';
2525
import { AssistantMessageOperations } from './assistant-message-operations';
2626
import { AssistantStateSection } from './assistant-state-section';
27-
import { FileAttachmentCard } from './file-attachment';
27+
import {
28+
FileAttachmentCard,
29+
shouldRenderFileReferenceFallback,
30+
} from './file-attachment';
2831
import { PermissionMessage } from './permission-message';
2932
import { QuestionMessage } from './question-message';
3033
import { SaveFileMessage } from './save-file-message';
@@ -119,16 +122,30 @@ export type ToolMessageRenderer = (
119122
const AssistantAudio = ({
120123
content,
121124
artifactId,
125+
onRequestFileAccess,
122126
}: {
123127
content: SessionHistoryMessageContent;
124128
artifactId?: string;
129+
onRequestFileAccess?: (message: string) => void | Promise<void>;
125130
}) => {
126131
const resolvedUrl = useResolvedUrl({
127132
src: content.url,
128133
filePath: content.fileRef?.path,
129134
environmentId: content.fileRef?.environmentId,
130135
});
131-
if (!resolvedUrl) return null;
136+
if (!resolvedUrl) {
137+
return shouldRenderFileReferenceFallback(content) ? (
138+
<FileAttachmentCard
139+
filename={content.fileName ?? 'Audio file'}
140+
contentType={content.mimeType ?? 'audio/mpeg'}
141+
path={content.fileRef?.path}
142+
environmentId={content.fileRef?.environmentId ?? undefined}
143+
size=""
144+
data-artifact={artifactId}
145+
onRequestFileAccess={onRequestFileAccess}
146+
/>
147+
) : null;
148+
}
132149
return (
133150
<CueStreamdownAudio
134151
src={resolvedUrl}
@@ -143,15 +160,31 @@ const AssistantAudio = ({
143160

144161
const AssistantVideo = ({
145162
content,
163+
artifactId,
164+
onRequestFileAccess,
146165
}: {
147166
content: SessionHistoryMessageContent;
167+
artifactId?: string;
168+
onRequestFileAccess?: (message: string) => void | Promise<void>;
148169
}) => {
149170
const resolvedUrl = useResolvedUrl({
150171
src: content.url,
151172
filePath: content.fileRef?.path,
152173
environmentId: content.fileRef?.environmentId,
153174
});
154-
if (!resolvedUrl) return null;
175+
if (!resolvedUrl) {
176+
return shouldRenderFileReferenceFallback(content) ? (
177+
<FileAttachmentCard
178+
filename={content.fileName ?? 'Video file'}
179+
contentType={content.mimeType ?? 'video/mp4'}
180+
path={content.fileRef?.path}
181+
environmentId={content.fileRef?.environmentId ?? undefined}
182+
size=""
183+
data-artifact={artifactId}
184+
onRequestFileAccess={onRequestFileAccess}
185+
/>
186+
) : null;
187+
}
155188
return (
156189
<CueStreamdownVideo
157190
src={resolvedUrl}
@@ -231,7 +264,8 @@ function renderContentBlock(
231264
content: SessionHistoryMessageContent,
232265
key: string,
233266
isAnimating: boolean,
234-
messageId: string
267+
messageId: string,
268+
onRequestFileAccess?: (message: string) => void | Promise<void>
235269
) {
236270
switch (content.type) {
237271
case 'text':
@@ -247,6 +281,21 @@ function renderContentBlock(
247281
</div>
248282
) : null;
249283
case 'image':
284+
if (shouldRenderFileReferenceFallback(content)) {
285+
return (
286+
<FileAttachmentCard
287+
key={key}
288+
filename={content.fileName ?? 'Image file'}
289+
contentType={content.mimeType ?? 'image/png'}
290+
path={content.fileRef?.path}
291+
environmentId={content.fileRef?.environmentId ?? undefined}
292+
size=""
293+
data-artifact={key}
294+
onRequestFileAccess={onRequestFileAccess}
295+
/>
296+
);
297+
}
298+
250299
return content.url || content.fileRef?.path ? (
251300
<AttachmentImage
252301
key={key}
@@ -261,11 +310,23 @@ function renderContentBlock(
261310
) : null;
262311
case 'audio':
263312
return content.url || content.fileRef?.path ? (
264-
<AssistantAudio key={key} content={content} artifactId={key} />
313+
<AssistantAudio
314+
key={key}
315+
content={content}
316+
artifactId={key}
317+
onRequestFileAccess={onRequestFileAccess}
318+
/>
265319
) : null;
266320
case 'video':
267321
if (content.url || content.fileRef?.path) {
268-
return <AssistantVideo key={key} content={content} />;
322+
return (
323+
<AssistantVideo
324+
key={key}
325+
content={content}
326+
artifactId={key}
327+
onRequestFileAccess={onRequestFileAccess}
328+
/>
329+
);
269330
}
270331
return null;
271332
case 'file':
@@ -282,6 +343,7 @@ function renderContentBlock(
282343
environmentId={content.fileRef?.environmentId ?? undefined}
283344
size=""
284345
data-artifact={key}
346+
onRequestFileAccess={onRequestFileAccess}
285347
/>
286348
);
287349
default:
@@ -299,6 +361,7 @@ export const AssistantMessage = ({
299361
isAnimating = false,
300362
hideOperations = false,
301363
renderToolMessage,
364+
onSendMessage,
302365
}: {
303366
items: AssistantGroupItem[];
304367
allMessages: SessionHistoryMessage[];
@@ -309,6 +372,7 @@ export const AssistantMessage = ({
309372
isAnimating?: boolean;
310373
hideOperations?: boolean;
311374
renderToolMessage?: ToolMessageRenderer;
375+
onSendMessage?: (text: string) => void;
312376
}) => {
313377
const hasCurrentExecutionState = items.some(
314378
item =>
@@ -633,7 +697,8 @@ export const AssistantMessage = ({
633697
prepared,
634698
`${msg.id || itemIndex}-${ci}`,
635699
isAnimating,
636-
msg.messageId ?? msg.id
700+
msg.messageId ?? msg.id,
701+
onSendMessage
637702
);
638703
})
639704
.filter(Boolean);

web/src/embedded/chat/components/messages/conversation-thread.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ export const ConversationMessageGroupView = ({
267267
activeTurnUserMessageId,
268268
isStreaming,
269269
renderToolMessage,
270+
onSendMessage,
270271
}: {
271272
group: ConversationMessageGroup;
272273
index: number;
@@ -277,6 +278,7 @@ export const ConversationMessageGroupView = ({
277278
activeTurnUserMessageId?: string;
278279
isStreaming: boolean;
279280
renderToolMessage?: ToolMessageRenderer;
281+
onSendMessage?: (text: string) => void;
280282
}) => {
281283
switch (group.type) {
282284
case 'user':
@@ -286,6 +288,7 @@ export const ConversationMessageGroupView = ({
286288
<UserMessage
287289
message={group.message}
288290
enterAnimation={isStreaming && index === totalGroups - 1}
291+
onSendMessage={onSendMessage}
289292
/>
290293
</DebugMessage>
291294
</ErrorBoundary>
@@ -311,6 +314,7 @@ export const ConversationMessageGroupView = ({
311314
isAnimating={isActiveAssistantTurn}
312315
hideOperations={hideOperations}
313316
renderToolMessage={renderToolMessage}
317+
onSendMessage={onSendMessage}
314318
/>
315319
</DebugMessage>
316320
</ErrorBoundary>
@@ -327,12 +331,14 @@ export const ConversationThread = ({
327331
isStreaming,
328332
className,
329333
renderToolMessage,
334+
onSendMessage,
330335
}: {
331336
messages: SessionHistoryMessage[];
332337
assistantStateSequence: AssistantState[];
333338
isStreaming: boolean;
334339
className?: string;
335340
renderToolMessage?: ToolMessageRenderer;
341+
onSendMessage?: (text: string) => void;
336342
}) => {
337343
const {
338344
allMessages,
@@ -361,6 +367,7 @@ export const ConversationThread = ({
361367
activeTurnUserMessageId={activeTurnUserMessageId}
362368
isStreaming={isStreaming}
363369
renderToolMessage={renderToolMessage}
370+
onSendMessage={onSendMessage}
364371
/>
365372
))}
366373
</div>

web/src/embedded/chat/components/messages/copy.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,13 @@ export function collectUserMessageContent(
4646
} else {
4747
result.texts.push(c.text);
4848
}
49-
} else if (c.type === 'image' && c.url) {
49+
} else if (c.type === 'image' && (c.url || c.fileRef?.path)) {
5050
result.images.push(c);
5151
} else if (c.type === 'audio') {
5252
result.audios.push(c);
5353
} else if (c.type === 'video') {
5454
result.videos.push(c);
55-
} else if (c.type === 'file' && c.url) {
55+
} else if (c.type === 'file' && (c.url || c.fileRef?.path)) {
5656
result.files.push({
5757
filename: c.fileName ?? 'unknown',
5858
contentType: c.mimeType ?? 'application/octet-stream',

0 commit comments

Comments
 (0)