GEMINI_2_MODEL_DRIFT_NOTE.md ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gemini 2.0 image generation drift note for future AI maintainers
2
+
3
+ This note exists to prevent accidental regressions when editing this Space.
4
+
5
+ ## Do not try to revive Gemini 2.0 Experimental with code
6
+
7
+ Some Gemini 2.0 image-generation model IDs were experimental or preview model IDs. Google can shut those models down server-side. Once a model is shut down, changing JavaScript code or reverting a package cannot make that endpoint available again.
8
+
9
+ Primary source:
10
+ - Gemini API deprecations: https://ai.google.dev/gemini-api/docs/deprecations
11
+ - Gemini API release notes: https://ai.google.dev/gemini-api/docs/changelog
12
+
13
+ Google's deprecations page says that once a model is shut down, it is completely turned off and the endpoint is no longer available.
14
+
15
+ Known affected image-generation IDs include, or have included:
16
+
17
+ - `gemini-2.0-flash-exp`
18
+ - `gemini-2.0-flash-exp-image-generation`
19
+ - `gemini-2.0-flash-preview-image-generation`
20
+
21
+ Keep them only as first-try compatibility options. Do not assume they will work.
22
+
23
+ ## Use the current Google Gen AI SDK, not the old JS SDK
24
+
25
+ The old package:
26
+
27
+ ```txt
28
+ @google/generative-ai
29
+ ```
30
+
31
+ is the older Google AI JavaScript SDK. Do not restore it just because old examples use `GoogleGenerativeAI`.
32
+
33
+ The current package for JavaScript/TypeScript Gemini API examples is:
34
+
35
+ ```txt
36
+ @google/genai
37
+ ```
38
+
39
+ Primary source:
40
+ - Gemini API libraries: https://ai.google.dev/gemini-api/docs/libraries
41
+
42
+ ## Expected strategy for this Space
43
+
44
+ The app should behave like this:
45
+
46
+ 1. Let the user select a preferred model.
47
+ 2. Default to `gemini-2.0-flash-exp` because the original request asked for Gemini 2.0 Experimental.
48
+ 3. If that model fails or returns no image, try current Gemini image models.
49
+ 4. Show an informational notice when fallback happens.
50
+ 5. Do not treat fallback success as a fatal error.
51
+
52
+ Recommended fallback candidates, subject to current Google documentation:
53
+
54
+ ```txt
55
+ gemini-2.0-flash-exp
56
+ gemini-2.0-flash-preview-image-generation
57
+ gemini-3.1-flash-image-preview
58
+ gemini-3-pro-image-preview
59
+ gemini-2.5-flash-image
60
+ ```
61
+
62
+ ## Response shape warning
63
+
64
+ Do not mix response formats from different libraries.
65
+
66
+ With `@google/genai`, read images from response parts such as:
67
+
68
+ ```ts
69
+ response.candidates?.[0]?.content?.parts
70
+ part.inlineData
71
+ part.text
72
+ ```
73
+
74
+ Do not rewrite this route using old snippets that expect a different SDK response shape unless you also update dependencies and test the build.
75
+
76
+ ## Important environment variable
77
+
78
+ The Space needs a secret:
79
+
80
+ ```txt
81
+ GEMINI_API_KEY
82
+ ```
83
+
84
+ Missing `GEMINI_API_KEY` is a runtime/API error, not a Docker build error.
app/api/image/route.ts CHANGED
@@ -1,142 +1,193 @@
1
  import { NextRequest, NextResponse } from "next/server";
2
- import { createGoogleGenerativeAI } from "@ai-sdk/google";
3
- import { generateText } from "ai";
4
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  const MODEL_ID = "gemini-2.0-flash-exp";
 
6
  const MODEL_IDS = [
7
- MODEL_ID,
8
- "gemini-2.5-flash-image",
9
  "gemini-3.1-flash-image-preview",
10
  "gemini-3-pro-image-preview",
 
11
  ];
