增加数字后缀,优化撤销逻辑
This commit is contained in:
+185
-22
@@ -75,6 +75,53 @@ function currentEditEl(): HTMLInputElement | null {
|
|||||||
if (!raw) return null;
|
if (!raw) return null;
|
||||||
return Array.isArray(raw) ? (raw[0] ?? null) : raw;
|
return Array.isArray(raw) ? (raw[0] ?? null) : raw;
|
||||||
}
|
}
|
||||||
|
const priorityStyle = ref<'{}' | '()' | '[]' | '<>' | 'suffix'>('{}');
|
||||||
|
const priorityStep = ref(1);
|
||||||
|
function splitTokensLocal(txt: string): string[] {
|
||||||
|
return txt.split(/[,,]/).map(s => s.trim()).filter(s => s.length > 0);
|
||||||
|
}
|
||||||
|
function normalizeToken(t: string): string { return t.trim(); }
|
||||||
|
function normalizePromptLocal(txt: string): string { return splitTokensLocal(txt).join(', '); }
|
||||||
|
function applyFullPrompt(newText: string) {
|
||||||
|
const el = inputEl.value;
|
||||||
|
if (!el) { text.value = newText; return; }
|
||||||
|
el.focus();
|
||||||
|
applyTextReplacement(el, 0, text.value.length, newText);
|
||||||
|
}
|
||||||
|
function getTokenWeight(token: string): number {
|
||||||
|
const { core } = store.parseTokenWrappers(token);
|
||||||
|
const idx = core.lastIndexOf(':');
|
||||||
|
if (idx > -1) {
|
||||||
|
const w = parseFloat(core.slice(idx + 1).trim());
|
||||||
|
return isNaN(w) ? 1 : w;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
function hasWeightSuffix(token: string): boolean {
|
||||||
|
const { core } = store.parseTokenWrappers(token);
|
||||||
|
return /:\s*\d+(?:\.\d+)?$/.test(core);
|
||||||
|
}
|
||||||
|
function roundToDecimals(v: number, decimals: number): number {
|
||||||
|
const m = Math.pow(10, decimals);
|
||||||
|
return Math.round(v * m) / m;
|
||||||
|
}
|
||||||
|
function adjustWeight(core: string, delta: number): string {
|
||||||
|
const idx = core.lastIndexOf(':');
|
||||||
|
let base = core;
|
||||||
|
let w: number | null = null;
|
||||||
|
if (idx > -1) {
|
||||||
|
const num = parseFloat(core.slice(idx + 1).trim());
|
||||||
|
if (!isNaN(num)) { base = core.slice(0, idx); w = num; }
|
||||||
|
}
|
||||||
|
const stepStr = String(priorityStep.value);
|
||||||
|
const decimals = stepStr.includes('.') ? stepStr.split('.')[1]!.length : 0;
|
||||||
|
const cur = w == null ? 1.0 : w;
|
||||||
|
let nw = cur + delta;
|
||||||
|
if (delta < 0 && nw <= 1.0) return base;
|
||||||
|
nw = Math.min(2.0, Math.max(0.1, nw));
|
||||||
|
nw = roundToDecimals(nw, decimals);
|
||||||
|
return base + ':' + nw;
|
||||||
|
}
|
||||||
const text = ref('');
|
const text = ref('');
|
||||||
|
|
||||||
watch(text, (val) => {
|
watch(text, (val) => {
|
||||||
@@ -196,26 +243,60 @@ async function copyLeft() {
|
|||||||
showNotification('复制失败,请手动复制', 'error');
|
showNotification('复制失败,请手动复制', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function replaceCnComma() { store.replaceChineseComma(); text.value = store.promptText; }
|
function replaceCnComma() { applyFullPrompt(text.value.replace(/,/g, ',')); }
|
||||||
function formatPrompt() { store.formatPrompt(); text.value = store.promptText; }
|
function formatPrompt() { applyFullPrompt(normalizePromptLocal(text.value)); }
|
||||||
|
|
||||||
// 新增功能方法
|
// 新增功能方法
|
||||||
function toggleUnderscoreSpace() {
|
function toggleUnderscoreSpace() {
|
||||||
store.toggleUnderscoreSpace();
|
const tokens = splitTokensLocal(text.value);
|
||||||
text.value = store.promptText;
|
const newTokens = tokens.map(token => {
|
||||||
|
const { core, wrappers } = store.parseTokenWrappers(token);
|
||||||
|
let newCore;
|
||||||
|
if (core.includes('_')) {
|
||||||
|
newCore = core.replace(/_/g, ' ');
|
||||||
|
} else if (core.includes(' ')) {
|
||||||
|
newCore = core.replace(/ /g, '_');
|
||||||
|
} else {
|
||||||
|
newCore = core;
|
||||||
|
}
|
||||||
|
return store.wrapToken(newCore, wrappers);
|
||||||
|
});
|
||||||
|
applyFullPrompt(newTokens.join(', '));
|
||||||
showNotification('已切换下划线/空格格式', 'success');
|
showNotification('已切换下划线/空格格式', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWrapperToToken(index: number) {
|
function addWrapperToToken(index: number) {
|
||||||
store.addWrapperToToken(index, '{}');
|
const tokens = splitTokensLocal(text.value);
|
||||||
text.value = store.promptText;
|
if (index < 0 || index >= tokens.length) return;
|
||||||
showNotification('已添加包裹层 {}', 'success');
|
const token = tokens[index]!;
|
||||||
|
const parsed = store.parseTokenWrappers(token);
|
||||||
|
const core = parsed?.core ?? token;
|
||||||
|
const wrappers = parsed?.wrappers ?? [];
|
||||||
|
if (priorityStyle.value === 'suffix') {
|
||||||
|
const newCore = adjustWeight(core, +priorityStep.value);
|
||||||
|
tokens[index] = store.wrapToken(newCore, wrappers);
|
||||||
|
} else {
|
||||||
|
const newWrappers = [...wrappers, priorityStyle.value];
|
||||||
|
tokens[index] = store.wrapToken(core, newWrappers);
|
||||||
|
}
|
||||||
|
applyFullPrompt(tokens.join(', '));
|
||||||
|
showNotification('已添加优先级', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeWrapperFromToken(index: number) {
|
function removeWrapperFromToken(index: number) {
|
||||||
store.removeWrapperFromToken(index);
|
const tokens = splitTokensLocal(text.value);
|
||||||
text.value = store.promptText;
|
if (index < 0 || index >= tokens.length) return;
|
||||||
showNotification('已移除外层包裹', 'success');
|
const token = tokens[index]!;
|
||||||
|
const { core, wrappers } = store.parseTokenWrappers(token);
|
||||||
|
if (priorityStyle.value === 'suffix') {
|
||||||
|
const newCore = adjustWeight(core, -priorityStep.value);
|
||||||
|
tokens[index] = store.wrapToken(newCore, wrappers);
|
||||||
|
} else if (wrappers.length > 0) {
|
||||||
|
const newWrappers = wrappers.slice(0, -1);
|
||||||
|
tokens[index] = store.wrapToken(core, newWrappers);
|
||||||
|
}
|
||||||
|
applyFullPrompt(tokens.join(', '));
|
||||||
|
showNotification('已调整优先级', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTokenWrapperInfo(token: string) {
|
function getTokenWrapperInfo(token: string) {
|
||||||
@@ -271,9 +352,12 @@ function handlePointerUp(e: PointerEvent) {
|
|||||||
} else {
|
} else {
|
||||||
to = j + (from > j ? 1 : 0);
|
to = j + (from > j ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
const list = splitTokensLocal(text.value);
|
||||||
if (to < 0) to = 0;
|
if (to < 0) to = 0;
|
||||||
if (to >= tokens.value.length) to = tokens.value.length - 1;
|
if (to >= list.length) to = list.length - 1;
|
||||||
store.reorderTokens(from, to);
|
const [item] = list.splice(from, 1);
|
||||||
|
if (item != null) list.splice(to, 0, item);
|
||||||
|
applyFullPrompt(list.join(', '));
|
||||||
showNotification('已重新排序', 'success');
|
showNotification('已重新排序', 'success');
|
||||||
}
|
}
|
||||||
cleanupDrag();
|
cleanupDrag();
|
||||||
@@ -351,7 +435,12 @@ function beginEdit(i: number) {
|
|||||||
}
|
}
|
||||||
function commitEdit() {
|
function commitEdit() {
|
||||||
if (editingIndex.value == null) return;
|
if (editingIndex.value == null) return;
|
||||||
store.updateToken(editingIndex.value, editingValue.value);
|
const tokens = splitTokensLocal(text.value);
|
||||||
|
const i = editingIndex.value!;
|
||||||
|
if (i >= 0 && i < tokens.length) {
|
||||||
|
tokens[i] = normalizeToken(editingValue.value);
|
||||||
|
applyFullPrompt(tokens.join(', '));
|
||||||
|
}
|
||||||
editingIndex.value = null;
|
editingIndex.value = null;
|
||||||
}
|
}
|
||||||
function cancelEdit() { editingIndex.value = null; }
|
function cancelEdit() { editingIndex.value = null; }
|
||||||
@@ -368,8 +457,17 @@ function commitAddMap() {
|
|||||||
addingMapIndex.value = null; addingMapValue.value = '';
|
addingMapIndex.value = null; addingMapValue.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeToken(i: number) { store.removeToken(i); }
|
function removeToken(i: number) {
|
||||||
function addTokenAfter(i: number) { store.addTokenAfter(i, 'new_token'); }
|
const tokens = splitTokensLocal(text.value);
|
||||||
|
if (i < 0 || i >= tokens.length) return;
|
||||||
|
tokens.splice(i, 1);
|
||||||
|
applyFullPrompt(tokens.join(', '));
|
||||||
|
}
|
||||||
|
function addTokenAfter(i: number) {
|
||||||
|
const tokens = splitTokensLocal(text.value);
|
||||||
|
tokens.splice(i + 1, 0, normalizeToken('new_token'));
|
||||||
|
applyFullPrompt(tokens.join(', '));
|
||||||
|
}
|
||||||
|
|
||||||
function savePreset() {
|
function savePreset() {
|
||||||
if (!presetName.value.trim()) {
|
if (!presetName.value.trim()) {
|
||||||
@@ -466,7 +564,18 @@ function applyEditSuggestion(s: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function displayTrans(key: string): string {
|
function displayTrans(key: string): string {
|
||||||
return store.getTranslation(key, selectedLang.value) ?? key;
|
const { core, wrappers } = store.parseTokenWrappers(key);
|
||||||
|
const m = core.match(/:(\d+(?:\.\d+)?)$/);
|
||||||
|
const base = m ? core.slice(0, core.lastIndexOf(':')) : core;
|
||||||
|
const suffix = m ? ':' + m[1]! : '';
|
||||||
|
const tag = store.getTagByKey(base);
|
||||||
|
const translatedCore = tag?.translation?.[selectedLang.value] ?? tag?.key ?? base;
|
||||||
|
return store.wrapToken(translatedCore + suffix, wrappers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRemoveDisabled(token: string): boolean {
|
||||||
|
const info = getTokenWrapperInfo(token);
|
||||||
|
return info.wrapperCount === 0 && !hasWeightSuffix(token);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -568,6 +677,26 @@ function displayTrans(key: string): string {
|
|||||||
</svg>
|
</svg>
|
||||||
切换 _/空格
|
切换 _/空格
|
||||||
</button>
|
</button>
|
||||||
|
<div class="pe-priority-group">
|
||||||
|
<label class="pe-priority-label">优先级样式</label>
|
||||||
|
<select class="pe-priority-select" v-model="priorityStyle" title="选择新增优先级的样式">
|
||||||
|
<option value="{}">{}</option>
|
||||||
|
<option value="()">()</option>
|
||||||
|
<option value="[]">[]</option>
|
||||||
|
<option value="<>"><></option>
|
||||||
|
<option value="suffix">后缀数字</option>
|
||||||
|
</select>
|
||||||
|
<label class="pe-priority-label">后缀数字间隔</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="pe-priority-step"
|
||||||
|
v-model.number="priorityStep"
|
||||||
|
title="设置增减间隔"
|
||||||
|
min="0.01"
|
||||||
|
step="0.01"
|
||||||
|
placeholder="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="pe-suggest" v-if="suggestions.length">
|
<ul class="pe-suggest" v-if="suggestions.length">
|
||||||
<li
|
<li
|
||||||
@@ -648,7 +777,7 @@ function displayTrans(key: string): string {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pe-token-controls-compact">
|
<div class="pe-token-controls-compact">
|
||||||
<button @click="addWrapperToToken(i)" class="pe-add-wrapper-btn" title="添加包裹层 {}">
|
<button @click="addWrapperToToken(i)" class="pe-add-wrapper-btn" :title="`添加优先级(样式:${priorityStyle})`">
|
||||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
||||||
<line x1="12" y1="8" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
|
<line x1="12" y1="8" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
|
||||||
@@ -658,8 +787,8 @@ function displayTrans(key: string): string {
|
|||||||
<button
|
<button
|
||||||
@click="removeWrapperFromToken(i)"
|
@click="removeWrapperFromToken(i)"
|
||||||
class="pe-remove-wrapper-btn"
|
class="pe-remove-wrapper-btn"
|
||||||
title="移除包裹层"
|
title="移除优先级"
|
||||||
:disabled="getTokenWrapperInfo(k).wrapperCount === 0"
|
:disabled="isRemoveDisabled(k)"
|
||||||
>
|
>
|
||||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
||||||
@@ -704,7 +833,7 @@ function displayTrans(key: string): string {
|
|||||||
<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2"/>
|
<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button @click="addWrapperToToken(i)" class="pe-add-wrapper-detail-btn" title="添加包裹层 {}">
|
<button @click="addWrapperToToken(i)" class="pe-add-wrapper-detail-btn" :title="`添加优先级(样式:${priorityStyle})`">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
||||||
<line x1="12" y1="8" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
|
<line x1="12" y1="8" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
|
||||||
@@ -714,8 +843,8 @@ function displayTrans(key: string): string {
|
|||||||
<button
|
<button
|
||||||
@click="removeWrapperFromToken(i)"
|
@click="removeWrapperFromToken(i)"
|
||||||
class="pe-remove-wrapper-detail-btn"
|
class="pe-remove-wrapper-detail-btn"
|
||||||
title="移除包裹层"
|
title="移除优先级"
|
||||||
:disabled="getTokenWrapperInfo(k).wrapperCount === 0"
|
:disabled="isRemoveDisabled(k)"
|
||||||
>
|
>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
||||||
@@ -1147,6 +1276,40 @@ function displayTrans(key: string): string {
|
|||||||
border-color: var(--color-border-hover);
|
border-color: var(--color-border-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pe-priority-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pe-priority-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pe-priority-select, .pe-priority-step {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background-color: var(--color-bg-primary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pe-priority-select:hover, .pe-priority-step:hover {
|
||||||
|
border-color: var(--color-border-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pe-priority-select:focus, .pe-priority-step:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
box-shadow: 0 0 0 3px var(--color-accent-light);
|
||||||
|
}
|
||||||
|
|
||||||
.pe-suggest {
|
.pe-suggest {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 1rem 0 0;
|
margin: 1rem 0 0;
|
||||||
|
|||||||
@@ -99,10 +99,9 @@ export const usePromptStore = defineStore('promptStore', {
|
|||||||
this.dataset = deepClone(baseline!);
|
this.dataset = deepClone(baseline!);
|
||||||
this.promptText = '1girl, solo, long hair, blue eyes, smile, looking_at_viewer, upper_body, outdoors, sunset';
|
this.promptText = '1girl, solo, long hair, blue eyes, smile, looking_at_viewer, upper_body, outdoors, sunset';
|
||||||
}
|
}
|
||||||
// 若无恢复语言,则按数据集进行推断
|
// 若无恢复语言,则默认使用 zh_CN
|
||||||
if (!this.selectedLang) {
|
if (!this.selectedLang) {
|
||||||
const guessLang: LangCode = (this.dataset.languages.includes('zh_CN') ? 'zh_CN' : 'en') as LangCode;
|
this.selectedLang = 'zh_CN' as LangCode;
|
||||||
this.selectedLang = guessLang;
|
|
||||||
}
|
}
|
||||||
// 初始化扩展预设管理
|
// 初始化扩展预设管理
|
||||||
this.initializeExtendedPresets();
|
this.initializeExtendedPresets();
|
||||||
|
|||||||
Reference in New Issue
Block a user