update setting
This commit is contained in:
parent
ae2d5071de
commit
5e0b3d5dba
6 changed files with 230 additions and 8 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
7
app.py
7
app.py
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
101
static/script.js
101
static/script.js
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue