davidlee831117 commited on
Commit
27735e2
ยท
verified ยท
1 Parent(s): b3722ab

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +237 -0
app.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import gradio as gr
4
+ import pandas as pd
5
+ from urllib.parse import urlparse, parse_qs
6
+ from PIL import Image
7
+ from io import BytesIO
8
+ import requests
9
+ from google import genai
10
+
11
+ # --- Google Sheets ็›ธ้—œๅ‡ฝๅผ ---
12
+ def read_google_sheet(sheet_url: str):
13
+ """
14
+ ๅพž Google Sheet ็š„ URL ่ฎ€ๅ–่ณ‡ๆ–™ใ€‚
15
+ """
16
+ if not sheet_url:
17
+ raise gr.Error("่ซ‹ๆไพ› Google Sheet URLใ€‚")
18
+ try:
19
+ def build_csv_url(url: str) -> str:
20
+ parsed = urlparse(url)
21
+ doc_id = parsed.path.strip("/").split("/")[2] if len(parsed.path.strip("/").split("/")) >= 3 and parsed.path.strip("/").split("/")[1] == "d" else None
22
+ gid = parse_qs(parsed.query).get("gid", [None])[0] or parse_qs(parsed.fragment).get("gid", [None])[0] or "0"
23
+ if doc_id:
24
+ return f"https://docs.google.com/spreadsheets/d/{doc_id}/export?format=csv&gid={gid}"
25
+ if "/export" in parsed.path and "format=csv" in parsed.query:
26
+ return url
27
+ return url.replace("/edit#gid=0", "/export?format=csv&gid=0")
28
+
29
+ csv_url = build_csv_url(sheet_url)
30
+ # ๅขžๅŠ  read_csv ็š„ robustness๏ผŒ้˜ฒๆญขๆŸไบ› Google Sheet ๅฐŽ่‡ด็š„ ParserError
31
+ df = pd.read_csv(csv_url, engine='python', on_bad_lines='warn', encoding='utf-8')
32
+ return df
33
+ except Exception as e:
34
+ raise gr.Error(f"่ฎ€ๅ– Google Sheet ๆ™‚็™ผ็”Ÿ้Œฏ่ชค: {e}")
35
+
36
+ def process_sheet_data(sheet_url):
37
+ """่™•็†่ฉฆ็ฎ—่กจ่ณ‡ๆ–™๏ผŒ็‚บ Gradio DataFrame ๆบ–ๅ‚™ใ€‚"""
38
+ try:
39
+ df = read_google_sheet(sheet_url)
40
+ # ๆชขๆŸฅๆ˜ฏๅฆ่‡ณๅฐ‘ๆœ‰ 3 ๅˆ— (็™ฝ่ƒŒๅœ–URL, ๅƒ่€ƒๅœ–URL, ๆ็คบ่ฉž)๏ผŒๅ› ็‚บ็ดขๅผ•ๆœƒๅœจ็จ‹ๅผไธญ็”Ÿๆˆ
41
+ if df.shape[1] < 3:
42
+ error_msg = f"้Œฏ่ชค๏ผšGoogle Sheet ่‡ณๅฐ‘้œ€่ฆ 3 ๅˆ— (็™ฝ่ƒŒๅœ–URL, ๅƒ่€ƒๅœ–URL, ๆ็คบ่ฉž)ใ€‚็›ฎๅ‰ๅชๆœ‰ {df.shape[1]} ๅˆ—ใ€‚"
43
+ raise gr.Error(error_msg)
44
+
45
+ data_list = []
46
+ for i, row in df.iterrows():
47
+ # ็ขบไฟๆœ‰่ถณๅค ็š„ๅˆ—ไธ”่‡ณๅฐ‘็ฌฌไธ€ๅˆ—(็™ฝ่ƒŒๅœ–URL)ไธ็‚บ็ฉบๆ‰ๅŠ ๅ…ฅ
48
+ if len(row) >= 3 and pd.notna(row.iloc[0]):
49
+ data_list.append([i + 2, row.iloc[0], row.iloc[1], row.iloc[2]])
50
+
51
+ log_message = f"ๆˆๅŠŸ่ฎ€ๅ– {len(data_list)} ็ญ†ๆ•ธๆ“šใ€‚"
52
+ return data_list, log_message
53
+ except Exception as e:
54
+ raise gr.Error(f"่™•็†่ฉฆ็ฎ—่กจ่ณ‡ๆ–™ๆ™‚็™ผ็”Ÿ้Œฏ่ชค: {e}")
55
+
56
+ def get_row_data(sheet_url, row_number):
57
+ """ๅพž Google Sheet ่ฎ€ๅ–ๆŒ‡ๅฎšๅˆ—็š„่ณ‡ๆ–™ใ€‚"""
58
+ try:
59
+ df = read_google_sheet(sheet_url)
60
+ # Gradio้กฏ็คบ็š„่กŒๆ•ธๅพž2้–‹ๅง‹๏ผŒDataFrame็ดขๅผ•ๅพž0้–‹ๅง‹๏ผŒๆ‰€ไปฅ้œ€่ฆๆธ›2
61
+ row_index = int(row_number) - 2
62
+ if row_index < 0 or row_index >= df.shape[0]:
63
+ raise gr.Error(f"ๆŒ‡ๅฎš็š„่กŒๆ•ธ {row_number} ไธๅญ˜ๅœจใ€‚")
64
+
65
+ row_data = df.iloc[row_index]
66
+ # ็ขบไฟๅ–ๅ€ผๆ™‚็ดขๅผ•ไธ่ถŠ็•Œ๏ผŒไธฆ่™•็† NaN
67
+ white_back_image_url = row_data.iloc[0] if len(row_data) > 0 and pd.notna(row_data.iloc[0]) else ""
68
+ ref_image_url = row_data.iloc[1] if len(row_data) > 1 and pd.notna(row_data.iloc[1]) else ""
69
+ prompt_text = row_data.iloc[2] if len(row_data) > 2 and pd.notna(row_data.iloc[2]) else ""
70
+
71
+ return white_back_image_url, ref_image_url, prompt_text
72
+ except Exception as e:
73
+ raise gr.Error(f"่ฎ€ๅ–ๆŒ‡ๅฎš่กŒๆ™‚็™ผ็”Ÿ้Œฏ่ชค: {e}")
74
+
75
+ # --- ไธ‹่ผ‰ๅœ–็‰‡ๅ‡ฝๅผ ---
76
+ def load_image_from_url(url: str):
77
+ """ๅพž URL ไธ‹่ผ‰ๅœ–็‰‡ไธฆไปฅ PIL Image ๆ ผๅผๅ›žๅ‚ณใ€‚"""
78
+ if not url or not isinstance(url, str) or not url.strip(): # ๅขžๅŠ ๅฐ็ฉบๅญ—ไธฒ็š„ๆชขๆŸฅ
79
+ return None
80
+ try:
81
+ headers = {
82
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
83
+ }
84
+ response = requests.get(url, timeout=20, headers=headers)
85
+ response.raise_for_status()
86
+ image = Image.open(BytesIO(response.content)).convert("RGB")
87
+ return image
88
+ except requests.exceptions.HTTPError as e:
89
+ gr.Warning(f"ไธ‹่ผ‰ๅœ–็‰‡ๅคฑๆ•—๏ผšHTTP ้Œฏ่ชค {e.response.status_code}")
90
+ return None
91
+ except Exception as e:
92
+ gr.Warning(f"ไธ‹่ผ‰ๅœ–็‰‡ๆ™‚็™ผ็”Ÿๆ„ๅค–้Œฏ่ชค๏ผš{e}")
93
+ return None
94
+
95
+ # --- Gemini ๆ ธๅฟƒๅ‡ฝๅผ ---
96
+ def generate_image(text, images, api_key, model="gemini-2.5-flash-image-preview"):
97
+ """ไฝฟ็”จ Gemini ๆจกๅž‹็”Ÿๆˆๅœ–็‰‡ใ€‚"""
98
+ if not api_key or api_key.strip() == "":
99
+ raise gr.Error("่ซ‹่ผธๅ…ฅๆœ‰ๆ•ˆ็š„ Gemini API ้‡‘้‘ฐใ€‚", duration=10)
100
+ try:
101
+ client = genai.Client(api_key=api_key.strip())
102
+ contents = images + [text]
103
+ response = client.models.generate_content(model=model, contents=contents)
104
+ text_response = ""
105
+ image_path = None
106
+ for part in response.candidates[0].content.parts:
107
+ if part.text is not None:
108
+ text_response += part.text + "\n"
109
+ elif part.inline_data is not None:
110
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
111
+ temp_path = tmp.name
112
+ generated_image = Image.open(BytesIO(part.inline_data.data))
113
+ generated_image.save(temp_path)
114
+ image_path = temp_path
115
+ return image_path, text_response
116
+ except Exception as e:
117
+ raise gr.Error(f"Gemini API ๅ‘ผๅซๅคฑๆ•—: {e}", duration=10)
118
+
119
+ # --- Gradio ไบ’ๅ‹•ๅ‡ฝๅผ ---
120
+ def generate_image_from_row(sheet_url, row_number, gemini_api_key):
121
+ """
122
+ ๆ นๆ“šๆŒ‡ๅฎš็š„่กŒๆ•ธ๏ผŒ็”Ÿๆˆๅœ–็‰‡๏ผŒไธฆ่ฟ”ๅ›ž็™ฝ่ƒŒๅœ–ใ€ๅƒ่€ƒๅœ–ๅ’Œ็”Ÿๆˆ็š„ๅœ–็‰‡ใ€‚
123
+ """
124
+ if not sheet_url:
125
+ raise gr.Error("่ซ‹ๅ…ˆ่ผธๅ…ฅ Google Sheet URLใ€‚", duration=5)
126
+ if not row_number or row_number <= 1:
127
+ raise gr.Error("่ซ‹่ผธๅ…ฅๆœ‰ๆ•ˆ็š„่กŒๆ•ธ (ๅคงๆ–ผ 1)ใ€‚", duration=5)
128
+
129
+ try:
130
+ white_back_url, ref_image_url, prompt_text = get_row_data(sheet_url, row_number)
131
+
132
+ log_message = f"้–‹ๅง‹่™•็†็ฌฌ {row_number} ่กŒ...\n"
133
+ log_message += f"็™ฝ่ƒŒๅœ–URL: {white_back_url if white_back_url else '็„ก'}\n"
134
+ log_message += f"ๅƒ่€ƒๅœ–URL: {ref_image_url if ref_image_url else '็„ก'}\n"
135
+ log_message += f"ๆ็คบ่ฉž: {prompt_text if prompt_text else '็„ก'}\n"
136
+
137
+ images_for_gemini = []
138
+
139
+ # ไธ‹่ผ‰ๅœ–็‰‡
140
+ wb_img = None
141
+ if white_back_url:
142
+ wb_img = load_image_from_url(white_back_url)
143
+ if wb_img:
144
+ images_for_gemini.append(wb_img)
145
+ else:
146
+ log_message += f"่ญฆๅ‘Š๏ผš็„กๆณ•ไธ‹่ผ‰็™ฝ่ƒŒๅœ– '{white_back_url}'ใ€‚\n"
147
+
148
+ ref_img = None
149
+ if ref_image_url:
150
+ ref_img = load_image_from_url(ref_image_url)
151
+ if ref_img:
152
+ images_for_gemini.append(ref_img)
153
+ else:
154
+ log_message += f"่ญฆๅ‘Š๏ผš็„กๆณ•ไธ‹่ผ‰ๅƒ่€ƒๅœ– '{ref_image_url}'ใ€‚\n"
155
+
156
+ if not images_for_gemini:
157
+ # ๅฆ‚ๆžœๆฒ’ๆœ‰ไปปไฝ•ๅœ–็‰‡๏ผŒไฝ†ๆœ‰ๆ็คบ่ฉž๏ผŒๅ˜—่ฉฆ็ด”ๆ–‡ๅญ—็”Ÿๆˆ
158
+ if prompt_text:
159
+ log_message += "ๆฒ’ๆœ‰ๅฏ็”จ็š„่ผธๅ…ฅๅœ–็‰‡๏ผŒๅ˜—่ฉฆๅƒ…ไฝฟ็”จๆ็คบ่ฉž็”Ÿๆˆๅœ–็‰‡ใ€‚\n"
160
+ else:
161
+ return None, None, None, "่ญฆๅ‘Š๏ผš็„กๆ•ˆ็š„ๅœ–็‰‡ URL ไธ”็„กๆ็คบ่ฉž๏ผŒ็„กๆณ•็”Ÿๆˆๅœ–็‰‡ใ€‚", log_message
162
+
163
+ log_message += "ๅœ–็‰‡ๅทฒไธ‹่ผ‰๏ผŒ้–‹ๅง‹ๅ‘ผๅซ Gemini ๆจกๅž‹...\n"
164
+
165
+ image_path, text_response = generate_image(text=prompt_text, images=images_for_gemini, api_key=gemini_api_key)
166
+
167
+ if image_path:
168
+ log_message += "ๅœ–็‰‡็”ŸๆˆๆˆๅŠŸ๏ผ"
169
+ return wb_img, ref_img, image_path, log_message
170
+ else:
171
+ log_message += "ๅœ–็‰‡็”Ÿๆˆๅคฑๆ•—ใ€‚\n"
172
+ log_message += f"Gemini ๆ–‡ๅญ—ๅ›žๆ‡‰: {text_response}"
173
+ return wb_img, ref_img, None, log_message
174
+
175
+ except ValueError:
176
+ return None, None, None, "่กŒๆ•ธๅฟ…้ ˆ็‚บๆ•ธๅญ—ใ€‚", "็”Ÿๆˆๅœ–็‰‡ๆ™‚็™ผ็”Ÿ้Œฏ่ชค๏ผš่กŒๆ•ธๅฟ…้ ˆ็‚บๆ•ธๅญ—ใ€‚"
177
+ except Exception as e:
178
+ return None, None, None, f"็”Ÿๆˆๅœ–็‰‡ๆ™‚็™ผ็”Ÿ้Œฏ่ชค: {e}", f"็”Ÿๆˆๅœ–็‰‡ๆ™‚็™ผ็”Ÿ้Œฏ่ชค: {e}"
179
+
180
+ # --- Gradio ไป‹้ข่จญๅฎš ---
181
+ with gr.Blocks() as demo:
182
+ gr.Markdown("## Google Sheets ๅœ–็‰‡็”Ÿๆˆๅ™จ")
183
+
184
+ with gr.Row(elem_classes="main-content"):
185
+ with gr.Column(elem_classes="input-column"):
186
+ gemini_api_key = gr.Textbox(
187
+ lines=1,
188
+ placeholder="่ซ‹ๅœจๆญค่ผธๅ…ฅไฝ ็š„ Gemini API ้‡‘้‘ฐ",
189
+ label="Gemini API ้‡‘้‘ฐ",
190
+ elem_classes="api-key-input",
191
+ # ๅพž็’ฐๅขƒ่ฎŠๆ•ธ่ฎ€ๅ–้ ่จญๅ€ผ (ๅฆ‚ๆžœๅญ˜ๅœจ)
192
+ value=os.environ.get("GEMINI_API_KEY", "")
193
+ )
194
+ sheet_url_input = gr.Textbox(
195
+ label="Google Sheet URL",
196
+ value="https://docs.google.com/spreadsheets/d/1G3olHxydDIbnyXdh5nnw5TG0akZFeMeYm-25JmCGDLg/edit?gid=0#gid=0"
197
+ )
198
+ process_button = gr.Button("่™•็†่ฉฆ็ฎ—่กจ", elem_classes="generate-btn")
199
+
200
+ with gr.Row():
201
+ row_index_input = gr.Number(label="่ฆ็”Ÿๆˆ็š„่กŒๆ•ธ", precision=0, value=2)
202
+ generate_selected_button = gr.Button("็”Ÿๆˆๆ‰€้ธ่กŒ็š„ๅœ–็‰‡", elem_classes="generate-btn")
203
+
204
+ with gr.Column(elem_classes="output-column"):
205
+ output_dataframe = gr.DataFrame(
206
+ headers=["Index", "็™ฝ่ƒŒๅœ–URL", "ๅƒ่€ƒๅœ–URL", "ๆ็คบ่ฉž"],
207
+ col_count=(4, "fixed"),
208
+ interactive=False,
209
+ label="ๅทฒ่™•็†็š„่ฉฆ็ฎ—่กจๆ•ธๆ“š"
210
+ )
211
+
212
+ # ๆ–ฐๅขž้กฏ็คบ็™ฝ่ƒŒๅœ–ๅ’Œๅƒ่€ƒๅœ–็š„ๆฌ„ไฝ
213
+ with gr.Row():
214
+ original_white_back_output = gr.Image(label="ๅŽŸๅง‹็™ฝ่ƒŒๅœ–", elem_classes="output-gallery", interactive=False)
215
+ original_ref_image_output = gr.Image(label="ๅŽŸๅง‹ๅƒ่€ƒๅœ–", elem_classes="output-gallery", interactive=False)
216
+
217
+ generated_image_output = gr.Image(label="็”Ÿๆˆ็š„ๅœ–็‰‡", elem_classes="output-gallery", interactive=False)
218
+ operation_log_output = gr.Textbox(
219
+ label="ๆ“ไฝœๆ—ฅ่ชŒ",
220
+ lines=10,
221
+ placeholder="ๆ–‡ๅญ—ๅ›žๆ‡‰ๅ’Œๆ—ฅ่ชŒๆœƒ้กฏ็คบๅœจ้€™่ฃกใ€‚"
222
+ )
223
+
224
+ # ๆŒ‰้ˆ•็š„ไบ‹ไปถ็ถๅฎš
225
+ process_button.click(
226
+ fn=process_sheet_data,
227
+ inputs=[sheet_url_input],
228
+ outputs=[output_dataframe, operation_log_output]
229
+ )
230
+
231
+ generate_selected_button.click(
232
+ fn=generate_image_from_row,
233
+ inputs=[sheet_url_input, row_index_input, gemini_api_key],
234
+ outputs=[original_white_back_output, original_ref_image_output, generated_image_output, operation_log_output]
235
+ )
236
+
237
+ demo.queue().launch(mcp_server=True, share=True)