12
 
13
- interface ImageHistoryPart {
14
- text?: string;
15
- image?: string;
 
 
 
 
16
  }
17
 
18
- interface HistoryMessage {
19
- role: "user" | "model" | "assistant";
20
- parts: ImageHistoryPart[];
 
21
  }
22
 
23
- interface RequestBody {
24
- prompt: string;
25
- image?: string;
26
- history?: HistoryMessage[];
27
- model?: string;
28
  }
29
 
30
- function getModelCandidates(model?: string) {
31
- const selectedModel = model && MODEL_IDS.includes(model) ? model : MODEL_ID;
32
- return [selectedModel, ...MODEL_IDS.filter((id) => id !== selectedModel)];
33
- }
34
 
35
- function getMimeType(dataUrl: string) {
36
- return dataUrl.match(/^data:([^;]+);base64,/)?.[1] ?? "image/png";
 
 
37
  }
38
 
39
- function getBase64Data(dataUrl: string) {
40
- return dataUrl.includes(",") ? dataUrl.split(",")[1] : dataUrl;
41
- }
42
-
43
- function toDataUrl(data: unknown, mediaType = "image/png") {
44
- if (typeof data === "string") {
45
- if (data.startsWith("data:")) {
46
- return data;
47
- }
48
- if (data.startsWith("http://") || data.startsWith("https://")) {
49
- return data;
50
- }
51
- return `data:${mediaType};base64,${data}`;
52
  }
53
 
54
- if (data instanceof Uint8Array) {
55
- return `data:${mediaType};base64,${Buffer.from(data).toString("base64")}`;
 
56
  }
57
 
58
- if (data instanceof ArrayBuffer) {
59
- return `data:${mediaType};base64,${Buffer.from(data).toString("base64")}`;
60
- }
61
-
62
- return null;
63
  }
64
 
