返回 presentation-ai
google.ts
根目录 / src / app / _actions / apps / image-studio / google.ts
1 "use server";
2
3 import { type LayoutType } from "@/components/notebook/presentation/utils/parser";
4 import { env } from "@/env";
5 import { auth } from "@/server/auth";
6
7 type GoogleImageSearchItem = {
8 link?: string;
9 title?: string;
10 displayLink?: string;
11 image?: {
12 thumbnailLink?: string;
13 contextLink?: string;
14 width?: number | string;
15 height?: number | string;
16 };
17 };
18
19 type GoogleImageSearchResponse = {
20 items?: GoogleImageSearchItem[];
21 };
22
23 type GoogleImageSearchResult = {
24 url: string;
25 thumb?: string;
26 title?: string;
27 source?: string;
28 width?: number;
29 height?: number;
30 };
31
32 function parseImageDimension(
33 value: number | string | undefined,
34 ): number | undefined {
35 if (typeof value === "number") {
36 return Number.isFinite(value) && value > 0 ? value : undefined;
37 }
38
39 if (typeof value !== "string") {
40 return undefined;
41 }
42
43 const parsedValue = Number.parseInt(value, 10);
44 return Number.isFinite(parsedValue) && parsedValue > 0
45 ? parsedValue
46 : undefined;
47 }
48
49 function isAtLeast1080p(image: GoogleImageSearchResult): boolean {
50 if (!image.width || !image.height) {
51 return false;
52 }
53
54 const shorterSide = Math.min(image.width, image.height);
55 const longerSide = Math.max(image.width, image.height);
56
57 return shorterSide >= 1080 && longerSide >= 1920;
58 }
59
60 function pickBestRelevantGoogleImage(
61 images: GoogleImageSearchResult[],
62 ): GoogleImageSearchResult | undefined {
63 return images.find(isAtLeast1080p) ?? images[0];
64 }
65
66 export async function searchGoogleImages(query: string): Promise<{
67 success: boolean;
68 images?: GoogleImageSearchResult[];
69 error?: string;
70 }> {
71 try {
72 const session = await auth();
73 if (!session?.user?.id) {
74 return { success: false, error: "You must be logged in to get images" };
75 }
76
77 if (!env.GOOGLE_CUSTOM_SEARCH_API_KEY || !env.SEARCH_ENGINE_CX) {
78 return { success: false, error: "Google image search is not configured" };
79 }
80
81 const params = new URLSearchParams({
82 key: env.GOOGLE_CUSTOM_SEARCH_API_KEY,
83 cx: env.SEARCH_ENGINE_CX,
84 searchType: "image",
85 q: query,
86 num: "10",
87 safe: "active",
88 });
89 const url = `https://www.googleapis.com/customsearch/v1?${params.toString()}`;
90 const res = await fetch(url, { next: { revalidate: 300 } });
91 if (!res.ok) {
92 throw new Error(`Google Custom Search API error: ${res.status}`);
93 }
94 const data = (await res.json()) as GoogleImageSearchResponse;
95
96 const images = (data.items ?? []).flatMap((it) => {
97 if (!it.link) {
98 return [];
99 }
100
101 const width = parseImageDimension(it.image?.width);
102 const height = parseImageDimension(it.image?.height);
103
104 return [
105 {
106 url: it.link,
107 thumb: it.image?.thumbnailLink,
108 title: it.title,
109 source: it.image?.contextLink ?? it.displayLink,
110 width,
111 height,
112 },
113 ];
114 });
115 return { success: true, images };
116 } catch (error) {
117 console.error("Error searching Google images:", error);
118 return {
119 success: false,
120 error: error instanceof Error ? error.message : "Failed to search images",
121 };
122 }
123 }
124
125 export async function getImageFromGoogle(
126 query: string,
127 _layoutType?: LayoutType,
128 ): Promise<{ success: boolean; imageUrl?: string; error?: string }> {
129 try {
130 const session = await auth();
131 if (!session?.user?.id) {
132 return { success: false, error: "You must be logged in to get images" };
133 }
134
135 const res = await searchGoogleImages(query);
136 if (!res.success || !res.images || res.images.length === 0) {
137 return { success: false, error: "No images found for this query" };
138 }
139 const selectedImage = pickBestRelevantGoogleImage(res.images);
140 if (!selectedImage?.url) {
141 return { success: false, error: "No images found for this query" };
142 }
143 return { success: true, imageUrl: selectedImage.url };
144 } catch (error) {
145 console.error("Error getting Google image:", error);
146 return {
147 success: false,
148 error: error instanceof Error ? error.message : "Failed to get image",
149 };
150 }
151 }
152
152 lines TYPESCRIPT