| author | |
| committer | |
| log | 34a12bc33f5fba02b0b398e7dff35ca041a87dc7 |
| tree | a85b50df50b71d33436b37acef554815dd2e254f |
| parent | 7afa184a99b266d466cc9517c147a75c3d839ad7 |
| signature | Signed by PGP key B5690EEEBB952194 |
5 files changed, 39 insertions(+), 0 deletions(-)
apps/mobile/src/features/sharing/incoming-share-model.ts+8| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | import { |
| 2 | isProviderSendTurnSupportedImageMimeType, | |
| 2 | 3 | PROVIDER_SEND_TURN_MAX_ATTACHMENTS, |
| 3 | 4 | PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, |
| 4 | 5 | } from "@t3tools/contracts"; |
| ... | ... | @@ -162,6 +163,13 @@ export async function buildIncomingShareDraft(input: { |
| 162 | 163 | await releaseOwnedFiles(input.fileReader, [uri, payload.value]); |
| 163 | 164 | continue; |
| 164 | 165 | } |
| 166 | if (!isProviderSendTurnSupportedImageMimeType(mimeType)) { | |
| 167 | warnings.push( | |
| 168 | `'${resolved?.originalName ?? fallbackName(uri, index, mimeType)}' is not a supported image type.`, | |
| 169 | ); | |
| 170 | await releaseOwnedFiles(input.fileReader, [uri, payload.value]); | |
| 171 | continue; | |
| 172 | } | |
| 165 | 173 | if ( |
| 166 | 174 | resolved?.contentSize !== null && |
| 167 | 175 | resolved?.contentSize !== undefined && |
apps/mobile/src/lib/composerImages.ts+5| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | import { |
| 2 | isProviderSendTurnSupportedImageMimeType, | |
| 2 | 3 | PROVIDER_SEND_TURN_MAX_ATTACHMENTS, |
| 3 | 4 | PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, |
| 4 | 5 | type UploadChatImageAttachment, |
| ... | ... | @@ -98,6 +99,10 @@ export async function pickComposerImages(input: { readonly existingCount: number |
| 98 | 99 | error = `Unsupported file type for '${asset.fileName ?? "image"}'.`; |
| 99 | 100 | continue; |
| 100 | 101 | } |
| 102 | if (!isProviderSendTurnSupportedImageMimeType(mimeType)) { | |
| 103 | error = `'${asset.fileName ?? "image"}' is not a supported image type. Attach GIF, JPEG, PNG, or WebP images.`; | |
| 104 | continue; | |
| 105 | } | |
| 101 | 106 | |
| 102 | 107 | const base64 = asset.base64; |
| 103 | 108 | if (!base64) { |
apps/web/src/components/chat/ChatComposer.tsx+5| ... | ... | @@ -12,6 +12,7 @@ import type { |
| 12 | 12 | ThreadId, |
| 13 | 13 | } from "@t3tools/contracts"; |
| 14 | 14 | import { |
| 15 | isProviderSendTurnSupportedImageMimeType, | |
| 15 | 16 | ProviderDriverKind, |
| 16 | 17 | ProviderInstanceId, |
| 17 | 18 | PROVIDER_SEND_TURN_MAX_ATTACHMENTS, |
| ... | ... | @@ -2305,6 +2306,10 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 2305 | 2306 | error = `Unsupported file type for '${file.name}'. Please attach image files only.`; |
| 2306 | 2307 | continue; |
| 2307 | 2308 | } |
| 2309 | if (!isProviderSendTurnSupportedImageMimeType(file.type)) { | |
| 2310 | error = `'${file.name}' is not a supported image type. Attach GIF, JPEG, PNG, or WebP images.`; | |
| 2311 | continue; | |
| 2312 | } | |
| 2308 | 2313 | if (reservedCount >= PROVIDER_SEND_TURN_MAX_ATTACHMENTS) { |
| 2309 | 2314 | error = `You can attach up to ${PROVIDER_SEND_TURN_MAX_ATTACHMENTS} images per message.`; |
| 2310 | 2315 | break; |
packages/contracts/src/orchestration.test.ts+7| ... | ... | @@ -23,6 +23,7 @@ import { |
| 23 | 23 | ThreadCreatedPayload, |
| 24 | 24 | ThreadTurnDiff, |
| 25 | 25 | ThreadTurnStartRequestedPayload, |
| 26 | isProviderSendTurnSupportedImageMimeType, | |
| 26 | 27 | } from "./orchestration.ts"; |
| 27 | 28 | import { ProviderInstanceId } from "./providerInstance.ts"; |
| 28 | 29 | |
| ... | ... | @@ -935,3 +936,9 @@ it.effect("project favicon overrides accept only supported image files", () => |
| 935 | 936 | assert.strictEqual(invalid._tag, "Failure"); |
| 936 | 937 | }), |
| 937 | 938 | ); |
| 939 | ||
| 940 | it("isProviderSendTurnSupportedImageMimeType accepts raster formats and rejects svg", () => { | |
| 941 | assert.strictEqual(isProviderSendTurnSupportedImageMimeType("image/png"), true); | |
| 942 | assert.strictEqual(isProviderSendTurnSupportedImageMimeType("IMAGE/JPEG"), true); | |
| 943 | assert.strictEqual(isProviderSendTurnSupportedImageMimeType("image/svg+xml"), false); | |
| 944 | }); |
packages/contracts/src/orchestration.ts+14| ... | ... | @@ -144,6 +144,20 @@ export type ProviderUserInputAnswers = typeof ProviderUserInputAnswers.Type; |
| 144 | 144 | export const PROVIDER_SEND_TURN_MAX_INPUT_CHARS = 120_000; |
| 145 | 145 | export const PROVIDER_SEND_TURN_MAX_ATTACHMENTS = 8; |
| 146 | 146 | export const PROVIDER_SEND_TURN_MAX_IMAGE_BYTES = 10 * 1024 * 1024; |
| 147 | export const PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES = [ | |
| 148 | "image/gif", | |
| 149 | "image/jpeg", | |
| 150 | "image/png", | |
| 151 | "image/webp", | |
| 152 | ] as const; | |
| 153 | const PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPE_SET = new Set<string>( | |
| 154 | PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES, | |
| 155 | ); | |
| 156 | ||
| 157 | /** Whether a pasted or picked image mime type can be sent on a provider turn. */ | |
| 158 | export function isProviderSendTurnSupportedImageMimeType(mimeType: string): boolean { | |
| 159 | return PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPE_SET.has(mimeType.toLowerCase()); | |
| 160 | } | |
| 147 | 161 | const PROVIDER_SEND_TURN_MAX_IMAGE_DATA_URL_CHARS = 14_000_000; |
| 148 | 162 | const CHAT_ATTACHMENT_ID_MAX_CHARS = 128; |
| 149 | 163 | // Correlation id is command id by design in this model. |