65
- function extractImage(result: any) {
66
- for (const file of result.files ?? []) {
67
- const mediaType = file.mediaType ?? file.mimeType ?? "image/png";
68
-
69
- if (!mediaType.startsWith("image/")) {
70
- continue;
71
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- const image =
74
- toDataUrl(file.base64, mediaType) ??
75
- toDataUrl(file.data, mediaType) ??
76
- toDataUrl(file.uint8Array, mediaType) ??
77
- toDataUrl(file.url, mediaType);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- if (image) {
80
- return image;
81
- }
82
  }
83
 
84
- for (const part of result.parts ?? []) {
85
- const mediaType = part.image?.mediaType ?? part.image?.mimeType ?? "image/png";
86
- const image =
87
- toDataUrl(part.image?.data, mediaType) ??
88
- toDataUrl(part.image?.base64, mediaType) ??
89
- toDataUrl(part.image?.url, mediaType);
90
-
91
- if (part.type === "output_image" && image) {
92
- return image;
93
- }
94
-
95
- if ((part.type === "image" || part.type === "file") && mediaType.startsWith("image/") && image) {
96
- return image;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  }
98
  }
99
 
100
- return null;
101
- }
102
-
103
- function formatHistory(history?: HistoryMessage[]) {
104
- return (
105
- history
106
- ?.map((msg) => {
107
- const role = msg.role === "user" ? "user" : "assistant";
108
- const parts = msg.parts
109
- .map((p) => {
110
- if (p.text) {
111
- return { type: "text", text: p.text };
112
- }
113
-
114
- if (p.image && role === "user") {
115
- return {
116
- type: "image",
117
- image: getBase64Data(p.image),
118
- mediaType: getMimeType(p.image),
119
- };
120
- }
121
-
122
- return null;
123
- })
124
- .filter(Boolean);
125
-
126
- if (parts.length === 0) {
127
- return null;
128
- }
129
-
130
- return { role, content: parts };
131
- })
132
- .filter(Boolean) ?? []
133
- );
134
  }
135
 
136
  export async function POST(req: NextRequest) {
137
  try {
138
- const body: RequestBody = await req.json();
139
- const { prompt, image: inputImage, history, model } = body;
 
 
 
 
 
 
 
 
140
 
141
  if (!prompt) {
142
  return NextResponse.json(
@@ -145,88 +196,60 @@ export async function POST(req: NextRequest) {
145
  );
146
  }
147
 
148
- const google = createGoogleGenerativeAI({
149
- apiKey: process.env.GEMINI_API_KEY ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY,
150
- });
151
- const parts: any[] = [{ type: "text", text: prompt }];
152
 
153
- if (inputImage) {
154
- if (!inputImage.startsWith("data:")) {
155
- return NextResponse.json(
156
- { error: "Invalid image data URL format" },
157
- { status: 400 }
158
- );
159
- }
160
-
161
- parts.push({
162
- type: "image",
163
- image: getBase64Data(inputImage),
164
- mediaType: getMimeType(inputImage),
165
- });
166
- }
167
-
168
- const formattedHistory = formatHistory(history);
169
- const modelCandidates = getModelCandidates(model);
170
- const attemptedModels: { model: string; error?: string }[] = [];
171
-
172
- for (const modelId of modelCandidates) {
173
  try {
174
- const result = await generateText({
175
- model: google(modelId),
176
- messages: [...formattedHistory, { role: "user", content: parts }] as any,
177
- providerOptions: {
178
- google: {
179
- responseModalities: ["TEXT", "IMAGE"],
180
- },
181
- },
182
- });
183
-
184
- const image = extractImage(result);
185
 
186
- if (!image) {
187
- attemptedModels.push({ model: modelId, error: "No image returned" });
188
- continue;
189
  }
190
 
191
- const fallback = modelId !== modelCandidates[0];
192
 
 
193
  return NextResponse.json({
194
- description: result.text ?? null,
195
- image,
196
- model: modelId,
 
197
  fallback,
198
  attemptedModels,
199
  notice: fallback
200
  ? {
201
  type: "info",
202
- message: `${modelCandidates[0]} did not return a usable image. The result was generated with ${modelId} instead.`,
203
  }
204
  : null,
205
  });
206
- } catch (err) {
207
- attemptedModels.push({
208
- model: modelId,
209
- error: err instanceof Error ? err.message : "Unknown error",
210
- });
211
  }
212
  }
213
 
214
  return NextResponse.json(
215
  {
216
  error: "Failed to generate image",
217
- details: attemptedModels.map((item) => `${item.model}: ${item.error}`).join("\n"),
218
  attemptedModels,
219
  },
220
  { status: 500 }
221
  );
222
- } catch (err) {
223
- const message =
224
- err instanceof Error ? err.message : "Unknown error";
225
-
226
  return NextResponse.json(
227
  {
228
  error: "Failed to generate image",
229
- details: message,
230
  },
231
  { status: 500 }
232
  );
 
1
  import { NextRequest, NextResponse } from "next/server";
2
+ import { GoogleGenAI, Modality } from "@google/genai";
3
+ import { HistoryItem, HistoryPart } from "@/lib/types";
4
 
5
+ // AI-MAINTAINER NOTE:
6
+ // Do not "fix" this by reverting to @google/generative-ai.
7
+ // Google marks that JS library as legacy/not actively maintained.
8
+ // Gemini 2.0 image models may be shut down server-side; code cannot revive them.
9
+ // Keep Gemini 2.0 as selectable first try, then fall back to current image models.
10
+ // Primary source: https://ai.google.dev/gemini-api/docs/deprecations
11
+
12
+ // Initialize the Google Gen AI client with your API key
13
+ const GEMINI_API_KEY = process.env.GEMINI_API_KEY || "";
14
+ const genAI = new GoogleGenAI({ apiKey: GEMINI_API_KEY });
15
+
16
+ // Define the model ID for Gemini 2.0 Flash experimental
17
  const MODEL_ID = "gemini-2.0-flash-exp";
18
+
19
  const MODEL_IDS = [
20
+ "gemini-2.0-flash-exp",
21
+ "gemini-2.0-flash-preview-image-generation",
22
  "gemini-3.1-flash-image-preview",
23
  "gemini-3-pro-image-preview",
24
+ "gemini-2.5-flash-image",
25
  ];
26
 
27
+ // Define interface for the formatted history item
28
+ interface FormattedHistoryItem {
29
+ role: "user" | "model";
30
+ parts: Array<{
31
+ text?: string;
32
+ inlineData?: { data: string; mimeType: string };
33
+ }>;
34
  }
35
 
36
+ interface ImageResult {
37
+ image: string | null;
38
+ description: string | null;
39
+ model: string;
40
  }
41
 
42
+ interface AttemptResult {
43
+ model: string;
44
+ error: string;
 
 
45
  }
46
 
47
+ function getModelIds(selectedModel?: string) {
48
+ const preferredModel = selectedModel && MODEL_IDS.includes(selectedModel)
49
+ ? selectedModel
50
+ : MODEL_ID;
51
 
52
+ return [
53
+ preferredModel,
54
+ ...MODEL_IDS.filter((modelId) => modelId !== preferredModel),
55
+ ];
56
  }
57
 
58
+ function imageToInlineData(image: string) {
59
+ if (!image.startsWith("data:")) {
60
+ throw new Error("Invalid image data URL format");
 
 
 
 
 
 
 
 
 
 
61
  }
62
 
63
+ const imageParts = image.split(",");
64
+ if (imageParts.length < 2) {
65
+ throw new Error("Invalid image data URL format");
66
  }
67
 
68
+ return {
69
+ data: imageParts[1],
70
+ mimeType: image.includes("image/png") ? "image/png" : "image/jpeg",
71
+ };
 
72
  }
73
 
74
+ function formatHistory(history?: HistoryItem[]) {
75
+ return history && history.length > 0
76
+ ? history
77
+ .map((item: HistoryItem) => {
78
+ return {
79
+ role: item.role,
80
+ parts: item.parts
81
+ .map((part: HistoryPart) => {
82
+ if (part.text) {
83
+ return { text: part.text };
84
+ }
85
+ if (part.image && item.role === "user") {
86
+ return { inlineData: imageToInlineData(part.image) };
87
+ }
88
+ return { text: "" };
89
+ })
90
+ .filter((part) => Object.keys(part).length > 0), // Remove empty parts
91
+ };
92
+ })
93
+ .filter((item: FormattedHistoryItem) => item.parts.length > 0) // Remove items with no parts
94
+ : [];
95
+ }
96
 
97
+ async function generateWithModel(
98
+ model: string,
99
+ prompt: string,
100
+ inputImage: string | null,
101
+ history?: HistoryItem[]
102
+ ): Promise<ImageResult> {
103
+ const contents = formatHistory(history);
104
+ const messageParts = [];
105
+
106
+ // Add the text prompt
107
+ messageParts.push({ text: prompt });
108
+
109
+ // Add the image if provided
110
+ if (inputImage) {
111
+ // For image editing
112
+ console.log("Processing image edit request");
113
+
114
+ const inlineData = imageToInlineData(inputImage);
115
+ console.log(
116
+ "Base64 image length:",
117
+ inlineData.data.length,
118
+ "MIME type:",
119
+ inlineData.mimeType
120
+ );
121
 
122
+ // Add the image to message parts
123
+ messageParts.push({ inlineData });
 
124
  }
125
 
126
+ contents.push({ role: "user", parts: messageParts });
127
+
128
+ // Send the message to the model
129
+ console.log("Sending message with", messageParts.length, "parts", "to", model);
130
+ const response = await genAI.models.generateContent({
131
+ model,
132
+ contents,
133
+ config: {
134
+ temperature: 1,
135
+ topP: 0.95,
136
+ topK: 40,
137
+ responseModalities: [Modality.TEXT, Modality.IMAGE],
138
+ },
139
+ });
140
+
141
+ let textResponse = null;
142
+ let imageData = null;
143
+ let mimeType = "image/png";
144
+
145
+ // Process the response
146
+ if (response.candidates && response.candidates.length > 0) {
147
+ const parts = response.candidates[0].content?.parts || [];
148
+ console.log("Number of parts in response:", parts.length);
149
+
150
+ for (const part of parts) {
151
+ if (part.inlineData) {
152
+ // Get the image data
153
+ imageData = part.inlineData.data || null;
154
+ mimeType = part.inlineData.mimeType || "image/png";
155
+ console.log(
156
+ "Image data received, length:",
157
+ imageData?.length || 0,
158
+ "MIME type:",
159
+ mimeType
160
+ );
161
+ } else if (part.text) {
162
+ // Store the text
163
+ textResponse = part.text;
164
+ console.log(
165
+ "Text response received:",
166
+ textResponse.substring(0, 50) + "..."
167
+ );
168
+ }
169
  }
170
  }
171
 
172
+ return {
173
+ image: imageData ? `data:${mimeType};base64,${imageData}` : null,
174
+ description: textResponse,
175
+ model,
176
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  }
178
 
179
  export async function POST(req: NextRequest) {
180
  try {
181
+ if (!GEMINI_API_KEY) {
182
+ return NextResponse.json(
183
+ { error: "GEMINI_API_KEY is not configured" },
184
+ { status: 500 }
185
+ );
186
+ }
187
+
188
+ // Parse JSON request instead of FormData
189
+ const requestData = await req.json();
190
+ const { prompt, image: inputImage, history, model: selectedModel } = requestData;
191
 
192
  if (!prompt) {
193
  return NextResponse.json(
 
196
  );
197
  }
198
 
199
+ const attemptedModels: AttemptResult[] = [];
200
+ const modelIds = getModelIds(selectedModel);
 
 
201
 
202
+ for (const modelId of modelIds) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  try {
204
+ const result = await generateWithModel(
205
+ modelId,
206
+ prompt,
207
+ inputImage || null,
208
+ history
209
+ );
 
 
 
 
 
210
 
211
+ if (!result.image) {
212
+ throw new Error("No image returned from API");
 
213
  }
214
 
215
+ const fallback = modelId !== modelIds[0];
216
 
217
+ // Return just the base64 image and description as JSON
218
  return NextResponse.json({
219
+ image: result.image,
220
+ description: result.description,
221
+ model: result.model,
222
+ preferredModel: modelIds[0],
223
  fallback,
224
  attemptedModels,
225
  notice: fallback
226
  ? {
227
  type: "info",
228
+ message: `${modelIds[0]} is unavailable or did not return an image. Generated with ${modelId} instead.`,
229
  }
230
  : null,
231
  });
232
+ } catch (error) {
233
+ const message = error instanceof Error ? error.message : String(error);
234
+ console.error(`Error generating image with ${modelId}:`, error);
235
+ attemptedModels.push({ model: modelId, error: message });
 
236
  }
237
  }
238
 
239
  return NextResponse.json(
240
  {
241
  error: "Failed to generate image",
242
+ details: attemptedModels.map((attempt) => `${attempt.model}: ${attempt.error}`).join(" | "),
243
  attemptedModels,
244
  },
245
  { status: 500 }
246
  );
247
+ } catch (error) {
248
+ console.error("Error generating image:", error);
 
 
249
  return NextResponse.json(
250
  {
251
  error: "Failed to generate image",
252
+ details: error instanceof Error ? error.message : String(error),
253
  },
254
  { status: 500 }
255
  );
app/page.tsx CHANGED
@@ -3,17 +3,10 @@ import { useState } from "react";
3
  import { ImageUpload } from "@/components/ImageUpload";
4
  import { ImagePromptInput } from "@/components/ImagePromptInput";
5
  import { ImageResultDisplay } from "@/components/ImageResultDisplay";
6
- import { ImageIcon, Wand2, ExternalLink } from "lucide-react";
7
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8
  import { HistoryItem } from "@/lib/types";
9
 
10
- const MODEL_OPTIONS = [
11
- "gemini-2.0-flash-exp",
12
- "gemini-2.5-flash-image",
13
- "gemini-3.1-flash-image-preview",
14
- "gemini-3-pro-image-preview",
15
- ];
16
-
17
  export default function Home() {
18
  const [image, setImage] = useState<string | null>(null);
19
  const [generatedImage, setGeneratedImage] = useState<string | null>(null);
@@ -21,8 +14,8 @@ export default function Home() {
21
  const [loading, setLoading] = useState(false);
22
  const [error, setError] = useState<string | null>(null);
23
  const [notice, setNotice] = useState<string | null>(null);
 
24
  const [history, setHistory] = useState<HistoryItem[]>([]);
25
- const [selectedModel, setSelectedModel] = useState(MODEL_OPTIONS[0]);
26
 
27
  const handleImageSelect = (imageData: string) => {
28
  setImage(imageData || null);
@@ -42,7 +35,7 @@ export default function Home() {
42
  prompt,
43
  image: imageToEdit,
44
  history: history.length > 0 ? history : undefined,
45
- model: selectedModel,
46
  };
47
 
48
  const response = await fetch("/api/image", {
@@ -142,8 +135,9 @@ export default function Home() {
142
  )}
143
 
144
  {notice && (
145
- <div className="p-4 mb-4 text-sm text-blue-700 bg-blue-100 rounded-lg">
146
- {notice}
 
147
  </div>
148
  )}
149
 
@@ -153,16 +147,18 @@ export default function Home() {
153
  </label>
154
  <select
155
  id="model-select"
156
- value={selectedModel}
157
- onChange={(event) => setSelectedModel(event.target.value)}
 
158
  disabled={loading}
159
- className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
160
  >
161
- {MODEL_OPTIONS.map((model) => (
162
- <option key={model} value={model}>
163
- {model}
164
- </option>
165
- ))}
 
 
166
  </select>
167
  </div>
168
 
 
3
  import { ImageUpload } from "@/components/ImageUpload";
4
  import { ImagePromptInput } from "@/components/ImagePromptInput";
5
  import { ImageResultDisplay } from "@/components/ImageResultDisplay";
6
+ import { ImageIcon, Wand2, ExternalLink, Info } from "lucide-react";
7
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8
  import { HistoryItem } from "@/lib/types";
9
 
 
 
 
 
 
 
 
10
  export default function Home() {
11
  const [image, setImage] = useState<string | null>(null);
12
  const [generatedImage, setGeneratedImage] = useState<string | null>(null);
 
14
  const [loading, setLoading] = useState(false);
15
  const [error, setError] = useState<string | null>(null);
16
  const [notice, setNotice] = useState<string | null>(null);
17
+ const [model, setModel] = useState("gemini-2.0-flash-exp");
18
  const [history, setHistory] = useState<HistoryItem[]>([]);
 
19
 
20
  const handleImageSelect = (imageData: string) => {
21
  setImage(imageData || null);
 
35
  prompt,
36
  image: imageToEdit,
37
  history: history.length > 0 ? history : undefined,
38
+ model,
39
  };
40
 
41
  const response = await fetch("/api/image", {
 
135
  )}
136
 
137
  {notice && (
138
+ <div className="p-4 mb-4 text-sm text-blue-700 bg-blue-100 rounded-lg flex items-start gap-2">
139
+ <Info className="h-4 w-4 mt-0.5 shrink-0" />
140
+ <span>{notice}</span>
141
  </div>
142
  )}
143
 
 
147
  </label>
148
  <select
149
  id="model-select"
150
+ value={model}
151
+ onChange={(event) => setModel(event.target.value)}
152
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground"
153
  disabled={loading}
 
154
  >
155
+ <option value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option>
156
+ <option value="gemini-2.0-flash-preview-image-generation">
157
+ gemini-2.0-flash-preview-image-generation
158
+ </option>
159
+ <option value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option>
160
+ <option value="gemini-3-pro-image-preview">gemini-3-pro-image-preview</option>
161
+ <option value="gemini-2.5-flash-image">gemini-2.5-flash-image</option>
162
  </select>
163
  </div>
164
 
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -1,44 +1,40 @@
1
  {
2
- "name": "nextjs-gemini-2-0-pdf-structured-data",
3
- "version": "0.1.0",
4
- "private": true,
5
- "scripts": {
6
- "dev": "next dev --turbopack",
7
- "build": "next build",
8
- "start": "next start",
9
- "lint": "next lint"
10
- },
11
- "dependencies": {
12
- "@ai-sdk/google": "^3.0.67",
13
- "@google-cloud/vertexai": "^1.11.0",
14
- "@google/genai": "^0.6.1",
15
- "@google/generative-ai": "^0.24.1",
16
- "@radix-ui/react-dialog": "^1.1.6",
17
- "@radix-ui/react-popover": "^1.1.6",
18
- "@radix-ui/react-slot": "^1.1.2",
19
- "@wojtekmaj/react-hooks": "^1.22.0",
20
- "ai": "^6.0.174",
21
- "class-variance-authority": "^0.7.1",
22
- "clsx": "^2.1.1",
23
- "lucide-react": "^0.475.0",
24
- "next": "^15.2.4",
25
- "next-themes": "^0.4.4",
26
- "react": "^19.0.0",
27
- "react-dom": "^19.0.0",
28
- "react-dropzone": "^14.3.8",
29
- "tailwind-merge": "^3.0.1",
30
- "tailwindcss-animate": "^1.0.7"
31
- },
32
- "devDependencies": {
33
- "@eslint/eslintrc": "^3",
34
- "@types/node": "^20",
35
- "@types/react": "^19",
36
- "@types/react-dom": "^19",
37
- "eslint": "^9",
38
- "eslint-config-next": "15.1.0",
39
- "postcss": "^8",
40
- "prettier-eslint": "^16.3.0",
41
- "tailwindcss": "^3.4.1",
42
- "typescript": "^5"
43
- }
44
  }
 
1
  {
2
+ "name": "nextjs-gemini-2-0-pdf-structured-data",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev --turbopack",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@google/genai": "^2.0.0",
13
+ "@radix-ui/react-dialog": "^1.1.6",
14
+ "@radix-ui/react-popover": "^1.1.6",
15
+ "@radix-ui/react-slot": "^1.1.2",
16
+ "@wojtekmaj/react-hooks": "^1.22.0",
17
+ "class-variance-authority": "^0.7.1",
18
+ "clsx": "^2.1.1",
19
+ "lucide-react": "^0.475.0",
20
+ "next": "15.1.0",
21
+ "next-themes": "^0.4.4",
22
+ "react": "^19.0.0",
23
+ "react-dom": "^19.0.0",
24
+ "react-dropzone": "^14.3.8",
25
+ "tailwind-merge": "^3.0.1",
26
+ "tailwindcss-animate": "^1.0.7"
27
+ },
28
+ "devDependencies": {
29
+ "@eslint/eslintrc": "^3",
30
+ "@types/node": "^20",
31
+ "@types/react": "^19",
32
+ "@types/react-dom": "^19",
33
+ "eslint": "^9",
34
+ "eslint-config-next": "15.1.0",
35
+ "postcss": "^8",
36
+ "prettier-eslint": "^16.3.0",
37
+ "tailwindcss": "^3.4.1",
38
+ "typescript": "^5"
39
+ }
 
 
 
 
40
  }