@@ -2,7 +2,7 @@ import * as Clipboard from "expo-clipboard";
22import * as Haptics from "expo-haptics" ;
33import { LinearGradient } from "expo-linear-gradient" ;
44import { CaretDown , CaretUp , File as FileIcon } from "phosphor-react-native" ;
5- import { useCallback , useState } from "react" ;
5+ import { type ReactNode , useCallback , useState } from "react" ;
66import {
77 Alert ,
88 type LayoutChangeEvent ,
@@ -12,20 +12,38 @@ import {
1212} from "react-native" ;
1313import { formatRelativeTime } from "@/lib/format" ;
1414import { toRgba , useThemeColors } from "@/lib/theme" ;
15- import { MarkdownImage } from "./MarkdownImage" ;
1615import { MarkdownText } from "./MarkdownText" ;
1716
1817export interface HumanMessageAttachment {
1918 kind : "image" | "document" ;
2019 uri : string ;
2120 fileName : string ;
2221 mimeType ?: string ;
22+ // Bytes stored as a cloud run artifact rather than on this device. When set,
23+ // the preview must be resolved through a presigned URL — the raw `uri` points
24+ // at the sandbox filesystem and is not fetchable here.
25+ cloudArtifact ?: { runId : string ; artifactId : string } ;
2326}
2427
2528interface HumanMessageProps {
2629 content : string ;
2730 timestamp ?: number ;
2831 attachments ?: HumanMessageAttachment [ ] ;
32+ // Lets a host (e.g. tasks) resolve cloud-backed image previews. Without one,
33+ // attachments render as plain file chips.
34+ renderAttachment ?: ( attachment : HumanMessageAttachment ) => ReactNode ;
35+ }
36+
37+ export function MessageFileChip ( { fileName } : { fileName : string } ) {
38+ const themeColors = useThemeColors ( ) ;
39+ return (
40+ < View className = "flex-row items-center gap-2 self-start rounded-md border border-gray-6 bg-gray-3 px-2 py-1.5" >
41+ < FileIcon size = { 14 } color = { themeColors . gray [ 11 ] } />
42+ < Text className = "font-mono text-[12px] text-gray-12" numberOfLines = { 1 } >
43+ { fileName }
44+ </ Text >
45+ </ View >
46+ ) ;
2947}
3048
3149const COLLAPSED_MAX_HEIGHT = 160 ;
@@ -34,6 +52,7 @@ export function HumanMessage({
3452 content,
3553 timestamp,
3654 attachments,
55+ renderAttachment,
3756} : HumanMessageProps ) {
3857 const themeColors = useThemeColors ( ) ;
3958 const [ isExpanded , setIsExpanded ] = useState ( false ) ;
@@ -109,28 +128,15 @@ export function HumanMessage({
109128 ) }
110129 { hasAttachments && (
111130 < View className = { hasContent ? "mt-2 gap-2" : "gap-2" } >
112- { attachments ?. map ( ( att ) =>
113- att . kind === "image" ? (
114- < MarkdownImage
115- key = { `${ att . uri } -${ att . fileName } ` }
116- url = { att . uri }
117- alt = { att . fileName }
118- />
119- ) : (
120- < View
121- key = { `${ att . uri } -${ att . fileName } ` }
122- className = "flex-row items-center gap-2 self-start rounded-md border border-gray-6 bg-gray-3 px-2 py-1.5"
123- >
124- < FileIcon size = { 14 } color = { themeColors . gray [ 11 ] } />
125- < Text
126- className = "font-mono text-[12px] text-gray-12"
127- numberOfLines = { 1 }
128- >
129- { att . fileName }
130- </ Text >
131- </ View >
132- ) ,
133- ) }
131+ { attachments ?. map ( ( att ) => (
132+ < View key = { `${ att . uri } -${ att . fileName } ` } >
133+ { renderAttachment ? (
134+ renderAttachment ( att )
135+ ) : (
136+ < MessageFileChip fileName = { att . fileName } />
137+ ) }
138+ </ View >
139+ ) ) }
134140 </ View >
135141 ) }
136142 </ View >
0 commit comments