Skip to content

Commit 707a49c

Browse files
authored
Merge pull request #689 from ThinkEx-OSS/codex/cloudflare-images
feat(images): normalize uploads with Cloudflare Images
2 parents 4294796 + 6ddc851 commit 707a49c

18 files changed

Lines changed: 574 additions & 438 deletions

containers/image-converter/Dockerfile

Lines changed: 0 additions & 11 deletions
This file was deleted.

containers/image-converter/server.mjs

Lines changed: 0 additions & 170 deletions
This file was deleted.

src/features/workspaces/components/ai-chat/AiChatAttachmentItem.tsx

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
AttachmentTrigger,
2222
} from "#/components/ui/attachment";
2323
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "#/components/ui/dialog";
24+
import { Skeleton } from "#/components/ui/skeleton";
2425
import { Spinner } from "#/components/ui/spinner";
2526
import type {
2627
AttachmentData,
@@ -51,7 +52,7 @@ export function AiChatAttachmentItem({
5152
data: AttachmentData;
5253
onRemove?: () => void;
5354
}) {
54-
if (isPreviewableImageAttachment(data)) {
55+
if (isImageAttachment(data)) {
5556
return <AiChatImageAttachment data={data} onRemove={onRemove} />;
5657
}
5758

@@ -64,57 +65,63 @@ export function AiChatAttachmentItem({
6465
);
6566
}
6667

67-
function isPreviewableImageAttachment(
68-
data: AttachmentData,
69-
): data is FileAttachmentData & { status: "ready"; url: string } {
70-
return (
71-
data.type === "file" &&
72-
data.status === "ready" &&
73-
getMediaCategory(data) === "image" &&
74-
Boolean(data.url)
75-
);
68+
function isImageAttachment(data: AttachmentData): data is FileAttachmentData {
69+
return data.type === "file" && getMediaCategory(data) === "image";
7670
}
7771

7872
function AiChatImageAttachment({
7973
data,
8074
onRemove,
8175
}: {
82-
data: FileAttachmentData & { status: "ready"; url: string };
76+
data: FileAttachmentData;
8377
onRemove?: () => void;
8478
}) {
8579
const [isOpen, setIsOpen] = useState(false);
8680
const label = getAttachmentLabel(data);
81+
const imageUrl = data.status === "ready" ? data.url : undefined;
8782

8883
return (
8984
<>
9085
<Attachment
91-
className="cursor-zoom-in focus-within:ring-2"
86+
className={imageUrl ? "cursor-zoom-in focus-within:ring-2" : undefined}
9287
orientation="vertical"
9388
size="default"
89+
state={getAttachmentState(data)}
9490
>
9591
<AttachmentMedia variant="image">
96-
<img
97-
alt={label}
98-
className="size-full object-cover"
99-
height={96}
100-
src={data.url}
101-
width={96}
102-
/>
92+
{imageUrl ? (
93+
<img
94+
alt={label}
95+
className="size-full object-cover"
96+
height={96}
97+
src={imageUrl}
98+
width={96}
99+
/>
100+
) : (
101+
<>
102+
<Skeleton aria-hidden="true" className="size-full rounded-none bg-foreground/10" />
103+
<span className="sr-only">Preparing {label}</span>
104+
</>
105+
)}
103106
</AttachmentMedia>
104-
<AttachmentTrigger aria-label={`Preview ${label}`} onClick={() => setIsOpen(true)} />
107+
{imageUrl ? (
108+
<AttachmentTrigger aria-label={`Preview ${label}`} onClick={() => setIsOpen(true)} />
109+
) : null}
105110
<AiChatAttachmentRemoveAction data={data} onRemove={onRemove} />
106111
</Attachment>
107112

108-
<Dialog open={isOpen} onOpenChange={setIsOpen}>
109-
<DialogContent className="max-w-[min(96vw,900px)] gap-4 p-4 sm:max-w-4xl">
110-
<DialogHeader className="pr-8">
111-
<DialogTitle className="truncate text-base">{label}</DialogTitle>
112-
</DialogHeader>
113-
<div className="flex max-h-[78vh] min-h-0 items-center justify-center overflow-hidden rounded-lg bg-muted/40">
114-
<img alt={label} className="max-h-[78vh] max-w-full object-contain" src={data.url} />
115-
</div>
116-
</DialogContent>
117-
</Dialog>
113+
{imageUrl ? (
114+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
115+
<DialogContent className="max-w-[min(96vw,900px)] gap-4 p-4 sm:max-w-4xl">
116+
<DialogHeader className="pr-8">
117+
<DialogTitle className="truncate text-base">{label}</DialogTitle>
118+
</DialogHeader>
119+
<div className="flex max-h-[78vh] min-h-0 items-center justify-center overflow-hidden rounded-lg bg-muted/40">
120+
<img alt={label} className="max-h-[78vh] max-w-full object-contain" src={imageUrl} />
121+
</div>
122+
</DialogContent>
123+
</Dialog>
124+
) : null}
118125
</>
119126
);
120127
}

src/features/workspaces/conversion/errors.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
export type WorkspaceFileConversionFailure = "conversion_failed" | "output_too_large";
2+
13
export class WorkspaceFileConversionError extends Error {
24
readonly userMessage: string;
35

4-
constructor(message: string, userMessage: string) {
5-
super(message);
6+
constructor(
7+
message: string,
8+
userMessage: string,
9+
readonly failure: WorkspaceFileConversionFailure = "conversion_failed",
10+
options?: ErrorOptions,
11+
) {
12+
super(message, options);
613
this.name = "WorkspaceFileConversionError";
714
this.userMessage = userMessage;
815
}

0 commit comments

Comments
 (0)