update setting

This commit is contained in:
phamhungd 2025-11-28 16:04:26 +07:00
parent ae2d5071de
commit 5e0b3d5dba
6 changed files with 230 additions and 8 deletions

BIN
.DS_Store vendored

Binary file not shown.

7
app.py
View file

@ -554,8 +554,11 @@ def generate_image():
image_url = url_for('static', filename=rel_path)
metadata = {
'prompt': api_prompt, # Store the processed prompt
'note': '', # Note is already merged into prompt
# Keep the exact user input before placeholder expansion
'prompt': prompt,
'note': note,
# Also store the expanded prompt for reference
'processed_prompt': api_prompt,
'aspect_ratio': aspect_ratio or 'Auto',
'resolution': resolution,
'reference_images': final_reference_paths,

View file

@ -5,5 +5,7 @@
"gemini-3-pro-image-preview_20251125_41.png",
"gemini-3-pro-image-preview_20251125_37.png",
"gemini-3-pro-image-preview_20251125_26.png",
"gemini-3-pro-image-preview_20251125_24.png"
"gemini-3-pro-image-preview_20251125_24.png",
"generated/gemini-3-pro-image-preview_20251124_10.png",
"generated/gemini-3-pro-image-preview_20251124_9.png"
]

View file

@ -89,6 +89,7 @@ document.addEventListener('DOMContentLoaded', () => {
const apiKeyToggleBtn = document.getElementById('toggle-api-key-visibility');
const apiKeyEyeIcon = apiKeyToggleBtn?.querySelector('.icon-eye');
const apiKeyEyeOffIcon = apiKeyToggleBtn?.querySelector('.icon-eye-off');
const bodyFontSelect = document.getElementById('body-font');
const placeholderState = document.getElementById('placeholder-state');
const loadingState = document.getElementById('loading-state');
@ -150,6 +151,10 @@ document.addEventListener('DOMContentLoaded', () => {
toggleResolutionVisibility();
}
currentTheme = settings.theme || DEFAULT_THEME;
applyBodyFont(settings.bodyFont || DEFAULT_BODY_FONT);
if (bodyFontSelect && settings.bodyFont) {
bodyFontSelect.value = settings.bodyFont;
}
return settings;
}
} catch (e) {
@ -173,6 +178,7 @@ document.addEventListener('DOMContentLoaded', () => {
model: apiModelSelect ? apiModelSelect.value : 'gemini-3-pro-image-preview',
referenceImages,
theme: currentTheme || DEFAULT_THEME,
bodyFont: bodyFontSelect ? bodyFontSelect.value : DEFAULT_BODY_FONT,
};
try {
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings));
@ -216,6 +222,7 @@ document.addEventListener('DOMContentLoaded', () => {
];
const DEFAULT_THEME = 'theme-sdvn';
const DEFAULT_BODY_FONT = 'Be Vietnam Pro';
const themeOptionsData = [
{ id: 'theme-sdvn', name: 'SDVN', gradient: 'linear-gradient(to bottom, #5858e6, #151523)' },
@ -306,6 +313,65 @@ document.addEventListener('DOMContentLoaded', () => {
noteHighlight.scrollLeft = promptNoteInput.scrollLeft;
}
function triggerInputUpdate(targetInput) {
if (!targetInput) return;
targetInput.dispatchEvent(new Event('input', { bubbles: true }));
}
function getFieldInput(target) {
if (target === 'note') return promptNoteInput;
return promptInput;
}
async function copyToClipboard(text) {
if (!navigator.clipboard?.writeText) {
const temp = document.createElement('textarea');
temp.value = text;
document.body.appendChild(temp);
temp.select();
document.execCommand('copy');
temp.remove();
return;
}
await navigator.clipboard.writeText(text);
}
async function pasteIntoInput(targetInput) {
if (!targetInput) return;
if (!navigator.clipboard?.readText) {
alert('Clipboard paste không được hỗ trợ trong trình duyệt này.');
return;
}
const text = await navigator.clipboard.readText();
if (!text && text !== '') return;
const start = targetInput.selectionStart ?? targetInput.value.length;
const end = targetInput.selectionEnd ?? start;
targetInput.value = targetInput.value.slice(0, start) + text + targetInput.value.slice(end);
const cursor = start + text.length;
requestAnimationFrame(() => {
targetInput.setSelectionRange(cursor, cursor);
targetInput.focus();
});
triggerInputUpdate(targetInput);
}
async function handleFieldAction(action, target) {
const targetInput = getFieldInput(target);
if (!targetInput) return;
if (action === 'copy') {
await copyToClipboard(targetInput.value);
return;
}
if (action === 'paste') {
await pasteIntoInput(targetInput);
return;
}
if (action === 'clear') {
targetInput.value = '';
triggerInputUpdate(targetInput);
}
}
function updateThemeSelectionUi() {
if (!themeOptionsContainer) return;
themeOptionsContainer.querySelectorAll('.theme-option').forEach(btn => {
@ -334,6 +400,19 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
function applyBodyFont(fontName) {
const fontMap = {
'Be Vietnam Pro': "'Be Vietnam Pro', sans-serif",
'Playwrite AU SA': "'Playwrite AU SA', cursive",
'JetBrains Mono': "'JetBrains Mono', monospace",
};
const cssFont = fontMap[fontName] || fontMap[DEFAULT_BODY_FONT];
document.body.style.fontFamily = cssFont;
if (bodyFontSelect && fontName) {
bodyFontSelect.value = fontName;
}
}
function renderThemeOptions(initialTheme) {
if (!themeOptionsContainer) return;
themeOptionsContainer.innerHTML = '';
@ -430,6 +509,7 @@ document.addEventListener('DOMContentLoaded', () => {
applyThemeClass(currentTheme || DEFAULT_THEME);
slotManager.initialize(savedSettings.referenceImages || []);
refreshPromptHighlight();
applyBodyFont(savedSettings.bodyFont || DEFAULT_BODY_FONT);
apiKeyInput.addEventListener('input', persistSettings);
let isApiKeyVisible = false;
@ -472,8 +552,28 @@ document.addEventListener('DOMContentLoaded', () => {
});
aspectRatioInput.addEventListener('change', persistSettings);
resolutionInput.addEventListener('change', persistSettings);
if (bodyFontSelect) {
bodyFontSelect.addEventListener('change', () => {
applyBodyFont(bodyFontSelect.value);
persistSettings();
});
}
window.addEventListener('beforeunload', persistSettings);
document.querySelectorAll('.field-action-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const action = btn.dataset.action;
const target = btn.closest('.field-action-buttons')?.dataset.target;
if (!action || !target) return;
try {
await handleFieldAction(action, target);
} catch (err) {
console.warn('Field action failed', err);
alert('Không thể thực hiện thao tác clipboard. Vui lòng thử lại.');
}
});
});
const queueCounter = document.getElementById('queue-counter');
const queueCountText = document.getElementById('queue-count-text');
@ -1770,6 +1870,7 @@ document.addEventListener('DOMContentLoaded', () => {
} else {
promptNoteInput.value = '';
}
refreshNoteHighlight();
if (metadata.aspect_ratio) aspectRatioInput.value = metadata.aspect_ratio;
if (metadata.resolution) resolutionInput.value = metadata.resolution;

View file

@ -438,7 +438,7 @@ body.theme-amin { --bd-bg: linear-gradient(to right, #4A00E0, #8E2DE2); }
.prompt-highlight {
position: absolute;
inset: 0;
padding: 0.75rem;
padding: 0.75rem 0.75rem 2.25rem 0.75rem;
overflow: auto;
pointer-events: none;
white-space: pre-wrap;
@ -472,7 +472,7 @@ body.theme-amin { --bd-bg: linear-gradient(to right, #4A00E0, #8E2DE2); }
background: transparent;
color: transparent;
caret-color: var(--accent-color);
padding: 0.75rem;
padding: 0.75rem 0.75rem 2.25rem 0.75rem;
font-family: inherit;
font-size: 0.875rem;
line-height: 1.4;
@ -493,7 +493,7 @@ body.theme-amin { --bd-bg: linear-gradient(to right, #4A00E0, #8E2DE2); }
.note-highlight {
position: absolute;
inset: 0;
padding: 0.75rem;
padding: 0.75rem 0.75rem 2.25rem 0.75rem;
overflow: auto;
pointer-events: none;
white-space: pre-wrap;
@ -526,7 +526,7 @@ body.theme-amin { --bd-bg: linear-gradient(to right, #4A00E0, #8E2DE2); }
background: transparent;
color: transparent;
caret-color: var(--accent-color);
padding: 0.75rem;
padding: 0.75rem 0.75rem 2.25rem 0.75rem;
font-family: inherit;
font-size: 0.875rem;
line-height: 1.4;
@ -536,6 +536,41 @@ body.theme-amin { --bd-bg: linear-gradient(to right, #4A00E0, #8E2DE2); }
height: 100%;
}
.field-action-buttons {
position: absolute;
right: 0.35rem;
bottom: 0.35rem;
display: inline-flex;
gap: 0.25rem;
padding: 0.1rem;
border-radius: 0.5rem;
z-index: 3;
background: transparent;
}
.field-action-btn {
border: none;
background: transparent;
color: var(--text-secondary);
width: 24px;
height: 24px;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.field-action-btn:hover {
background: rgba(255, 255, 255, 0.06);
color: var(--accent-color);
}
.field-action-btn:active {
transform: translateY(1px);
}
.theme-option-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));

View file

@ -10,7 +10,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;600;700&family=Playwrite+AU+SA&display=swap"
href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;600;700&family=Playwrite+AU+SA&family=JetBrains+Mono:wght@400;500;600;700&display=swap"
rel="stylesheet">
</head>
@ -43,6 +43,41 @@
<div class="prompt-wrapper prompt-highlighting">
<div id="prompt-highlight" class="prompt-highlight" aria-hidden="true"></div>
<textarea id="prompt" placeholder="Nhập prompt miêu tả..." rows="6"></textarea>
<div class="field-action-buttons" data-target="prompt" aria-label="Prompt actions">
<button type="button" class="field-action-btn" data-action="copy" title="Copy prompt"
aria-label="Copy prompt">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2.5" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
</button>
<button type="button" class="field-action-btn" data-action="paste" title="Paste"
aria-label="Paste vào prompt">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round">
<path d="M8 4h8" />
<path d="M9 2h6a2 2 0 0 1 2 2v1H7V4a2 2 0 0 1 2-2z" />
<rect x="5" y="5" width="14" height="16" rx="2" />
<path d="M9 12h3" />
<path d="M9 16h6" />
</svg>
</button>
<button type="button" class="field-action-btn" data-action="clear" title="Clear prompt"
aria-label="Xoá prompt">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round">
<path d="M3 6h18" />
<path d="M19 6v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
<path d="M10 11v6" />
<path d="M14 11v6" />
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
</svg>
</button>
</div>
</div>
<div class="prompt-actions">
<a href="https://chatgpt.com/g/g-6923d39c8efc8191be0bc3089bebc441-banana-prompt-guide"
@ -94,6 +129,41 @@
<div id="note-highlight" class="note-highlight" aria-hidden="true"></div>
<textarea id="prompt-note" placeholder="Thêm chi tiết hoặc điều chỉnh cho prompt..."
rows="2"></textarea>
<div class="field-action-buttons" data-target="note" aria-label="Note actions">
<button type="button" class="field-action-btn" data-action="copy" title="Copy note"
aria-label="Copy note">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2.5" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
</button>
<button type="button" class="field-action-btn" data-action="paste" title="Paste"
aria-label="Paste vào note">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round">
<path d="M8 4h8" />
<path d="M9 2h6a2 2 0 0 1 2 2v1H7V4a2 2 0 0 1 2-2z" />
<rect x="5" y="5" width="14" height="16" rx="2" />
<path d="M9 12h3" />
<path d="M9 16h6" />
</svg>
</button>
<button type="button" class="field-action-btn" data-action="clear" title="Clear note"
aria-label="Xoá note">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none"
stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
stroke-linejoin="round">
<path d="M3 6h18" />
<path d="M19 6v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
<path d="M10 11v6" />
<path d="M14 11v6" />
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
</svg>
</button>
</div>
</div>
</div>
@ -429,6 +499,17 @@
</select>
</div>
</div>
<div class="input-group api-settings-input-group">
<label for="body-font">Body Font</label>
<div class="select-wrapper">
<select id="body-font"
style="width: 100%; padding: 0.75rem; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 0.5rem; color: var(--text-primary); font-size: 0.9rem;">
<option value="Be Vietnam Pro">Be Vietnam Pro (Default)</option>
<option value="Playwrite AU SA">Playwrite AU SA</option>
<option value="JetBrains Mono">JetBrains Mono</option>
</select>
</div>
</div>
<div class="input-group api-settings-input-group">
<label for="theme-options">Theme</label>
<div id="theme-options" class="theme-option-grid" role="listbox" aria-label="Chọn theme"></div>