vibefast-cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FINAL-STATUS.md +144 -0
- package/HOW-IT-WORKS.md +559 -0
- package/PLAN.md +453 -0
- package/README.md +129 -0
- package/RECIPES-READY.md +172 -0
- package/STATUS.md +199 -0
- package/SUCCESS.md +259 -0
- package/TESTING-CHECKLIST.md +450 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/64907821e2634080acce34618d2f3d4c/blobs/11f2769953c717e188062bc644da97c1fd1e4d6d0813a226ce7567dba759afab0000019a736fb8d4 +1 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite +0 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite-shm +0 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite-wal +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite-shm +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite-wal +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/vibefast-recipes/blobs/620e8cf7c35d9806da25dee237e1d7e8b2432bd98f755b60e2c7f08a48d2c7b90000019a73736484 +0 -0
- package/cloudflare-worker/MIGRATION.md +160 -0
- package/cloudflare-worker/QUICKSTART.md +200 -0
- package/cloudflare-worker/README.md +242 -0
- package/cloudflare-worker/generate-token.js +32 -0
- package/cloudflare-worker/mini-native@latest.zip +0 -0
- package/cloudflare-worker/setup.sh +143 -0
- package/cloudflare-worker/test-recipe/apps/native/src/app/mini/index.tsx +15 -0
- package/cloudflare-worker/test-recipe/recipe.json +16 -0
- package/cloudflare-worker/worker.js +308 -0
- package/cloudflare-worker/wrangler.toml +13 -0
- package/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +149 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/devices.d.ts +3 -0
- package/dist/commands/devices.d.ts.map +1 -0
- package/dist/commands/devices.js +35 -0
- package/dist/commands/devices.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +67 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +40 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +23 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +16 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/remove.d.ts +3 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +67 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/core/__tests__/journal.test.d.ts +2 -0
- package/dist/core/__tests__/journal.test.d.ts.map +1 -0
- package/dist/core/__tests__/journal.test.js +101 -0
- package/dist/core/__tests__/journal.test.js.map +1 -0
- package/dist/core/__tests__/validate.test.d.ts +2 -0
- package/dist/core/__tests__/validate.test.d.ts.map +1 -0
- package/dist/core/__tests__/validate.test.js +53 -0
- package/dist/core/__tests__/validate.test.js.map +1 -0
- package/dist/core/archive.d.ts +2 -0
- package/dist/core/archive.d.ts.map +1 -0
- package/dist/core/archive.js +59 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/auth.d.ts +15 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +76 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/codemod.d.ts +20 -0
- package/dist/core/codemod.d.ts.map +1 -0
- package/dist/core/codemod.js +150 -0
- package/dist/core/codemod.js.map +1 -0
- package/dist/core/fsx.d.ts +12 -0
- package/dist/core/fsx.d.ts.map +1 -0
- package/dist/core/fsx.js +70 -0
- package/dist/core/fsx.js.map +1 -0
- package/dist/core/http.d.ts +30 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/http.js +95 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/journal.d.ts +18 -0
- package/dist/core/journal.d.ts.map +1 -0
- package/dist/core/journal.js +34 -0
- package/dist/core/journal.js.map +1 -0
- package/dist/core/log.d.ts +8 -0
- package/dist/core/log.d.ts.map +1 -0
- package/dist/core/log.js +9 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/pathGuard.d.ts +3 -0
- package/dist/core/pathGuard.d.ts.map +1 -0
- package/dist/core/pathGuard.js +18 -0
- package/dist/core/pathGuard.js.map +1 -0
- package/dist/core/paths.d.ts +11 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +22 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/validate.d.ts +8 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +27 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/docs/decisions.md +55 -0
- package/package.json +39 -0
- package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/index.ts +4 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
- package/recipes/audio-recorder/recipe.json +22 -0
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/charts/index.tsx +3 -0
- package/recipes/charts/apps/native/src/features/charts/README.md +185 -0
- package/recipes/charts/apps/native/src/features/charts/app/preview.tsx +223 -0
- package/recipes/charts/apps/native/src/features/charts/components/area-chart.tsx +40 -0
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +143 -0
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +196 -0
- package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +65 -0
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +143 -0
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +246 -0
- package/recipes/charts/apps/native/src/features/charts/components/index.ts +10 -0
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +308 -0
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +180 -0
- package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +188 -0
- package/recipes/charts/apps/native/src/features/charts/components/stacked-area-chart.tsx +265 -0
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +322 -0
- package/recipes/charts/apps/native/src/features/charts/data/mock-data.ts +183 -0
- package/recipes/charts/apps/native/src/features/charts/types/index.ts +66 -0
- package/recipes/charts/recipe.json +22 -0
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/app/index.tsx +302 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-input-bar.tsx +469 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +246 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/image-preview-list.tsx +115 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/message-list.tsx +173 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/constants/models.ts +20 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +143 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-config.ts +664 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +359 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-conversation.ts +79 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +207 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/index.ts +86 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/models.ts +162 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/providers.ts +62 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/types.ts +40 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/services/file-uploader.ts +238 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/services/message-handler-service.ts +180 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/types/index.ts +60 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
- package/recipes/chatbot/recipe.json +22 -0
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/gallery.tsx +3 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/index.tsx +3 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/_layout.tsx +25 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +237 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/gallery-image.tsx +26 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +210 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +93 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/models/models.ts +66 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/services/image-gallery-service.ts +98 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/services/image-save-service.ts +121 -0
- package/recipes/image-generator/recipe.json +22 -0
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/quiz/apps/native/src/app/quiz/index.tsx +47 -0
- package/recipes/quiz/apps/native/src/features/quiz/components/question.tsx +67 -0
- package/recipes/quiz/apps/native/src/features/quiz/config.ts +11 -0
- package/recipes/quiz/apps/native/src/features/quiz/index.tsx +133 -0
- package/recipes/quiz/recipe.json +22 -0
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/tracker-app/apps/native/src/app/tracker-app/index.tsx +1 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +108 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/animated-number.tsx +102 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/calorie-card.tsx +66 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/circular-progress.tsx +97 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/floating-add-button.tsx +27 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/macro-card.tsx +80 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/promo-banner.tsx +98 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/recently-logged.tsx +64 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/week-calendar.tsx +68 -0
- package/recipes/tracker-app/recipe.json +22 -0
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/upload-all.sh +32 -0
- package/recipes/voice-bot/apps/native/src/app/voice-bot/index.tsx +27 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/README.md +185 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/conversation-status.tsx +76 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/index.ts +4 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/message-input.tsx +98 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/voice-bot-screen.tsx +173 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/voice-controls.tsx +73 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/index.ts +3 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/services/index.ts +1 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/services/use-voice-bot.ts +161 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/types.ts +29 -0
- package/recipes/voice-bot/recipe.json +22 -0
- package/recipes/voice-bot@latest.zip +0 -0
- package/scripts/create-recipes.mjs +189 -0
- package/src/commands/add.ts +183 -0
- package/src/commands/devices.ts +38 -0
- package/src/commands/doctor.ts +67 -0
- package/src/commands/list.ts +45 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/logout.ts +15 -0
- package/src/commands/remove.ts +78 -0
- package/src/core/__tests__/journal.test.ts +119 -0
- package/src/core/__tests__/validate.test.ts +64 -0
- package/src/core/archive.ts +69 -0
- package/src/core/auth.ts +103 -0
- package/src/core/codemod.ts +211 -0
- package/src/core/fsx.ts +80 -0
- package/src/core/http.ts +136 -0
- package/src/core/journal.ts +64 -0
- package/src/core/log.ts +9 -0
- package/src/core/pathGuard.ts +22 -0
- package/src/core/paths.ts +33 -0
- package/src/core/validate.ts +44 -0
- package/src/index.ts +27 -0
- package/test-critical-cases.mjs +258 -0
- package/tsconfig.json +21 -0
- package/vitest.config.mts +12 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { Feather } from '@expo/vector-icons';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import { ScrollView, View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { Pressable, Text } from '@/components/ui';
|
|
6
|
+
import colors from '@/components/ui/colors';
|
|
7
|
+
import { Modal, useModal } from '@/components/ui/core/overlays/modal';
|
|
8
|
+
import { GeminiIcon } from '@/components/ui/icons/gemini';
|
|
9
|
+
import { OpenaiIcon } from '@/components/ui/icons/openai';
|
|
10
|
+
import { useImageGeneratorSettings } from '@/features/image-generator/hooks/use-image-generator-settings';
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_IMAGE_MODEL_ID,
|
|
13
|
+
getImageModelById,
|
|
14
|
+
getImageModelsByProvider,
|
|
15
|
+
getProviderDisplayName,
|
|
16
|
+
IMAGE_MODEL_PROVIDERS,
|
|
17
|
+
type ImageModel,
|
|
18
|
+
type ImageModelId,
|
|
19
|
+
type ImageProvider,
|
|
20
|
+
} from '@/features/image-generator/models/models';
|
|
21
|
+
import { useThemeConfig } from '@/lib/use-theme-config';
|
|
22
|
+
|
|
23
|
+
const getProviderIcon = (provider: ImageProvider): React.ComponentType<any> => {
|
|
24
|
+
switch (provider) {
|
|
25
|
+
case 'openai':
|
|
26
|
+
return OpenaiIcon;
|
|
27
|
+
case 'gemini':
|
|
28
|
+
return GeminiIcon;
|
|
29
|
+
default:
|
|
30
|
+
return OpenaiIcon;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type ModelSelectorProps = {
|
|
35
|
+
testID?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Image model selector component that mirrors the chatbot model selector UX.
|
|
40
|
+
*/
|
|
41
|
+
export const ImageModelSelector: React.FC<ModelSelectorProps> = ({
|
|
42
|
+
testID = 'image-model-selector',
|
|
43
|
+
}) => {
|
|
44
|
+
const theme = useThemeConfig();
|
|
45
|
+
const modal = useModal();
|
|
46
|
+
const { settings, updateModel } = useImageGeneratorSettings();
|
|
47
|
+
|
|
48
|
+
const currentModel =
|
|
49
|
+
getImageModelById(settings.modelId) ??
|
|
50
|
+
getImageModelById(DEFAULT_IMAGE_MODEL_ID);
|
|
51
|
+
|
|
52
|
+
const getCurrentModelIcon = () => {
|
|
53
|
+
if (!currentModel) return null;
|
|
54
|
+
const IconComponent = getProviderIcon(currentModel.provider);
|
|
55
|
+
return <IconComponent />;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleModelSelect = (modelId: ImageModelId) => {
|
|
59
|
+
updateModel(modelId);
|
|
60
|
+
modal.dismiss();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<Pressable
|
|
66
|
+
onPress={modal.present}
|
|
67
|
+
className="flex-row items-center gap-2.5 rounded-full border border-neutral-300/60 bg-neutral-50 px-4 py-3.5 dark:border-neutral-800 dark:bg-neutral-800"
|
|
68
|
+
style={{ alignSelf: 'flex-start' }}
|
|
69
|
+
accessibilityLabel={`Current model: ${currentModel?.name ?? 'Select image model'}`}
|
|
70
|
+
accessibilityHint="Tap to change image generation model"
|
|
71
|
+
accessibilityRole="button"
|
|
72
|
+
testID={testID}
|
|
73
|
+
>
|
|
74
|
+
<View className="size-4">{getCurrentModelIcon()}</View>
|
|
75
|
+
<View className="max-w-[140px]">
|
|
76
|
+
<Text
|
|
77
|
+
className="text-xs font-medium text-neutral-800 dark:text-neutral-200"
|
|
78
|
+
numberOfLines={1}
|
|
79
|
+
>
|
|
80
|
+
{currentModel?.name ?? 'Select image model'}
|
|
81
|
+
</Text>
|
|
82
|
+
{/* <Text className="text-[10px] text-neutral-500 dark:text-neutral-400">
|
|
83
|
+
{currentModel?.provider
|
|
84
|
+
? getProviderDisplayName(currentModel.provider)
|
|
85
|
+
: getProviderDisplayName("gemini")}
|
|
86
|
+
</Text> */}
|
|
87
|
+
</View>
|
|
88
|
+
<Feather
|
|
89
|
+
name="chevron-down"
|
|
90
|
+
size={14}
|
|
91
|
+
color={theme.dark ? colors.neutral[400] : colors.neutral[500]}
|
|
92
|
+
/>
|
|
93
|
+
</Pressable>
|
|
94
|
+
|
|
95
|
+
<Modal ref={modal.ref} index={0} snapPoints={['70%']}>
|
|
96
|
+
<ScrollView
|
|
97
|
+
className="flex-1"
|
|
98
|
+
showsVerticalScrollIndicator={false}
|
|
99
|
+
contentContainerStyle={{ paddingHorizontal: 24 }}
|
|
100
|
+
>
|
|
101
|
+
<View className="mb-6 mt-2 items-center">
|
|
102
|
+
<Text className="text-xl font-semibold text-neutral-900 dark:text-neutral-50">
|
|
103
|
+
Choose an image model
|
|
104
|
+
</Text>
|
|
105
|
+
<Text className="mt-1 text-center text-xs text-neutral-500 dark:text-neutral-400">
|
|
106
|
+
Different models balance quality, speed, and style. Pick the one
|
|
107
|
+
that fits your workflow.
|
|
108
|
+
</Text>
|
|
109
|
+
</View>
|
|
110
|
+
|
|
111
|
+
{IMAGE_MODEL_PROVIDERS.map((provider) => {
|
|
112
|
+
const providerModels = getImageModelsByProvider(provider);
|
|
113
|
+
if (!providerModels.length) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const ProviderIcon = getProviderIcon(provider);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<View key={provider} className="mb-6">
|
|
121
|
+
<View className="mb-4 flex-row items-center gap-3">
|
|
122
|
+
<View className="size-6">
|
|
123
|
+
<ProviderIcon />
|
|
124
|
+
</View>
|
|
125
|
+
<Text className="text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
|
126
|
+
{getProviderDisplayName(provider)}
|
|
127
|
+
</Text>
|
|
128
|
+
</View>
|
|
129
|
+
|
|
130
|
+
{providerModels.map((model) => (
|
|
131
|
+
<ModelOption
|
|
132
|
+
key={model.id}
|
|
133
|
+
model={model}
|
|
134
|
+
isSelected={settings.modelId === model.id}
|
|
135
|
+
onSelect={() => handleModelSelect(model.id)}
|
|
136
|
+
testID={`${testID}-${model.id}`}
|
|
137
|
+
/>
|
|
138
|
+
))}
|
|
139
|
+
</View>
|
|
140
|
+
);
|
|
141
|
+
})}
|
|
142
|
+
</ScrollView>
|
|
143
|
+
</Modal>
|
|
144
|
+
</>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
type ModelOptionProps = {
|
|
149
|
+
model: ImageModel;
|
|
150
|
+
isSelected: boolean;
|
|
151
|
+
onSelect: () => void;
|
|
152
|
+
testID?: string;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const ModelOption: React.FC<ModelOptionProps> = ({
|
|
156
|
+
model,
|
|
157
|
+
isSelected,
|
|
158
|
+
onSelect,
|
|
159
|
+
testID,
|
|
160
|
+
}) => {
|
|
161
|
+
const theme = useThemeConfig();
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<Pressable
|
|
165
|
+
onPress={onSelect}
|
|
166
|
+
className="mb-3 rounded-2xl border p-4"
|
|
167
|
+
style={{
|
|
168
|
+
borderColor: isSelected
|
|
169
|
+
? theme.dark
|
|
170
|
+
? 'rgba(96, 165, 250, 0.7)'
|
|
171
|
+
: 'rgba(37, 99, 235, 0.7)'
|
|
172
|
+
: theme.dark
|
|
173
|
+
? 'rgba(71, 85, 105, 0.6)'
|
|
174
|
+
: 'rgba(203, 213, 225, 0.7)',
|
|
175
|
+
backgroundColor: isSelected
|
|
176
|
+
? theme.dark
|
|
177
|
+
? 'rgba(37, 99, 235, 0.14)'
|
|
178
|
+
: 'rgba(219, 234, 254, 0.6)'
|
|
179
|
+
: theme.dark
|
|
180
|
+
? 'rgba(51, 65, 85, 0.35)'
|
|
181
|
+
: 'rgba(248, 250, 252, 0.5)',
|
|
182
|
+
}}
|
|
183
|
+
accessibilityLabel={`Select ${model.name}`}
|
|
184
|
+
accessibilityHint="Applies this model for future image generations"
|
|
185
|
+
accessibilityState={{ selected: isSelected }}
|
|
186
|
+
testID={testID}
|
|
187
|
+
>
|
|
188
|
+
<View className="flex-row items-center justify-between">
|
|
189
|
+
<Text className="text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
|
190
|
+
{model.name}
|
|
191
|
+
</Text>
|
|
192
|
+
<View className="flex-row items-center gap-2">
|
|
193
|
+
{model.isPremium ? (
|
|
194
|
+
<Text className="rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-semibold text-amber-700 dark:bg-amber-400/20 dark:text-amber-200">
|
|
195
|
+
Premium
|
|
196
|
+
</Text>
|
|
197
|
+
) : null}
|
|
198
|
+
{model.defaultSize || model.defaultAspectRatio ? (
|
|
199
|
+
<Text className="text-[10px] font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-400">
|
|
200
|
+
{model.defaultSize ?? model.defaultAspectRatio}
|
|
201
|
+
</Text>
|
|
202
|
+
) : null}
|
|
203
|
+
</View>
|
|
204
|
+
</View>
|
|
205
|
+
<Text className="mt-2 text-xs text-neutral-600 dark:text-neutral-300">
|
|
206
|
+
{model.description}
|
|
207
|
+
</Text>
|
|
208
|
+
</Pressable>
|
|
209
|
+
);
|
|
210
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { MaterialIcons } from '@expo/vector-icons';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { Text } from '@/components/ui';
|
|
6
|
+
/**
|
|
7
|
+
* Placeholder component to show before first generation
|
|
8
|
+
*/
|
|
9
|
+
export const ImagePlaceholder: React.FC = () => (
|
|
10
|
+
<View
|
|
11
|
+
className="aspect-square w-full items-center justify-center rounded-lg border-2 border-dashed border-neutral-300 bg-neutral-50 dark:border-neutral-600 dark:bg-neutral-800"
|
|
12
|
+
testID="image-placeholder"
|
|
13
|
+
>
|
|
14
|
+
<View className="items-center justify-center">
|
|
15
|
+
<MaterialIcons
|
|
16
|
+
name="brush"
|
|
17
|
+
size={32}
|
|
18
|
+
color="#9CA3AF"
|
|
19
|
+
style={{ marginBottom: 8 }}
|
|
20
|
+
/>
|
|
21
|
+
<Text className="text-center text-neutral-500 dark:text-neutral-400">
|
|
22
|
+
Generated image will appear here
|
|
23
|
+
</Text>
|
|
24
|
+
</View>
|
|
25
|
+
</View>
|
|
26
|
+
);
|
package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-gallery.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ImageGalleryService,
|
|
5
|
+
type SavedImageMetadata,
|
|
6
|
+
} from '@/features/image-generator/services/image-gallery-service';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook for managing the local image gallery
|
|
10
|
+
*/
|
|
11
|
+
export const useImageGallery = () => {
|
|
12
|
+
const [images, setImages] = useState<SavedImageMetadata[]>([]);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
14
|
+
const [error, setError] = useState<string | null>(null);
|
|
15
|
+
|
|
16
|
+
// Load images from storage
|
|
17
|
+
const loadImages = useCallback(async () => {
|
|
18
|
+
try {
|
|
19
|
+
setIsLoading(true);
|
|
20
|
+
setError(null);
|
|
21
|
+
const galleryImages = await ImageGalleryService.getAllImages();
|
|
22
|
+
setImages(galleryImages);
|
|
23
|
+
} catch (_err) {
|
|
24
|
+
console.error('Failed to load gallery images:', _err);
|
|
25
|
+
setError('Failed to load images');
|
|
26
|
+
} finally {
|
|
27
|
+
setIsLoading(false);
|
|
28
|
+
}
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
// Remove an image from the gallery
|
|
32
|
+
const removeImage = useCallback(async (imageId: string) => {
|
|
33
|
+
try {
|
|
34
|
+
await ImageGalleryService.removeImage(imageId);
|
|
35
|
+
setImages((prev) => prev.filter((img) => img.id !== imageId));
|
|
36
|
+
} catch (_err) {
|
|
37
|
+
console.error('Failed to remove image:', _err);
|
|
38
|
+
setError('Failed to remove image');
|
|
39
|
+
}
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
// Clear all images from the gallery
|
|
43
|
+
const clearGallery = useCallback(async () => {
|
|
44
|
+
try {
|
|
45
|
+
await ImageGalleryService.clearGallery();
|
|
46
|
+
setImages([]);
|
|
47
|
+
} catch (_err) {
|
|
48
|
+
console.error('Failed to clear gallery:', _err);
|
|
49
|
+
setError('Failed to clear gallery');
|
|
50
|
+
}
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
// Refresh the gallery
|
|
54
|
+
const refreshGallery = useCallback(() => {
|
|
55
|
+
loadImages();
|
|
56
|
+
}, [loadImages]);
|
|
57
|
+
|
|
58
|
+
// Load images on mount
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
loadImages();
|
|
61
|
+
}, [loadImages]);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
images,
|
|
65
|
+
isLoading,
|
|
66
|
+
error,
|
|
67
|
+
removeImage,
|
|
68
|
+
clearGallery,
|
|
69
|
+
refreshGallery,
|
|
70
|
+
};
|
|
71
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { defaultCacheService } from '@/core/cache';
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_IMAGE_MODEL_ID,
|
|
6
|
+
getImageModelById,
|
|
7
|
+
getImageModelsByProvider,
|
|
8
|
+
IMAGE_MODELS,
|
|
9
|
+
type ImageModelId,
|
|
10
|
+
type ImageProvider,
|
|
11
|
+
} from '@/features/image-generator/models/models';
|
|
12
|
+
|
|
13
|
+
export type ImageGeneratorSettings = {
|
|
14
|
+
provider: ImageProvider;
|
|
15
|
+
modelId: ImageModelId;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const CACHE_KEY = 'image_generator_settings_v3';
|
|
19
|
+
|
|
20
|
+
const DEFAULT_SETTINGS: ImageGeneratorSettings = {
|
|
21
|
+
provider: 'gemini',
|
|
22
|
+
modelId: DEFAULT_IMAGE_MODEL_ID,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type RawSettings = Partial<ImageGeneratorSettings> & {
|
|
26
|
+
provider?: unknown;
|
|
27
|
+
modelId?: unknown;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const providerHasModel = (
|
|
31
|
+
provider: ImageProvider,
|
|
32
|
+
modelId: ImageModelId,
|
|
33
|
+
): boolean => {
|
|
34
|
+
return getImageModelsByProvider(provider).some(
|
|
35
|
+
(model) => model.id === modelId,
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const normalizeSettings = (
|
|
40
|
+
rawSettings: RawSettings | null | undefined,
|
|
41
|
+
): ImageGeneratorSettings => {
|
|
42
|
+
if (!rawSettings) {
|
|
43
|
+
return DEFAULT_SETTINGS;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const provider =
|
|
47
|
+
rawSettings.provider === 'openai' || rawSettings.provider === 'gemini'
|
|
48
|
+
? rawSettings.provider
|
|
49
|
+
: DEFAULT_SETTINGS.provider;
|
|
50
|
+
|
|
51
|
+
const modelId =
|
|
52
|
+
typeof rawSettings.modelId === 'string' &&
|
|
53
|
+
IMAGE_MODELS.some((model) => model.id === rawSettings.modelId) &&
|
|
54
|
+
providerHasModel(provider, rawSettings.modelId)
|
|
55
|
+
? (rawSettings.modelId as ImageModelId)
|
|
56
|
+
: DEFAULT_IMAGE_MODEL_ID;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
provider,
|
|
60
|
+
modelId,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Hook for managing image generator settings with persistent storage
|
|
66
|
+
*/
|
|
67
|
+
export const useImageGeneratorSettings = () => {
|
|
68
|
+
const [settings, setSettings] =
|
|
69
|
+
useState<ImageGeneratorSettings>(DEFAULT_SETTINGS);
|
|
70
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
71
|
+
|
|
72
|
+
// Load settings from cache on mount
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const loadSettings = async () => {
|
|
75
|
+
try {
|
|
76
|
+
const cached =
|
|
77
|
+
await defaultCacheService.getItem<RawSettings>(CACHE_KEY);
|
|
78
|
+
setSettings(normalizeSettings(cached));
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Failed to load image generator settings:', error);
|
|
81
|
+
} finally {
|
|
82
|
+
setIsLoaded(true);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
loadSettings();
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
// Save settings to cache
|
|
90
|
+
const saveSettings = useCallback(
|
|
91
|
+
async (newSettings: ImageGeneratorSettings) => {
|
|
92
|
+
try {
|
|
93
|
+
const normalized = normalizeSettings(newSettings);
|
|
94
|
+
await defaultCacheService.setItem(CACHE_KEY, normalized);
|
|
95
|
+
setSettings(normalized);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Failed to save image generator settings:', error);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
[],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const updateProvider = useCallback(
|
|
104
|
+
async (provider: ImageProvider) => {
|
|
105
|
+
const providerModels = getImageModelsByProvider(provider);
|
|
106
|
+
const defaultModelForProvider =
|
|
107
|
+
providerModels[0]?.id ?? DEFAULT_IMAGE_MODEL_ID;
|
|
108
|
+
const newSettings: ImageGeneratorSettings = {
|
|
109
|
+
provider,
|
|
110
|
+
modelId: defaultModelForProvider,
|
|
111
|
+
};
|
|
112
|
+
await saveSettings(newSettings);
|
|
113
|
+
},
|
|
114
|
+
[saveSettings],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const updateModel = useCallback(
|
|
118
|
+
async (model: string) => {
|
|
119
|
+
if (typeof model !== 'string') {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const selectedModel = getImageModelById(model as ImageModelId);
|
|
124
|
+
if (!selectedModel) {
|
|
125
|
+
console.warn(
|
|
126
|
+
`[useImageGeneratorSettings] Unsupported model "${model}" received. Reverting to default.`,
|
|
127
|
+
);
|
|
128
|
+
await saveSettings(DEFAULT_SETTINGS);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const provider =
|
|
133
|
+
settings.provider === selectedModel.provider
|
|
134
|
+
? settings.provider
|
|
135
|
+
: selectedModel.provider;
|
|
136
|
+
|
|
137
|
+
const newSettings: ImageGeneratorSettings = {
|
|
138
|
+
provider,
|
|
139
|
+
modelId: selectedModel.id,
|
|
140
|
+
};
|
|
141
|
+
await saveSettings(newSettings);
|
|
142
|
+
},
|
|
143
|
+
[settings.provider, saveSettings],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
settings,
|
|
148
|
+
isLoaded,
|
|
149
|
+
updateProvider,
|
|
150
|
+
updateModel,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Id } from '@vibefast/backend/_generated/dataModel';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Alert } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { imageGeneratorApi } from '@/api-client/image-generator';
|
|
6
|
+
|
|
7
|
+
import { getImageModelById } from '../models/models';
|
|
8
|
+
import { useImageGeneratorSettings } from './use-image-generator-settings';
|
|
9
|
+
|
|
10
|
+
type GeneratedImageData = {
|
|
11
|
+
imageUri: string;
|
|
12
|
+
prompt: string;
|
|
13
|
+
provider: string;
|
|
14
|
+
model: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
imageId?: Id<'generatedImages'>;
|
|
17
|
+
storageId?: Id<'_storage'>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Custom hook for managing image generation state and operations
|
|
22
|
+
*/
|
|
23
|
+
export const useImageGenerator = () => {
|
|
24
|
+
const [prompt, setPrompt] = useState('');
|
|
25
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
26
|
+
const [generatedImageData, setGeneratedImageData] =
|
|
27
|
+
useState<GeneratedImageData | null>(null);
|
|
28
|
+
|
|
29
|
+
const { settings, isLoaded } = useImageGeneratorSettings();
|
|
30
|
+
|
|
31
|
+
const generateImage = imageGeneratorApi.useGenerateImage();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate an image based on the current prompt
|
|
35
|
+
*/
|
|
36
|
+
const handleGenerateImage = async () => {
|
|
37
|
+
if (!prompt.trim()) {
|
|
38
|
+
Alert.alert('Warning', 'Please enter a prompt to generate an image');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!isLoaded) {
|
|
43
|
+
Alert.alert('Please wait', 'Settings are still loading...');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setIsLoading(true);
|
|
48
|
+
try {
|
|
49
|
+
const selectedModel = getImageModelById(settings.modelId);
|
|
50
|
+
const provider = selectedModel?.provider ?? settings.provider;
|
|
51
|
+
const result = await generateImage({
|
|
52
|
+
prompt: prompt.trim(),
|
|
53
|
+
provider,
|
|
54
|
+
model: selectedModel?.id ?? settings.modelId,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
console.log('Frontend received result:', {
|
|
58
|
+
mimeType: result.mimeType,
|
|
59
|
+
dataUriLength: result.imageDataUri.length,
|
|
60
|
+
dataUriPreview: result.imageDataUri.substring(0, 100),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
setGeneratedImageData({
|
|
64
|
+
imageUri: result.imageDataUri,
|
|
65
|
+
prompt: result.prompt,
|
|
66
|
+
provider: result.provider,
|
|
67
|
+
model: result.model,
|
|
68
|
+
mimeType: result.mimeType,
|
|
69
|
+
imageId: result.imageId,
|
|
70
|
+
storageId: result.storageId,
|
|
71
|
+
});
|
|
72
|
+
// Alert.alert('Success', 'Image generated successfully!');
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('Image generation error:', error);
|
|
75
|
+
Alert.alert(
|
|
76
|
+
'Error',
|
|
77
|
+
error instanceof Error ? error.message : 'Failed to generate image',
|
|
78
|
+
);
|
|
79
|
+
} finally {
|
|
80
|
+
setIsLoading(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
prompt,
|
|
86
|
+
setPrompt,
|
|
87
|
+
isLoading,
|
|
88
|
+
generatedImageData,
|
|
89
|
+
generatedImageUri: generatedImageData?.imageUri || null, // backward compatibility
|
|
90
|
+
handleGenerateImage,
|
|
91
|
+
settings,
|
|
92
|
+
};
|
|
93
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type ImageProvider = 'openai' | 'gemini';
|
|
2
|
+
|
|
3
|
+
export type ImageModelId =
|
|
4
|
+
| 'dall-e-3'
|
|
5
|
+
| 'gpt-image-1'
|
|
6
|
+
| 'gemini-2.5-flash-image';
|
|
7
|
+
|
|
8
|
+
export type ImageModel = {
|
|
9
|
+
id: ImageModelId;
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
provider: ImageProvider;
|
|
13
|
+
defaultSize?: string;
|
|
14
|
+
defaultAspectRatio?: string;
|
|
15
|
+
isPremium?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const IMAGE_MODELS: ImageModel[] = [
|
|
19
|
+
{
|
|
20
|
+
id: 'dall-e-3',
|
|
21
|
+
name: 'DALL·E 3',
|
|
22
|
+
description:
|
|
23
|
+
'OpenAI’s flagship model for creative storytelling and illustration.',
|
|
24
|
+
provider: 'openai',
|
|
25
|
+
defaultSize: '1024x1024',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'gpt-image-1',
|
|
29
|
+
name: 'GPT Image 1',
|
|
30
|
+
description:
|
|
31
|
+
'GPT-4o based image model with strong photorealism and typography.',
|
|
32
|
+
provider: 'openai',
|
|
33
|
+
defaultSize: '1024x1024',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'gemini-2.5-flash-image',
|
|
37
|
+
name: 'Gemini 2.5 Flash Image',
|
|
38
|
+
description:
|
|
39
|
+
'Fast and cost-effective image generation ideal for rapid iteration.',
|
|
40
|
+
provider: 'gemini',
|
|
41
|
+
defaultSize: '1024x1024',
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export const DEFAULT_IMAGE_MODEL_ID: ImageModelId = 'gemini-2.5-flash-image';
|
|
46
|
+
|
|
47
|
+
export const getImageModelsByProvider = (provider: ImageProvider) =>
|
|
48
|
+
IMAGE_MODELS.filter((model) => model.provider === provider);
|
|
49
|
+
|
|
50
|
+
export const getImageModelById = (id: ImageModelId) =>
|
|
51
|
+
IMAGE_MODELS.find((model) => model.id === id) ?? null;
|
|
52
|
+
|
|
53
|
+
export const getProviderDisplayName = (provider: ImageProvider) => {
|
|
54
|
+
switch (provider) {
|
|
55
|
+
case 'openai':
|
|
56
|
+
return 'OpenAI';
|
|
57
|
+
case 'gemini':
|
|
58
|
+
return 'Google Gemini';
|
|
59
|
+
default:
|
|
60
|
+
return provider;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const IMAGE_MODEL_PROVIDERS: ImageProvider[] = Array.from(
|
|
65
|
+
new Set(IMAGE_MODELS.map((model) => model.provider)),
|
|
66
|
+
) as ImageProvider[];
|