main重复定义修复

This commit is contained in:
2025-12-06 11:48:21 +08:00
parent 83032d3f6b
commit 9df5794383
+101 -148
View File
@@ -48,10 +48,10 @@ const translationTokens = computed(() => {
} }
return unmappedTokens.value; return unmappedTokens.value;
}); });
const notification = ref<{ message: string; type: 'success' | 'error' | 'info'; show: boolean }>({ const notification = ref<{ message: string; type: 'success' | 'error' | 'info'; show: boolean }>({
message: '', message: '',
type: 'info', type: 'info',
show: false show: false
}); });
function showNotification(message: string, type: 'success' | 'error' | 'info' = 'info') { function showNotification(message: string, type: 'success' | 'error' | 'info' = 'info') {
@@ -94,7 +94,7 @@ const tokens = computed(() => store.tokens);
const folderTree = computed(() => { const folderTree = computed(() => {
const folders = store.presetFolders || []; const folders = store.presetFolders || [];
const rootFolders = folders.filter(f => !f.parentId); const rootFolders = folders.filter(f => !f.parentId);
function buildTree(parentFolders: PresetFolder[]): any[] { function buildTree(parentFolders: PresetFolder[]): any[] {
return parentFolders.map(folder => ({ return parentFolders.map(folder => ({
...folder, ...folder,
@@ -102,7 +102,7 @@ const folderTree = computed(() => {
presetCount: (store.extendedPresets || []).filter(p => p.folderId === folder.id).length presetCount: (store.extendedPresets || []).filter(p => p.folderId === folder.id).length
})); }));
} }
return buildTree(rootFolders); return buildTree(rootFolders);
}); });
@@ -139,8 +139,8 @@ function splitTokensLocal(txt: string): string[] {
return splitTokens(txt); return splitTokens(txt);
} }
function normalizeToken(t: string): string { return t.trim().replace(/\s+/g, ' '); } function normalizeToken(t: string): string { return t.trim().replace(/\s+/g, ' '); }
function normalizePromptLocal(txt: string): string { function normalizePromptLocal(txt: string): string {
return splitTokens(txt).map(t => t.replace(/\s+/g, ' ')).join(', '); return splitTokens(txt).map(t => t.replace(/\s+/g, ' ')).join(', ');
} }
function applyFullPrompt(newText: string) { function applyFullPrompt(newText: string) {
const el = editorInputRef.value?.inputEl; const el = editorInputRef.value?.inputEl;
@@ -177,7 +177,7 @@ function adjustWeight(core: string, delta: number): string {
const decimals = stepStr.includes('.') ? stepStr.split('.')[1]!.length : 0; const decimals = stepStr.includes('.') ? stepStr.split('.')[1]!.length : 0;
const cur = w == null ? 1.0 : w; const cur = w == null ? 1.0 : w;
let nw = cur + delta; let nw = cur + delta;
nw = roundToDecimals(nw, decimals); nw = roundToDecimals(nw, decimals);
// If weight is 1, return base without suffix // If weight is 1, return base without suffix
@@ -236,7 +236,7 @@ function applyTextReplacement(
} }
const ok = (document as any).execCommand && (document as any).execCommand('insertText', false, text); const ok = (document as any).execCommand && (document as any).execCommand('insertText', false, text);
if (ok) return; if (ok) return;
} catch {} } catch { }
try { try {
el.setRangeText(text, start, end, 'end'); el.setRangeText(text, start, end, 'end');
try { try {
@@ -259,7 +259,7 @@ function handleAddTag(tag: string) {
store.setPromptTextRaw(store.promptText ? store.promptText + ', ' + tag : tag); store.setPromptTextRaw(store.promptText ? store.promptText + ', ' + tag : tag);
return; return;
} }
el.focus(); el.focus();
let start = el.selectionStart ?? store.promptText.length; let start = el.selectionStart ?? store.promptText.length;
@@ -277,7 +277,7 @@ function handleAddTag(tag: string) {
const rawToken = textVal.slice(tokenStart, tokenEnd); const rawToken = textVal.slice(tokenStart, tokenEnd);
const trimmedToken = rawToken.trim(); const trimmedToken = rawToken.trim();
if (trimmedToken.length > 0) { if (trimmedToken.length > 0) {
const tokenCenter = tokenStart + rawToken.length / 2; const tokenCenter = tokenStart + rawToken.length / 2;
if (start < tokenCenter) { if (start < tokenCenter) {
@@ -289,43 +289,43 @@ function handleAddTag(tag: string) {
let prefix = ''; let prefix = '';
let suffix = ''; let suffix = '';
if (start > 0) { if (start > 0) {
const prevText = textVal.slice(0, start); const prevText = textVal.slice(0, start);
if (/[^,\s]$/.test(prevText)) { if (/[^,\s]$/.test(prevText)) {
prefix = ', '; prefix = ', ';
} else if (/[,]$/.test(prevText)) { } else if (/[,]$/.test(prevText)) {
prefix = ' '; prefix = ' ';
} else if (/[,]\s+$/.test(prevText)) { } else if (/[,]\s+$/.test(prevText)) {
prefix = ''; prefix = '';
} else if (/\s+$/.test(prevText)) { } else if (/\s+$/.test(prevText)) {
const trimmedPrev = prevText.trimEnd(); const trimmedPrev = prevText.trimEnd();
if (trimmedPrev.length > 0 && !/[,]$/.test(trimmedPrev)) { if (trimmedPrev.length > 0 && !/[,]$/.test(trimmedPrev)) {
if (!/[,]\s*$/.test(prevText)) { if (!/[,]\s*$/.test(prevText)) {
prefix = ', '; prefix = ', ';
}
} }
}
} }
} }
if (start < len) { if (start < len) {
const nextText = textVal.slice(start); const nextText = textVal.slice(start);
if (/^[^,\s]/.test(nextText)) { if (/^[^,\s]/.test(nextText)) {
suffix = ', '; suffix = ', ';
} else if (/^\s+[^,]/.test(nextText)) { } else if (/^\s+[^,]/.test(nextText)) {
suffix = ', '; suffix = ', ';
} }
} }
const toInsert = prefix + tag + suffix; const toInsert = prefix + tag + suffix;
applyTextReplacement(el, start, start, toInsert); applyTextReplacement(el, start, start, toInsert);
nextTick(() => { nextTick(() => {
el.focus(); el.focus();
}); });
} }
async function copyLeft() { async function copyLeft() {
try { try {
await navigator.clipboard.writeText(store.promptText); await navigator.clipboard.writeText(store.promptText);
showNotification('提示词已复制到剪贴板', 'success'); showNotification('提示词已复制到剪贴板', 'success');
@@ -342,16 +342,16 @@ function unifyPriorityStyle() {
const { core, weight, wrappers } = parseDetailedToken(token); const { core, weight, wrappers } = parseDetailedToken(token);
let result = core; let result = core;
let currentWrappers = [...wrappers]; let currentWrappers = [...wrappers];
if (weight !== undefined && weight !== 1) { if (weight !== undefined && weight !== 1) {
const lastWrapper = currentWrappers[currentWrappers.length - 1]; const lastWrapper = currentWrappers[currentWrappers.length - 1];
if (lastWrapper === '()') { if (lastWrapper === '()') {
currentWrappers.pop(); currentWrappers.pop();
} }
const wStr = Number.isInteger(weight) ? weight.toString() : weight.toFixed(2).replace(/\.?0+$/, ''); const wStr = Number.isInteger(weight) ? weight.toString() : weight.toFixed(2).replace(/\.?0+$/, '');
result = `(${result}:${wStr})`; result = `(${result}:${wStr})`;
} }
return store.wrapToken(result, currentWrappers); return store.wrapToken(result, currentWrappers);
}); });
applyFullPrompt(processed.join(', ')); applyFullPrompt(processed.join(', '));
@@ -359,16 +359,16 @@ function unifyPriorityStyle() {
} }
// 新增功能方法 // 新增功能方法
function toggleUnderscoreSpace() { function toggleUnderscoreSpace() {
const tokens = splitTokens(text.value); const tokens = splitTokens(text.value);
// 1. 统计全局倾向 // 1. 统计全局倾向
let spaceCount = 0; let spaceCount = 0;
let underscoreCount = 0; let underscoreCount = 0;
// 预解析所有 Token // 预解析所有 Token
const parsedList = tokens.map(t => parseDetailedToken(t)); const parsedList = tokens.map(t => parseDetailedToken(t));
parsedList.forEach(({ core }) => { parsedList.forEach(({ core }) => {
for (const char of core) { for (const char of core) {
if (char === ' ') spaceCount++; if (char === ' ') spaceCount++;
@@ -384,35 +384,35 @@ function toggleUnderscoreSpace() {
const newTokens = parsedList.map(({ core, weight, wrappers }) => { const newTokens = parsedList.map(({ core, weight, wrappers }) => {
let newCore = core; let newCore = core;
if (targetIsUnderscore) { if (targetIsUnderscore) {
newCore = newCore.replace(/ /g, '_'); newCore = newCore.replace(/ /g, '_');
} else { } else {
newCore = newCore.replace(/_/g, ' '); newCore = newCore.replace(/_/g, ' ');
} }
// 重构 Token (保持权重和包裹层) // 重构 Token (保持权重和包裹层)
let result = newCore; let result = newCore;
let currentWrappers = [...wrappers]; let currentWrappers = [...wrappers];
if (weight !== undefined && weight !== 1) { if (weight !== undefined && weight !== 1) {
const lastWrapper = currentWrappers[currentWrappers.length - 1]; const lastWrapper = currentWrappers[currentWrappers.length - 1];
if (lastWrapper === '()') { if (lastWrapper === '()') {
currentWrappers.pop(); currentWrappers.pop();
} }
const wStr = Number.isInteger(weight) ? weight.toString() : weight.toFixed(2).replace(/\.?0+$/, ''); const wStr = Number.isInteger(weight) ? weight.toString() : weight.toFixed(2).replace(/\.?0+$/, '');
result = `(${result}:${wStr})`; result = `(${result}:${wStr})`;
} }
return store.wrapToken(result, currentWrappers); return store.wrapToken(result, currentWrappers);
}); });
applyFullPrompt(newTokens.join(', ')); applyFullPrompt(newTokens.join(', '));
showNotification(targetIsUnderscore ? '已统一为下划线格式' : '已统一为空格格式', 'success'); showNotification(targetIsUnderscore ? '已统一为下划线格式' : '已统一为空格格式', 'success');
} }
function addWrapperToToken(index: number) { function addWrapperToToken(index: number) {
const tokens = splitTokensLocal(text.value); const tokens = splitTokensLocal(text.value);
if (index < 0 || index >= tokens.length) return; if (index < 0 || index >= tokens.length) return;
const token = tokens[index]!; const token = tokens[index]!;
const parsed = store.parseTokenWrappers(token); const parsed = store.parseTokenWrappers(token);
@@ -425,12 +425,12 @@ function addWrapperToToken(index: number) {
const newWrappers = [...wrappers, priorityStyle.value]; const newWrappers = [...wrappers, priorityStyle.value];
tokens[index] = store.wrapToken(core, newWrappers); tokens[index] = store.wrapToken(core, newWrappers);
} }
applyFullPrompt(tokens.join(', ')); applyFullPrompt(tokens.join(', '));
showNotification('已添加优先级', 'success'); showNotification('已添加优先级', 'success');
} }
function removeWrapperFromToken(index: number) { function removeWrapperFromToken(index: number) {
const tokens = splitTokensLocal(text.value); const tokens = splitTokensLocal(text.value);
if (index < 0 || index >= tokens.length) return; if (index < 0 || index >= tokens.length) return;
const token = tokens[index]!; const token = tokens[index]!;
const { core, wrappers } = store.parseTokenWrappers(token); const { core, wrappers } = store.parseTokenWrappers(token);
@@ -441,7 +441,7 @@ function removeWrapperFromToken(index: number) {
const newWrappers = wrappers.slice(0, -1); const newWrappers = wrappers.slice(0, -1);
tokens[index] = store.wrapToken(core, newWrappers); tokens[index] = store.wrapToken(core, newWrappers);
} }
applyFullPrompt(tokens.join(', ')); applyFullPrompt(tokens.join(', '));
showNotification('已调整优先级', 'success'); showNotification('已调整优先级', 'success');
} }
@@ -493,11 +493,11 @@ function onPointerDown(index: number, e: PointerEvent) {
} }
function handlePointerMove(e: PointerEvent) { function handlePointerMove(e: PointerEvent) {
lastX.value = e.clientX; lastX.value = e.clientX;
lastY.value = e.clientY; lastY.value = e.clientY;
const dx = e.clientX - startX.value; const dx = e.clientX - startX.value;
const dy = e.clientY - startY.value; const dy = e.clientY - startY.value;
if (!dragStarted.value) { if (!dragStarted.value) {
if (Math.hypot(dx, dy) > DRAG_THRESHOLD) { if (Math.hypot(dx, dy) > DRAG_THRESHOLD) {
dragStarted.value = true; dragStarted.value = true;
@@ -506,7 +506,7 @@ function handlePointerMove(e: PointerEvent) {
} }
return; return;
} }
if (!isDragging.value) return; if (!isDragging.value) return;
// 使用 requestAnimationFrame 节流渲染 // 使用 requestAnimationFrame 节流渲染
@@ -520,9 +520,9 @@ function handlePointerMove(e: PointerEvent) {
function handlePointerUp(e: PointerEvent) { function handlePointerUp(e: PointerEvent) {
window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointermove', handlePointerMove);
if (!dragStarted.value || draggingIndex.value == null) { if (!dragStarted.value || draggingIndex.value == null) {
cleanupDrag(); cleanupDrag();
return; return;
} }
const from = draggingIndex.value!; const from = draggingIndex.value!;
const j = overIndex.value; const j = overIndex.value;
@@ -576,7 +576,7 @@ function createPointerPreview(index: number) {
preview.style.left = '0'; preview.style.left = '0';
preview.style.zIndex = '1000'; preview.style.zIndex = '1000';
preview.style.pointerEvents = 'none'; preview.style.pointerEvents = 'none';
;(preview.style as any).contain = 'layout style paint'; ; (preview.style as any).contain = 'layout style paint';
preview.style.willChange = 'transform, opacity'; preview.style.willChange = 'transform, opacity';
document.body.appendChild(preview); document.body.appendChild(preview);
dragPreview.value = preview; dragPreview.value = preview;
@@ -590,7 +590,7 @@ function positionPreview(x: number, y: number) {
function updateOverIndexAndSideFast(clientX: number, clientY: number) { function updateOverIndexAndSideFast(clientX: number, clientY: number) {
const dragContainer = tokenMappingRef.value?.dragContainer; const dragContainer = tokenMappingRef.value?.dragContainer;
if (!dragContainer) return; if (!dragContainer) return;
// 计算鼠标在容器内的相对坐标 // 计算鼠标在容器内的相对坐标
const containerRect = dragContainer.getBoundingClientRect(); const containerRect = dragContainer.getBoundingClientRect();
const relX = clientX - containerRect.left; const relX = clientX - containerRect.left;
@@ -598,7 +598,7 @@ function updateOverIndexAndSideFast(clientX: number, clientY: number) {
// 在缓存中查找命中的 Token // 在缓存中查找命中的 Token
// 简单碰撞检测 // 简单碰撞检测
const target = cachedTokenRects.value.find(item => const target = cachedTokenRects.value.find(item =>
relX >= item.left && relX <= item.left + item.width && relX >= item.left && relX <= item.left + item.width &&
relY >= item.top && relY <= item.top + item.height relY >= item.top && relY <= item.top + item.height
); );
@@ -643,13 +643,13 @@ function addTokenAfter(i: number) {
} }
function savePreset() { function savePreset() {
if (!presetName.value.trim()) { if (!presetName.value.trim()) {
showNotification('请输入预设名称', 'error'); showNotification('请输入预设名称', 'error');
return; return;
} }
const name = presetName.value.trim(); const name = presetName.value.trim();
// 只保存到新的扩展预设系统 // 只保存到新的扩展预设系统
const folderId = selectedFolderId.value || store.presetManagement?.settings?.defaultFolder; const folderId = selectedFolderId.value || store.presetManagement?.settings?.defaultFolder;
store.createExtendedPreset({ store.createExtendedPreset({
@@ -659,7 +659,7 @@ function savePreset() {
description: '从编辑器快速保存', description: '从编辑器快速保存',
folderId: folderId folderId: folderId
}); });
showNotification(`预设「${name}」已保存到预设管理`, 'success'); showNotification(`预设「${name}」已保存到预设管理`, 'success');
presetName.value = ''; presetName.value = '';
} }
@@ -716,7 +716,7 @@ function displayTrans(key: string): string {
const { core, weight, wrappers, prefix, suffix } = parseDetailedToken(key); const { core, weight, wrappers, prefix, suffix } = parseDetailedToken(key);
const tag = store.getTagByKey(core); const tag = store.getTagByKey(core);
const translatedCore = tag?.translation?.[selectedLang.value] ?? tag?.key ?? core; const translatedCore = tag?.translation?.[selectedLang.value] ?? tag?.key ?? core;
return constructToken(translatedCore, weight, wrappers, prefix, suffix); return constructToken(translatedCore, weight, wrappers, prefix, suffix);
} }
@@ -728,84 +728,38 @@ function isRemoveDisabled(token: string): boolean {
<template> <template>
<div class="pe-root"> <div class="pe-root">
<EditorToolbar <EditorToolbar :languages="store.languages as LangCode[]" v-model:selected-lang="selectedLang"
:languages="store.languages as LangCode[]" v-model:preset-name="presetName" v-model:selected-folder-id="selectedFolderId"
v-model:selected-lang="selectedLang" v-model:show-preset-dropdown="showPresetDropdown" :folder-tree="folderTree" :flattened-folders="flattenedFolders"
v-model:preset-name="presetName" @copy="copyLeft" @save-preset="savePreset" @preset-load="handlePresetLoad" @preset-save="handlePresetSave"
v-model:selected-folder-id="selectedFolderId" @preset-delete="handlePresetDelete" @preset-rename="handlePresetRename" />
v-model:show-preset-dropdown="showPresetDropdown"
:folder-tree="folderTree"
:flattened-folders="flattenedFolders"
@copy="copyLeft"
@save-preset="savePreset"
@preset-load="handlePresetLoad"
@preset-save="handlePresetSave"
@preset-delete="handlePresetDelete"
@preset-rename="handlePresetRename"
/>
<main class="pe-main"> <div class="pe-main">
<EditorInput <EditorInput ref="editorInputRef" v-model:text="text" v-model:priority-style="priorityStyle"
ref="editorInputRef" v-model:priority-step="priorityStep" :suggestions="suggestions"
v-model:text="text"
v-model:priority-style="priorityStyle"
v-model:priority-step="priorityStep"
:suggestions="suggestions"
:get-suggestions="(prefix, limit) => store.getSuggestions(prefix, limit)" :get-suggestions="(prefix, limit) => store.getSuggestions(prefix, limit)"
@update-suggestions="updateSuggestionsFromText" @update-suggestions="updateSuggestionsFromText" @copy="copyLeft" @replace-cn-comma="replaceCnComma"
@copy="copyLeft" @format-prompt="formatPrompt" @unify-priority="unifyPriorityStyle" @toggle-underscore="toggleUnderscoreSpace"
@replace-cn-comma="replaceCnComma" @add-tag="handleAddTag" />
@format-prompt="formatPrompt"
@unify-priority="unifyPriorityStyle" <TokenMappingPanel ref="tokenMappingRef" :tokens="tokens" :selected-lang="selectedLang"
@toggle-underscore="toggleUnderscoreSpace" v-model:view-mode="viewMode" :dragging-index="draggingIndex" :over-index="overIndex" :insert-side="insertSide"
@add-tag="handleAddTag" :is-dragging="isDragging" :edit-suggestions="editSuggestions" :priority-style="priorityStyle"
/> :display-trans="displayTrans" :is-unmapped="isUnmapped" :get-token-wrapper-info="getTokenWrapperInfo"
:has-weight-suffix="hasWeightSuffix" :get-suggestions="(prefix, limit) => store.getSuggestions(prefix, limit)"
<TokenMappingPanel @pointer-down="onPointerDown" @begin-edit="(i) => editingIndex = i" @commit-edit="commitEdit"
ref="tokenMappingRef" @cancel-edit="() => editingIndex = null" @show-add-map="showAddMap" @add-wrapper="addWrapperToToken"
:tokens="tokens" @remove-wrapper="removeWrapperFromToken" @remove-token="removeToken" @add-token-after="addTokenAfter"
:selected-lang="selectedLang"
v-model:view-mode="viewMode"
:dragging-index="draggingIndex"
:over-index="overIndex"
:insert-side="insertSide"
:is-dragging="isDragging"
:edit-suggestions="editSuggestions"
:priority-style="priorityStyle"
:display-trans="displayTrans"
:is-unmapped="isUnmapped"
:get-token-wrapper-info="getTokenWrapperInfo"
:has-weight-suffix="hasWeightSuffix"
:get-suggestions="(prefix, limit) => store.getSuggestions(prefix, limit)"
@pointer-down="onPointerDown"
@begin-edit="(i) => editingIndex = i"
@commit-edit="commitEdit"
@cancel-edit="() => editingIndex = null"
@show-add-map="showAddMap"
@add-wrapper="addWrapperToToken"
@remove-wrapper="removeWrapperFromToken"
@remove-token="removeToken"
@add-token-after="addTokenAfter"
@show-translation-popup="() => { translationTargetToken = null; showTranslationPopup = true; }" @show-translation-popup="() => { translationTargetToken = null; showTranslationPopup = true; }"
@update-edit-value="updateEditSuggestionsFromValue" @update-edit-value="updateEditSuggestionsFromValue" />
/> </div>
</main>
<!-- 翻译弹窗 --> <!-- 翻译弹窗 -->
<TranslationPopup <TranslationPopup :visible="showTranslationPopup" :tokens="translationTokens" :target-lang="selectedLang"
:visible="showTranslationPopup" @close="() => { showTranslationPopup = false; translationTargetToken = null; }" @apply="handleApplyTranslation" />
:tokens="translationTokens"
:target-lang="selectedLang"
@close="() => { showTranslationPopup = false; translationTargetToken = null; }"
@apply="handleApplyTranslation"
/>
<!-- 通知组件 --> <!-- 通知组件 -->
<NotificationToast <NotificationToast :message="notification.message" :type="notification.type" :show="notification.show" />
:message="notification.message"
:type="notification.type"
:show="notification.show"
/>
</div> </div>
</template> </template>
@@ -835,10 +789,9 @@ function isRemoveDisabled(token: string): boolean {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
} }
.pe-left-pane { .pe-left-pane {
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border);
} }
} }
</style> </style>