DucLai commited on
Commit
28ff47d
·
verified ·
1 Parent(s): eff76f2

Update script.js

Browse files
Files changed (1) hide show
  1. script.js +326 -328
script.js CHANGED
@@ -1,329 +1,327 @@
1
- class SketchToRenderApp {
2
- constructor() {
3
- this.canvas = document.getElementById('drawingCanvas');
4
- this.ctx = this.canvas.getContext('2d');
5
- this.isDrawing = false;
6
- this.drawingHistory = [];
7
- this.currentPath = [];
8
- this.renderedImageUrl = null;
9
-
10
- // API Key được nhúng trực tiếp trong code
11
- this.apiKey = 'AIzaSyCinF1_Z7XXPzu_zvMzifKyPIy7i7eUBGc'; // Thay thế bằng API key thực tế
12
-
13
- this.initializeCanvas();
14
- this.setupEventListeners();
15
- this.saveCanvasState();
16
- }
17
-
18
- initializeCanvas() {
19
- this.ctx.lineCap = 'round';
20
- this.ctx.lineJoin = 'round';
21
- this.ctx.fillStyle = 'white';
22
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
23
- }
24
-
25
- setupEventListeners() {
26
- // Canvas drawing events
27
- this.canvas.addEventListener('mousedown', (e) => this.startDrawing(e));
28
- this.canvas.addEventListener('mousemove', (e) => this.draw(e));
29
- this.canvas.addEventListener('mouseup', () => this.stopDrawing());
30
- this.canvas.addEventListener('mouseout', () => this.stopDrawing());
31
-
32
- // Touch events for mobile
33
- this.canvas.addEventListener('touchstart', (e) => {
34
- e.preventDefault();
35
- const touch = e.touches[0];
36
- const mouseEvent = new MouseEvent('mousedown', {
37
- clientX: touch.clientX,
38
- clientY: touch.clientY
39
- });
40
- this.canvas.dispatchEvent(mouseEvent);
41
- });
42
-
43
- this.canvas.addEventListener('touchmove', (e) => {
44
- e.preventDefault();
45
- const touch = e.touches[0];
46
- const mouseEvent = new MouseEvent('mousemove', {
47
- clientX: touch.clientX,
48
- clientY: touch.clientY
49
- });
50
- this.canvas.dispatchEvent(mouseEvent);
51
- });
52
-
53
- this.canvas.addEventListener('touchend', (e) => {
54
- e.preventDefault();
55
- const mouseEvent = new MouseEvent('mouseup', {});
56
- this.canvas.dispatchEvent(mouseEvent);
57
- });
58
-
59
- // Control events
60
- document.getElementById('brushSize').addEventListener('input', (e) => {
61
- document.getElementById('brushSizeValue').textContent = e.target.value + 'px';
62
- });
63
-
64
- document.getElementById('clearCanvas').addEventListener('click', () => {
65
- this.clearCanvas();
66
- });
67
-
68
- document.getElementById('undoBtn').addEventListener('click', () => {
69
- this.undo();
70
- });
71
-
72
- document.getElementById('generateBtn').addEventListener('click', () => {
73
- this.generateRender();
74
- });
75
-
76
- document.getElementById('downloadBtn').addEventListener('click', () => {
77
- this.downloadImage();
78
- });
79
- }
80
-
81
- getMousePos(e) {
82
- const rect = this.canvas.getBoundingClientRect();
83
- return {
84
- x: e.clientX - rect.left,
85
- y: e.clientY - rect.top
86
- };
87
- }
88
-
89
- startDrawing(e) {
90
- this.isDrawing = true;
91
- const pos = this.getMousePos(e);
92
- this.currentPath = [pos];
93
-
94
- this.ctx.strokeStyle = document.getElementById('brushColor').value;
95
- this.ctx.lineWidth = document.getElementById('brushSize').value;
96
-
97
- this.ctx.beginPath();
98
- this.ctx.moveTo(pos.x, pos.y);
99
- }
100
-
101
- draw(e) {
102
- if (!this.isDrawing) return;
103
-
104
- const pos = this.getMousePos(e);
105
- this.currentPath.push(pos);
106
-
107
- this.ctx.lineTo(pos.x, pos.y);
108
- this.ctx.stroke();
109
- }
110
-
111
- stopDrawing() {
112
- if (this.isDrawing) {
113
- this.isDrawing = false;
114
- this.saveCanvasState();
115
- }
116
- }
117
-
118
- saveCanvasState() {
119
- this.drawingHistory.push(this.canvas.toDataURL());
120
- if (this.drawingHistory.length > 20) {
121
- this.drawingHistory.shift();
122
- }
123
- }
124
-
125
- clearCanvas() {
126
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
127
- this.ctx.fillStyle = 'white';
128
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
129
- this.saveCanvasState();
130
- }
131
-
132
- undo() {
133
- if (this.drawingHistory.length > 1) {
134
- this.drawingHistory.pop();
135
- const previousState = this.drawingHistory[this.drawingHistory.length - 1];
136
- const img = new Image();
137
- img.onload = () => {
138
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
139
- this.ctx.drawImage(img, 0, 0);
140
- };
141
- img.src = previousState;
142
- }
143
- }
144
-
145
- async generateRender() {
146
- if (!this.apiKey || this.apiKey === 'YOUR_GEMINI_API_KEY_HERE') {
147
- this.showError('Vui lòng cấu hình API Key trong source code');
148
- return;
149
- }
150
-
151
- // Check if canvas is empty
152
- if (this.isCanvasEmpty()) {
153
- this.showError('Vui lòng vẽ gì đó trước khi tạo render');
154
- return;
155
- }
156
-
157
- this.showLoading(true);
158
- this.hideError();
159
-
160
- try {
161
- // Step 1: Analyze sketch with Gemini
162
- const analysisResult = await this.analyzeSketchWithGemini();
163
- console.log('Analysis result:', analysisResult);
164
-
165
- // Step 2: Generate image using the description from Gemini
166
- const imageUrl = await this.generateImageFromDescription(analysisResult.description);
167
-
168
- // Step 3: Display results
169
- this.displayResults(imageUrl, analysisResult.score, analysisResult.prediction);
170
-
171
- } catch (error) {
172
- console.error('Error:', error);
173
- this.showError(`Lỗi: ${error.message}`);
174
- } finally {
175
- this.showLoading(false);
176
- }
177
- }
178
-
179
- isCanvasEmpty() {
180
- const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
181
- return imageData.data.every((pixel, index) => {
182
- if ((index + 1) % 4 === 0) return true; // Alpha channel
183
- return pixel === 255; // White pixels
184
- });
185
- }
186
-
187
- async analyzeSketchWithGemini() {
188
- const canvasDataURL = this.canvas.toDataURL('image/png');
189
- const base64Data = canvasDataURL.split(',')[1];
190
-
191
- const prompt = `Phân tích bức vẽ sketch này và cung cấp:
192
-
193
- 1. ĐIỂM SỐ: Đánh giá chất lượng và độ rõ ràng từ 1–10, với tiêu chuẩn thoải mái (hình được vẽ bởi người bình thường bằng bút đen trên canvas, không yêu cầu đường nét hoàn hảo).
194
- 2. DỰ ĐOÁN: Tên ngắn gọn của sự vật được vẽ (VD: "Mèo", "Ngôi nhà", "Hoa")
195
- 3. MÔ TẢ: Mô tả ngắn gọn để tạo ảnh render (viết bằng tiếng Anh, phong cách artistic)
196
-
197
- Định dạng trả lời:
198
- ĐIỂM: [số]
199
- DỰ ĐOÁN: [tên sự vật]
200
- MÔ TẢ: [mô tả ngắn gọn bằng tiếng Anh]`;
201
-
202
- const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${this.apiKey}`, {
203
- method: 'POST',
204
- headers: {
205
- 'Content-Type': 'application/json',
206
- },
207
- body: JSON.stringify({
208
- contents: [{
209
- parts: [
210
- { text: prompt },
211
- {
212
- inline_data: {
213
- mime_type: "image/png",
214
- data: base64Data
215
- }
216
- }
217
- ]
218
- }]
219
- })
220
- });
221
-
222
- if (!response.ok) {
223
- const errorData = await response.json();
224
- throw new Error(`Gemini API Error: ${errorData.error?.message || 'Unknown error'}`);
225
- }
226
-
227
- const data = await response.json();
228
-
229
- if (data.candidates && data.candidates.length > 0) {
230
- const result = data.candidates[0].content.parts[0].text;
231
- return this.parseGeminiResponse(result);
232
- } else {
233
- throw new Error('Không có kết quả từ Gemini API');
234
- }
235
- }
236
-
237
- parseGeminiResponse(text) {
238
- console.log('Gemini response:', text);
239
-
240
- // Extract score
241
- const scoreMatch = text.match(/ĐIỂM[:\s]*(\d+)/i) || text.match(/score[:\s]*(\d+)/i);
242
- const score = scoreMatch ? parseInt(scoreMatch[1]) : 7;
243
-
244
- // Extract prediction
245
- const predictionMatch = text.match(/DỰ ĐOÁN[:\s]*([^\n]+)/i) || text.match(/prediction[:\s]*([^\n]+)/i);
246
- const prediction = predictionMatch ? predictionMatch[1].trim() : 'Không xác định';
247
-
248
- // Extract description
249
- const descriptionMatch = text.match(/MÔ TẢ[:\s]*([^]*)/i) || text.match(/description[:\s]*([^]*)/i);
250
- let description = descriptionMatch ? descriptionMatch[1].trim() : text;
251
-
252
- // If no specific description found, use the whole response
253
- if (!descriptionMatch) {
254
- // Try to extract the most descriptive part
255
- const lines = text.split('\n').filter(line => line.trim().length > 20);
256
- description = lines.length > 0 ? lines[lines.length - 1] : text;
257
- }
258
-
259
- return {
260
- score: Math.min(Math.max(score, 1), 10), // Ensure score is between 1-10
261
- prediction: prediction.replace(/^["']|["']$/g, ''), // Remove quotes
262
- description: description.replace(/^["']|["']$/g, '') // Remove quotes
263
- };
264
- }
265
-
266
- async generateImageFromDescription(description) {
267
- // Using Pollinations AI (free image generation API)
268
- // This creates an image based on the description from Gemini
269
- const enhancedPrompt = `${description}, high quality, detailed artwork, professional illustration`;
270
- const encodedPrompt = encodeURIComponent(enhancedPrompt);
271
-
272
- // Generate a unique seed to avoid caching
273
- const seed = Math.floor(Math.random() * 1000000);
274
- const imageUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?seed=${seed}&width=512&height=512&model=flux`;
275
-
276
- // Verify the image loads
277
- return new Promise((resolve, reject) => {
278
- const img = new Image();
279
- img.onload = () => resolve(imageUrl);
280
- img.onerror = () => reject(new Error('Không thể tạo ảnh render'));
281
- img.src = imageUrl;
282
- });
283
- }
284
-
285
- displayResults(imageUrl, score, prediction) {
286
- // Display rendered image
287
- const imageContainer = document.getElementById('imageContainer');
288
- imageContainer.innerHTML = `<img src="${imageUrl}" alt="Rendered Image" class="rendered-image">`;
289
-
290
- this.renderedImageUrl = imageUrl;
291
- document.getElementById('downloadBtn').style.display = 'block';
292
-
293
- // Display results
294
- document.getElementById('scoreDisplay').textContent = `${score}/10`;
295
- document.getElementById('predictionText').textContent = prediction;
296
-
297
- // Show results
298
- document.getElementById('resultContent').style.display = 'block';
299
- }
300
-
301
- downloadImage() {
302
- if (this.renderedImageUrl) {
303
- const link = document.createElement('a');
304
- link.download = `sketch-render-${Date.now()}.png`;
305
- link.href = this.renderedImageUrl;
306
- link.click();
307
- }
308
- }
309
-
310
- showLoading(show) {
311
- document.getElementById('loading').style.display = show ? 'block' : 'none';
312
- document.getElementById('resultContent').style.display = show ? 'none' : 'block';
313
- }
314
-
315
- showError(message) {
316
- const errorElement = document.getElementById('errorMessage');
317
- errorElement.textContent = message;
318
- errorElement.style.display = 'block';
319
- }
320
-
321
- hideError() {
322
- document.getElementById('errorMessage').style.display = 'none';
323
- }
324
- }
325
-
326
- // Initialize the app when the page loads
327
- document.addEventListener('DOMContentLoaded', () => {
328
- new SketchToRenderApp();
329
  });
 
1
+ class SketchToRenderApp {
2
+ constructor() {
3
+ this.canvas = document.getElementById('drawingCanvas');
4
+ this.ctx = this.canvas.getContext('2d');
5
+ this.isDrawing = false;
6
+ this.drawingHistory = [];
7
+ this.currentPath = [];
8
+ this.renderedImageUrl = null;
9
+
10
+ // API Key được nhúng trực tiếp trong code
11
+ this.apiKey = 'AIzaSyCinF1_Z7XXPzu_zvMzifKyPIy7i7eUBGc'; // Thay thế bằng API key thực tế
12
+
13
+ this.initializeCanvas();
14
+ this.setupEventListeners();
15
+ this.saveCanvasState();
16
+ }
17
+
18
+ initializeCanvas() {
19
+ this.ctx.lineCap = 'round';
20
+ this.ctx.lineJoin = 'round';
21
+ this.ctx.fillStyle = 'white';
22
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
23
+ }
24
+
25
+ setupEventListeners() {
26
+ // Canvas drawing events
27
+ this.canvas.addEventListener('mousedown', (e) => this.startDrawing(e));
28
+ this.canvas.addEventListener('mousemove', (e) => this.draw(e));
29
+ this.canvas.addEventListener('mouseup', () => this.stopDrawing());
30
+ this.canvas.addEventListener('mouseout', () => this.stopDrawing());
31
+
32
+ // Touch events for mobile
33
+ this.canvas.addEventListener('touchstart', (e) => {
34
+ e.preventDefault();
35
+ const touch = e.touches[0];
36
+ const mouseEvent = new MouseEvent('mousedown', {
37
+ clientX: touch.clientX,
38
+ clientY: touch.clientY
39
+ });
40
+ this.canvas.dispatchEvent(mouseEvent);
41
+ });
42
+
43
+ this.canvas.addEventListener('touchmove', (e) => {
44
+ e.preventDefault();
45
+ const touch = e.touches[0];
46
+ const mouseEvent = new MouseEvent('mousemove', {
47
+ clientX: touch.clientX,
48
+ clientY: touch.clientY
49
+ });
50
+ this.canvas.dispatchEvent(mouseEvent);
51
+ });
52
+
53
+ this.canvas.addEventListener('touchend', (e) => {
54
+ e.preventDefault();
55
+ const mouseEvent = new MouseEvent('mouseup', {});
56
+ this.canvas.dispatchEvent(mouseEvent);
57
+ });
58
+
59
+ // Control events
60
+ document.getElementById('brushSize').addEventListener('input', (e) => {
61
+ document.getElementById('brushSizeValue').textContent = e.target.value + 'px';
62
+ });
63
+
64
+ document.getElementById('clearCanvas').addEventListener('click', () => {
65
+ this.clearCanvas();
66
+ });
67
+
68
+ document.getElementById('undoBtn').addEventListener('click', () => {
69
+ this.undo();
70
+ });
71
+
72
+ document.getElementById('generateBtn').addEventListener('click', () => {
73
+ this.generateRender();
74
+ });
75
+
76
+ document.getElementById('downloadBtn').addEventListener('click', () => {
77
+ this.downloadImage();
78
+ });
79
+ }
80
+
81
+ getMousePos(e) {
82
+ const rect = this.canvas.getBoundingClientRect();
83
+ return {
84
+ x: e.clientX - rect.left,
85
+ y: e.clientY - rect.top
86
+ };
87
+ }
88
+
89
+ startDrawing(e) {
90
+ this.isDrawing = true;
91
+ const pos = this.getMousePos(e);
92
+ this.currentPath = [pos];
93
+
94
+ this.ctx.strokeStyle = document.getElementById('brushColor').value;
95
+ this.ctx.lineWidth = document.getElementById('brushSize').value;
96
+
97
+ this.ctx.beginPath();
98
+ this.ctx.moveTo(pos.x, pos.y);
99
+ }
100
+
101
+ draw(e) {
102
+ if (!this.isDrawing) return;
103
+
104
+ const pos = this.getMousePos(e);
105
+ this.currentPath.push(pos);
106
+
107
+ this.ctx.lineTo(pos.x, pos.y);
108
+ this.ctx.stroke();
109
+ }
110
+
111
+ stopDrawing() {
112
+ if (this.isDrawing) {
113
+ this.isDrawing = false;
114
+ this.saveCanvasState();
115
+ }
116
+ }
117
+
118
+ saveCanvasState() {
119
+ this.drawingHistory.push(this.canvas.toDataURL());
120
+ if (this.drawingHistory.length > 20) {
121
+ this.drawingHistory.shift();
122
+ }
123
+ }
124
+
125
+ clearCanvas() {
126
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
127
+ this.ctx.fillStyle = 'white';
128
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
129
+ this.saveCanvasState();
130
+ }
131
+
132
+ undo() {
133
+ if (this.drawingHistory.length > 1) {
134
+ this.drawingHistory.pop();
135
+ const previousState = this.drawingHistory[this.drawingHistory.length - 1];
136
+ const img = new Image();
137
+ img.onload = () => {
138
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
139
+ this.ctx.drawImage(img, 0, 0);
140
+ };
141
+ img.src = previousState;
142
+ }
143
+ }
144
+
145
+ async generateRender() {
146
+ if (!this.apiKey || this.apiKey === 'YOUR_GEMINI_API_KEY_HERE') {
147
+ this.showError('Vui lòng cấu hình API Key trong source code');
148
+ return;
149
+ }
150
+
151
+ // Check if canvas is empty
152
+ if (this.isCanvasEmpty()) {
153
+ this.showError('Vui lòng vẽ gì đó trước khi tạo render');
154
+ return;
155
+ }
156
+
157
+ this.showLoading(true);
158
+ this.hideError();
159
+
160
+ try {
161
+ // Step 1: Analyze sketch with Gemini
162
+ const analysisResult = await this.analyzeSketchWithGemini();
163
+ console.log('Analysis result:', analysisResult);
164
+
165
+ // Step 2: Generate image using the description from Gemini
166
+ const imageUrl = await this.generateImageFromDescription(analysisResult.description);
167
+
168
+ // Step 3: Display results
169
+ this.displayResults(imageUrl, analysisResult.score, analysisResult.prediction);
170
+
171
+ } catch (error) {
172
+ console.error('Error:', error);
173
+ this.showError(`Lỗi: ${error.message}`);
174
+ } finally {
175
+ this.showLoading(false);
176
+ }
177
+ }
178
+
179
+ isCanvasEmpty() {
180
+ const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
181
+ return imageData.data.every((pixel, index) => {
182
+ if ((index + 1) % 4 === 0) return true; // Alpha channel
183
+ return pixel === 255; // White pixels
184
+ });
185
+ }
186
+
187
+ async analyzeSketchWithGemini() {
188
+ const canvasDataURL = this.canvas.toDataURL('image/png');
189
+ const base64Data = canvasDataURL.split(',')[1];
190
+
191
+ const prompt = `Phân tích bức vẽ sketch này và cung cấp:
192
+
193
+ 1. ĐIỂM SỐ: Đánh giá chất lượng và độ rõ ràng từ 1–10, với tiêu chuẩn thoải mái (hình được vẽ bởi người bình thường bằng bút đen trên canvas, không yêu cầu đường nét hoàn hảo).
194
+ 2. DỰ ĐOÁN: Tên ngắn gọn của sự vật được vẽ (VD: "Mèo", "Ngôi nhà", "Hoa")
195
+ 3. MÔ TẢ: Mô tả ngắn gọn để tạo ảnh render (viết bằng tiếng Anh, phong cách artistic)
196
+
197
+ Định dạng trả lời:
198
+ ĐIỂM: [số]
199
+ DỰ ĐOÁN: [tên sự vật]
200
+ MÔ TẢ: [mô tả ngắn gọn bằng tiếng Anh]`;
201
+
202
+ const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${this.apiKey}`, {
203
+ method: 'POST',
204
+ headers: {
205
+ 'Content-Type': 'application/json',
206
+ },
207
+ body: JSON.stringify({
208
+ contents: [{
209
+ parts: [
210
+ { text: prompt },
211
+ {
212
+ inline_data: {
213
+ mime_type: "image/png",
214
+ data: base64Data
215
+ }
216
+ }
217
+ ]
218
+ }]
219
+ })
220
+ });
221
+
222
+ if (!response.ok) {
223
+ const errorData = await response.json();
224
+ throw new Error(`Gemini API Error: ${errorData.error?.message || 'Unknown error'}`);
225
+ }
226
+
227
+ const data = await response.json();
228
+
229
+ if (data.candidates && data.candidates.length > 0) {
230
+ const result = data.candidates[0].content.parts[0].text;
231
+ return this.parseGeminiResponse(result);
232
+ } else {
233
+ throw new Error('Không có kết quả từ Gemini API');
234
+ }
235
+ }
236
+
237
+ parseGeminiResponse(text) {
238
+ console.log('Gemini response:', text);
239
+
240
+ // Extract score
241
+ const scoreMatch = text.match(/ĐIỂM[:\s]*(\d+)/i) || text.match(/score[:\s]*(\d+)/i);
242
+ const score = scoreMatch ? parseInt(scoreMatch[1]) : 7;
243
+
244
+ // Extract prediction
245
+ const predictionMatch = text.match(/DỰ ĐOÁN[:\s]*([^\n]+)/i) || text.match(/prediction[:\s]*([^\n]+)/i);
246
+ const prediction = predictionMatch ? predictionMatch[1].trim() : 'Không xác định';
247
+
248
+ // Extract description
249
+ const descriptionMatch = text.match(/MÔ TẢ[:\s]*([^]*)/i) || text.match(/description[:\s]*([^]*)/i);
250
+ let description = descriptionMatch ? descriptionMatch[1].trim() : text;
251
+
252
+ // If no specific description found, use the whole response
253
+ if (!descriptionMatch) {
254
+ // Try to extract the most descriptive part
255
+ const lines = text.split('\n').filter(line => line.trim().length > 20);
256
+ description = lines.length > 0 ? lines[lines.length - 1] : text;
257
+ }
258
+
259
+ return {
260
+ score: Math.min(Math.max(score, 1), 10), // Ensure score is between 1-10
261
+ prediction: prediction.replace(/^["']|["']$/g, ''), // Remove quotes
262
+ description: description.replace(/^["']|["']$/g, '') // Remove quotes
263
+ };
264
+ }
265
+
266
+ async generateImageFromDescription(description) {
267
+ // Tạo prompt kèm style
268
+ const enhancedPrompt = `${description}, high quality, detailed artwork, professional illustration`;
269
+ // Dùng seed ngẫu nhiên (0 để Stability tự lấy seed)
270
+ const seed = Math.floor(Math.random() * 1e6);
271
+
272
+ // Thiết lập endpoint headers cho Stability AI
273
+ const engineId = 'stable-diffusion-512-v2-1'; // model 512x512
274
+ const url = `https://api.stability.ai/v1/generation/${engineId}/text-to-image`;
275
+ const response = await fetch(url, {
276
+ method: 'POST',
277
+ headers: {
278
+ 'Content-Type': 'application/json',
279
+ 'Accept': 'application/json',
280
+ 'Authorization': `Bearer ${sk-JzOAcde6bqHRjfInzdrNmNjjrCs0LFAvB838xi14OhhEQ06T }`, // API key Stability đã config
281
+ },
282
+ body: JSON.stringify({
283
+ text_prompts: [{ text: enhancedPrompt }],
284
+ cfg_scale: 7,
285
+ width: 512,
286
+ height: 512,
287
+ samples: 1,
288
+ steps: 30,
289
+ seed: seed
290
+ })
291
+ });
292
+
293
+ if (!response.ok) {
294
+ const err = await response.json().catch(() => ({}));
295
+ throw new Error(`Stability API lỗi: ${err.message || response.statusText}`);
296
+ }
297
+
298
+ const data = await response.json();
299
+ if (data.artifacts && data.artifacts.length > 0) {
300
+ // Chuyển base64 thành URL hiển thị được
301
+ const base64 = data.artifacts[0].base64;
302
+ return `data:image/png;base64,${base64}`;
303
+ } else {
304
+ throw new Error('Không nhận được ảnh từ Stability AI');
305
+ }
306
+ }
307
+
308
+ showLoading(show) {
309
+ document.getElementById('loading').style.display = show ? 'block' : 'none';
310
+ document.getElementById('resultContent').style.display = show ? 'none' : 'block';
311
+ }
312
+
313
+ showError(message) {
314
+ const errorElement = document.getElementById('errorMessage');
315
+ errorElement.textContent = message;
316
+ errorElement.style.display = 'block';
317
+ }
318
+
319
+ hideError() {
320
+ document.getElementById('errorMessage').style.display = 'none';
321
+ }
322
+ }
323
+
324
+ // Initialize the app when the page loads
325
+ document.addEventListener('DOMContentLoaded', () => {
326
+ new SketchToRenderApp();
 
 
327
  });