up fix
This commit is contained in:
parent
7cfe03832c
commit
41d14dcd24
4 changed files with 203 additions and 7 deletions
56
app.py
56
app.py
|
|
@ -12,7 +12,11 @@ from google import genai
|
|||
from google.genai import types
|
||||
from PIL import Image, PngImagePlugin
|
||||
|
||||
import logging
|
||||
|
||||
app = Flask(__name__)
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.setLevel(logging.ERROR)
|
||||
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
|
||||
|
||||
PREVIEW_MAX_DIMENSION = 1024
|
||||
|
|
@ -200,6 +204,7 @@ def generate_image():
|
|||
return jsonify({'error': 'API Key is required.'}), 401
|
||||
|
||||
try:
|
||||
print("Đang gửi lệnh...", flush=True)
|
||||
client = genai.Client(api_key=api_key)
|
||||
|
||||
image_config_args = {
|
||||
|
|
@ -294,6 +299,7 @@ def generate_image():
|
|||
continue
|
||||
|
||||
model_name = "gemini-3-pro-image-preview"
|
||||
print("Đang tạo...", flush=True)
|
||||
response = client.models.generate_content(
|
||||
model=model_name,
|
||||
contents=contents,
|
||||
|
|
@ -302,6 +308,7 @@ def generate_image():
|
|||
image_config=types.ImageConfig(**image_config_args),
|
||||
)
|
||||
)
|
||||
print("Hoàn tất!", flush=True)
|
||||
|
||||
for part in response.parts:
|
||||
if part.inline_data:
|
||||
|
|
@ -795,6 +802,55 @@ def update_template():
|
|||
print(f"Error updating template: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/delete_template', methods=['POST'])
|
||||
def delete_template():
|
||||
try:
|
||||
template_index = request.form.get('template_index')
|
||||
if template_index is None:
|
||||
return jsonify({'error': 'Template index is required'}), 400
|
||||
|
||||
try:
|
||||
template_index = int(template_index)
|
||||
except ValueError:
|
||||
return jsonify({'error': 'Invalid template index'}), 400
|
||||
|
||||
user_prompts_path = os.path.join(os.path.dirname(__file__), 'user_prompts.json')
|
||||
if not os.path.exists(user_prompts_path):
|
||||
return jsonify({'error': 'User prompts file not found'}), 404
|
||||
|
||||
with open(user_prompts_path, 'r', encoding='utf-8') as f:
|
||||
user_prompts = json.load(f)
|
||||
|
||||
if template_index < 0 or template_index >= len(user_prompts):
|
||||
return jsonify({'error': 'Template not found'}), 404
|
||||
|
||||
template_to_delete = user_prompts[template_index]
|
||||
|
||||
# Delete preview image if it exists and is local
|
||||
preview_path = template_to_delete.get('preview')
|
||||
if preview_path and '/static/preview/' in preview_path:
|
||||
# Extract filename
|
||||
try:
|
||||
filename = preview_path.split('/static/preview/')[1]
|
||||
preview_dir = os.path.join(app.static_folder, 'preview')
|
||||
filepath = os.path.join(preview_dir, filename)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
except Exception as e:
|
||||
print(f"Error deleting preview image: {e}")
|
||||
|
||||
# Remove from list
|
||||
del user_prompts[template_index]
|
||||
|
||||
# Save back
|
||||
with open(user_prompts_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(user_prompts, f, indent=4, ensure_ascii=False)
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/refine_prompt', methods=['POST'])
|
||||
def refine_prompt():
|
||||
data = request.get_json()
|
||||
|
|
|
|||
|
|
@ -6,13 +6,38 @@
|
|||
import { i18n } from './i18n.js';
|
||||
|
||||
export function createTemplateGallery({ container, onSelectTemplate }) {
|
||||
const STORAGE_KEY = 'gemini-app-template-filters';
|
||||
|
||||
// Load saved filters
|
||||
let savedFilters = {};
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY);
|
||||
if (saved) savedFilters = JSON.parse(saved);
|
||||
} catch (e) {
|
||||
console.warn('Failed to load template filters', e);
|
||||
}
|
||||
|
||||
let allTemplates = [];
|
||||
let currentCategory = 'all';
|
||||
let currentMode = 'all';
|
||||
let currentCategory = savedFilters.category || 'all';
|
||||
let currentMode = savedFilters.mode || 'all';
|
||||
let searchQuery = '';
|
||||
let favoriteTemplateKeys = new Set();
|
||||
let favoriteFilterActive = false;
|
||||
let userTemplateFilterActive = false;
|
||||
let favoriteFilterActive = savedFilters.favorites || false;
|
||||
let userTemplateFilterActive = savedFilters.userTemplates || false;
|
||||
|
||||
function persistFilters() {
|
||||
try {
|
||||
const filters = {
|
||||
category: currentCategory,
|
||||
mode: currentMode,
|
||||
favorites: favoriteFilterActive,
|
||||
userTemplates: userTemplateFilterActive
|
||||
};
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(filters));
|
||||
} catch (e) {
|
||||
console.warn('Failed to save template filters', e);
|
||||
}
|
||||
}
|
||||
|
||||
function setFavoriteKeys(keys) {
|
||||
if (Array.isArray(keys)) {
|
||||
|
|
@ -203,6 +228,78 @@ export function createTemplateGallery({ container, onSelectTemplate }) {
|
|||
}
|
||||
});
|
||||
previewActions.appendChild(editBtn);
|
||||
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.className = 'template-edit-btn'; // Reuse same style
|
||||
deleteBtn.style.marginLeft = '4px';
|
||||
deleteBtn.innerHTML = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 6H5H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 6V4C8 3.46957 8.21071 3 8.58579 2.62513C8.96086 2.25026 9.46957 2.03967 10 2.03967H14C14.5304 2.03967 15.0391 2.25026 15.4142 2.62513C15.7893 3 16 3.46957 16 4V6M19 6V20C19 20.5304 18.7893 21.0391 18.4142 21.4142C18.0391 21.7893 17.5304 22 17 22H7C6.46957 22 5.96086 21.7893 5.58579 21.4142C5.21071 21.0391 5 20.5304 5 20V6H19Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
`;
|
||||
deleteBtn.title = 'Delete Template';
|
||||
deleteBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const deleteModal = document.getElementById('delete-confirm-modal');
|
||||
const confirmBtn = document.getElementById('confirm-delete-btn');
|
||||
const cancelBtn = document.getElementById('cancel-delete-btn');
|
||||
const closeBtn = document.getElementById('close-delete-modal');
|
||||
|
||||
if (!deleteModal || !confirmBtn) {
|
||||
console.error('Delete modal elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
deleteModal.classList.add('hidden');
|
||||
confirmBtn.replaceWith(confirmBtn.cloneNode(true)); // Remove listeners
|
||||
cancelBtn?.replaceWith(cancelBtn.cloneNode(true));
|
||||
closeBtn?.replaceWith(closeBtn.cloneNode(true));
|
||||
};
|
||||
|
||||
deleteModal.classList.remove('hidden');
|
||||
|
||||
// Setup new listeners
|
||||
const newConfirmBtn = document.getElementById('confirm-delete-btn');
|
||||
const newCancelBtn = document.getElementById('cancel-delete-btn');
|
||||
const newCloseBtn = document.getElementById('close-delete-modal');
|
||||
|
||||
newConfirmBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('template_index', template.userTemplateIndex);
|
||||
|
||||
const response = await fetch('/delete_template', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
closeModal();
|
||||
load();
|
||||
} else {
|
||||
alert('Failed to delete: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting template:', error);
|
||||
alert('Error deleting template');
|
||||
}
|
||||
});
|
||||
|
||||
newCancelBtn?.addEventListener('click', closeModal);
|
||||
newCloseBtn?.addEventListener('click', closeModal);
|
||||
|
||||
// Close on click outside
|
||||
deleteModal.onclick = (event) => {
|
||||
if (event.target === deleteModal) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
});
|
||||
previewActions.appendChild(deleteBtn);
|
||||
}
|
||||
|
||||
preview.appendChild(previewActions);
|
||||
|
|
@ -307,6 +404,7 @@ export function createTemplateGallery({ container, onSelectTemplate }) {
|
|||
modeSelect.value = currentMode;
|
||||
modeSelect.addEventListener('change', (e) => {
|
||||
currentMode = e.target.value;
|
||||
persistFilters();
|
||||
render();
|
||||
});
|
||||
|
||||
|
|
@ -325,6 +423,7 @@ export function createTemplateGallery({ container, onSelectTemplate }) {
|
|||
categorySelect.value = currentCategory;
|
||||
categorySelect.addEventListener('change', (e) => {
|
||||
currentCategory = e.target.value;
|
||||
persistFilters();
|
||||
render();
|
||||
});
|
||||
|
||||
|
|
@ -343,6 +442,7 @@ export function createTemplateGallery({ container, onSelectTemplate }) {
|
|||
`;
|
||||
favoritesToggle.addEventListener('click', () => {
|
||||
favoriteFilterActive = !favoriteFilterActive;
|
||||
persistFilters();
|
||||
render();
|
||||
});
|
||||
const filterRow = document.createElement('div');
|
||||
|
|
@ -365,6 +465,7 @@ export function createTemplateGallery({ container, onSelectTemplate }) {
|
|||
`;
|
||||
userToggle.addEventListener('click', () => {
|
||||
userTemplateFilterActive = !userTemplateFilterActive;
|
||||
persistFilters();
|
||||
render();
|
||||
});
|
||||
filterRow.appendChild(userToggle);
|
||||
|
|
|
|||
|
|
@ -1080,6 +1080,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
loadTemplateGallery();
|
||||
initializeSidebarResizer(sidebar, resizeHandle);
|
||||
|
||||
// Restore last image if available
|
||||
try {
|
||||
const lastImage = localStorage.getItem('gemini-app-last-image');
|
||||
if (lastImage) {
|
||||
displayImage(lastImage);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to restore last image', e);
|
||||
}
|
||||
|
||||
// Setup canvas language toggle
|
||||
const canvasLangInput = document.getElementById('canvas-lang-input');
|
||||
if (canvasLangInput) {
|
||||
|
|
@ -1152,6 +1162,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
|
||||
hasGeneratedImage = true; // Mark that we have an image
|
||||
setViewState('result');
|
||||
|
||||
// Persist image URL
|
||||
try {
|
||||
localStorage.setItem('gemini-app-last-image', imageUrl);
|
||||
} catch (e) {
|
||||
console.warn('Failed to save last image URL', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCanvasDropUrl(imageUrl) {
|
||||
|
|
|
|||
|
|
@ -47,10 +47,14 @@
|
|||
<a href="https://chatgpt.com/g/g-6923d39c8efc8191be0bc3089bebc441-banana-prompt-guide"
|
||||
target="_blank" rel="noopener noreferrer" class="prompt-action-btn"
|
||||
title="Prompt Guide">
|
||||
<svg fill="currentColor" height="16px" width="16px" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px"
|
||||
viewBox="0 0 32 32" xml:space="preserve" fill="currentColor">
|
||||
<path
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z" />
|
||||
d="M21,24c0,0.552-0.447,1-1,1h-8c-0.553,0-1-0.448-1-1s0.447-1,1-1h8C20.553,23,21,23.448,21,24z
|
||||
M20,26h-8c-0.553,0-1,0.448-1,1s0.447,1,1,1h8c0.553,0,1-0.448,1-1S20.553,26,20,26z M15,29v1c0,0.552,0.448,1,1,1s1-0.448,1-1v-1
|
||||
H15z M26,11c0,5-5,8-5,10c0,0.552-0.448,1-1,1h-8c-0.552,0-1-0.448-1-1c0-2-5-5-5-10C6,5.477,10.477,1,16,1S26,5.477,26,11z M17,4
|
||||
c0-0.552-0.447-1-1-1c-4.411,0-8,3.589-8,8c0,0.552,0.447,1,1,1s1-0.448,1-1c0-3.309,2.691-6,6-6C16.553,5,17,4.552,17,4z" />
|
||||
</svg>
|
||||
<span>Prompt Guide</span>
|
||||
</a>
|
||||
|
|
@ -370,6 +374,24 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="delete-confirm-modal" class="popup-overlay hidden">
|
||||
<div class="popup-card" style="max-width: 400px;">
|
||||
<header class="popup-header">
|
||||
<h2>Xác nhận xoá</h2>
|
||||
<button id="close-delete-modal" type="button" class="popup-close" aria-label="Close">×</button>
|
||||
</header>
|
||||
<div class="popup-body">
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1.5rem;">Bạn có chắc chắn muốn xoá template này
|
||||
không?</p>
|
||||
<div class="controls-footer" style="justify-content: flex-end; gap: 0.5rem;">
|
||||
<button id="cancel-delete-btn" class="action-btn"
|
||||
style="background: rgba(255, 255, 255, 0.1); color: var(--text-primary);">Huỷ</button>
|
||||
<button id="confirm-delete-btn" class="action-btn"
|
||||
style="background: var(--danger-color); color: white;">Xoá</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="popup-overlay" class="popup-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="popup-title">
|
||||
<div class="popup-card">
|
||||
<header class="popup-header">
|
||||
|
|
|
|||
Loading…
Reference in a new issue